mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-28 21:26:40 +00:00
Compare commits
3 Commits
logs/peerl
...
set-comman
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
355bab9bb4 | ||
|
|
6922826919 | ||
|
|
56a1a75e3f |
34
README.md
34
README.md
@@ -134,3 +134,37 @@ We use open-source technologies like [WireGuard®](https://www.wireguard.com/),
|
|||||||
### Legal
|
### Legal
|
||||||
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.
|
||||||
|
|
||||||
|
## Configuration Management
|
||||||
|
|
||||||
|
Netbird now supports direct configuration management via CLI commands:
|
||||||
|
|
||||||
|
- You can use `netbird set` as a regular user if the daemon is running; it will securely update the config via the daemon.
|
||||||
|
- If the daemon is not running, you need write access to the config file (typically requires root).
|
||||||
|
|
||||||
|
### Set a configuration value
|
||||||
|
|
||||||
|
```
|
||||||
|
netbird set <setting> <value>
|
||||||
|
# or using environment variables
|
||||||
|
NB_INTERFACE_NAME=utun5 netbird set interface-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get a configuration value
|
||||||
|
|
||||||
|
```
|
||||||
|
netbird get <setting>
|
||||||
|
# or using environment variables
|
||||||
|
NB_INTERFACE_NAME=utun5 netbird get interface-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Show all configuration values
|
||||||
|
|
||||||
|
```
|
||||||
|
netbird show
|
||||||
|
```
|
||||||
|
|
||||||
|
- All settings support environment variable overrides: `NB_<SETTING>` or `WT_<SETTING>` (e.g. `NB_ENABLE_ROSENPASS=true`).
|
||||||
|
- Supported settings: management-url, admin-url, interface-name, external-ip-map, extra-iface-blacklist, dns-resolver-address, extra-dns-labels, preshared-key, enable-rosenpass, rosenpass-permissive, allow-server-ssh, network-monitor, disable-auto-connect, disable-client-routes, disable-server-routes, disable-dns, disable-firewall, block-lan-access, block-inbound, enable-lazy-connection, wireguard-port, dns-router-interval.
|
||||||
|
|
||||||
|
See `netbird set --help`, `netbird get --help`, and `netbird show --help` for more details.
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal"
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
"github.com/netbirdio/netbird/upload-server/types"
|
"github.com/netbirdio/netbird/upload-server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,6 +88,30 @@ var (
|
|||||||
Long: "",
|
Long: "",
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCmd = &cobra.Command{
|
||||||
|
Use: "get <setting>",
|
||||||
|
Short: "Get a configuration value from the config file",
|
||||||
|
Long: `Get a configuration value from the Netbird config file. You can also use NB_<SETTING> or WT_<SETTING> environment variables to override the value (same as 'set').`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: getFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
showCmd = &cobra.Command{
|
||||||
|
Use: "show",
|
||||||
|
Short: "Show all configuration values",
|
||||||
|
Long: `Show all configuration values from the Netbird config file, with environment variable overrides if present.`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: showFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadCmd = &cobra.Command{
|
||||||
|
Use: "reload",
|
||||||
|
Short: "Reload the configuration in the daemon (daemon mode)",
|
||||||
|
Long: `Reload the configuration from disk in the running daemon. Use after 'set' to apply changes without restarting the service.`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: reloadFunc,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Execute executes the root command.
|
// Execute executes the root command.
|
||||||
@@ -152,6 +177,9 @@ func init() {
|
|||||||
rootCmd.AddCommand(networksCMD)
|
rootCmd.AddCommand(networksCMD)
|
||||||
rootCmd.AddCommand(forwardingRulesCmd)
|
rootCmd.AddCommand(forwardingRulesCmd)
|
||||||
rootCmd.AddCommand(debugCmd)
|
rootCmd.AddCommand(debugCmd)
|
||||||
|
rootCmd.AddCommand(getCmd)
|
||||||
|
rootCmd.AddCommand(showCmd)
|
||||||
|
rootCmd.AddCommand(reloadCmd)
|
||||||
|
|
||||||
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd) // service control commands are subcommands of service
|
||||||
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
serviceCmd.AddCommand(installCmd, uninstallCmd) // service installer commands are subcommands of service
|
||||||
@@ -408,3 +436,167 @@ func getClient(cmd *cobra.Command) (*grpc.ClientConn, error) {
|
|||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFunc(cmd *cobra.Command, args []string) error {
|
||||||
|
setting := args[0]
|
||||||
|
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
|
||||||
|
if v, ok := os.LookupEnv("NB_" + upper); ok {
|
||||||
|
cmd.Println(v)
|
||||||
|
return nil
|
||||||
|
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
|
||||||
|
cmd.Println(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
config, err := internal.ReadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read config: %v", err)
|
||||||
|
}
|
||||||
|
switch setting {
|
||||||
|
case "management-url":
|
||||||
|
cmd.Println(config.ManagementURL.String())
|
||||||
|
case "admin-url":
|
||||||
|
cmd.Println(config.AdminURL.String())
|
||||||
|
case "interface-name":
|
||||||
|
cmd.Println(config.WgIface)
|
||||||
|
case "external-ip-map":
|
||||||
|
cmd.Println(strings.Join(config.NATExternalIPs, ","))
|
||||||
|
case "extra-iface-blacklist":
|
||||||
|
cmd.Println(strings.Join(config.IFaceBlackList, ","))
|
||||||
|
case "dns-resolver-address":
|
||||||
|
cmd.Println(config.CustomDNSAddress)
|
||||||
|
case "extra-dns-labels":
|
||||||
|
cmd.Println(config.DNSLabels.SafeString())
|
||||||
|
case "preshared-key":
|
||||||
|
cmd.Println(config.PreSharedKey)
|
||||||
|
case "enable-rosenpass":
|
||||||
|
cmd.Println(config.RosenpassEnabled)
|
||||||
|
case "rosenpass-permissive":
|
||||||
|
cmd.Println(config.RosenpassPermissive)
|
||||||
|
case "allow-server-ssh":
|
||||||
|
if config.ServerSSHAllowed != nil {
|
||||||
|
cmd.Println(*config.ServerSSHAllowed)
|
||||||
|
} else {
|
||||||
|
cmd.Println(false)
|
||||||
|
}
|
||||||
|
case "network-monitor":
|
||||||
|
if config.NetworkMonitor != nil {
|
||||||
|
cmd.Println(*config.NetworkMonitor)
|
||||||
|
} else {
|
||||||
|
cmd.Println(false)
|
||||||
|
}
|
||||||
|
case "disable-auto-connect":
|
||||||
|
cmd.Println(config.DisableAutoConnect)
|
||||||
|
case "disable-client-routes":
|
||||||
|
cmd.Println(config.DisableClientRoutes)
|
||||||
|
case "disable-server-routes":
|
||||||
|
cmd.Println(config.DisableServerRoutes)
|
||||||
|
case "disable-dns":
|
||||||
|
cmd.Println(config.DisableDNS)
|
||||||
|
case "disable-firewall":
|
||||||
|
cmd.Println(config.DisableFirewall)
|
||||||
|
case "block-lan-access":
|
||||||
|
cmd.Println(config.BlockLANAccess)
|
||||||
|
case "block-inbound":
|
||||||
|
cmd.Println(config.BlockInbound)
|
||||||
|
case "enable-lazy-connection":
|
||||||
|
cmd.Println(config.LazyConnectionEnabled)
|
||||||
|
case "wireguard-port":
|
||||||
|
cmd.Println(config.WgPort)
|
||||||
|
case "dns-router-interval":
|
||||||
|
cmd.Println(config.DNSRouteInterval)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown setting: %s", setting)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func showFunc(cmd *cobra.Command, args []string) error {
|
||||||
|
config, err := internal.ReadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read config: %v", err)
|
||||||
|
}
|
||||||
|
settings := []string{
|
||||||
|
"management-url", "admin-url", "interface-name", "external-ip-map", "extra-iface-blacklist", "dns-resolver-address", "extra-dns-labels", "preshared-key", "enable-rosenpass", "rosenpass-permissive", "allow-server-ssh", "network-monitor", "disable-auto-connect", "disable-client-routes", "disable-server-routes", "disable-dns", "disable-firewall", "block-lan-access", "block-inbound", "enable-lazy-connection", "wireguard-port", "dns-router-interval",
|
||||||
|
}
|
||||||
|
for _, setting := range settings {
|
||||||
|
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
|
||||||
|
var val string
|
||||||
|
if v, ok := os.LookupEnv("NB_" + upper); ok {
|
||||||
|
val = v + " (from NB_ env)"
|
||||||
|
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
|
||||||
|
val = v + " (from WT_ env)"
|
||||||
|
} else {
|
||||||
|
switch setting {
|
||||||
|
case "management-url":
|
||||||
|
val = config.ManagementURL.String()
|
||||||
|
case "admin-url":
|
||||||
|
val = config.AdminURL.String()
|
||||||
|
case "interface-name":
|
||||||
|
val = config.WgIface
|
||||||
|
case "external-ip-map":
|
||||||
|
val = strings.Join(config.NATExternalIPs, ",")
|
||||||
|
case "extra-iface-blacklist":
|
||||||
|
val = strings.Join(config.IFaceBlackList, ",")
|
||||||
|
case "dns-resolver-address":
|
||||||
|
val = config.CustomDNSAddress
|
||||||
|
case "extra-dns-labels":
|
||||||
|
val = config.DNSLabels.SafeString()
|
||||||
|
case "preshared-key":
|
||||||
|
val = config.PreSharedKey
|
||||||
|
case "enable-rosenpass":
|
||||||
|
val = fmt.Sprintf("%v", config.RosenpassEnabled)
|
||||||
|
case "rosenpass-permissive":
|
||||||
|
val = fmt.Sprintf("%v", config.RosenpassPermissive)
|
||||||
|
case "allow-server-ssh":
|
||||||
|
if config.ServerSSHAllowed != nil {
|
||||||
|
val = fmt.Sprintf("%v", *config.ServerSSHAllowed)
|
||||||
|
} else {
|
||||||
|
val = "false"
|
||||||
|
}
|
||||||
|
case "network-monitor":
|
||||||
|
if config.NetworkMonitor != nil {
|
||||||
|
val = fmt.Sprintf("%v", *config.NetworkMonitor)
|
||||||
|
} else {
|
||||||
|
val = "false"
|
||||||
|
}
|
||||||
|
case "disable-auto-connect":
|
||||||
|
val = fmt.Sprintf("%v", config.DisableAutoConnect)
|
||||||
|
case "disable-client-routes":
|
||||||
|
val = fmt.Sprintf("%v", config.DisableClientRoutes)
|
||||||
|
case "disable-server-routes":
|
||||||
|
val = fmt.Sprintf("%v", config.DisableServerRoutes)
|
||||||
|
case "disable-dns":
|
||||||
|
val = fmt.Sprintf("%v", config.DisableDNS)
|
||||||
|
case "disable-firewall":
|
||||||
|
val = fmt.Sprintf("%v", config.DisableFirewall)
|
||||||
|
case "block-lan-access":
|
||||||
|
val = fmt.Sprintf("%v", config.BlockLANAccess)
|
||||||
|
case "block-inbound":
|
||||||
|
val = fmt.Sprintf("%v", config.BlockInbound)
|
||||||
|
case "enable-lazy-connection":
|
||||||
|
val = fmt.Sprintf("%v", config.LazyConnectionEnabled)
|
||||||
|
case "wireguard-port":
|
||||||
|
val = fmt.Sprintf("%d", config.WgPort)
|
||||||
|
case "dns-router-interval":
|
||||||
|
val = config.DNSRouteInterval.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Printf("%-22s: %s\n", setting, val)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadFunc(cmd *cobra.Command, args []string) error {
|
||||||
|
conn, err := getClient(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
_, err = client.ReloadConfig(cmd.Context(), &proto.ReloadConfigRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reload config in daemon: %v", err)
|
||||||
|
}
|
||||||
|
cmd.Println("Configuration reloaded in daemon.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
475
client/cmd/set.go
Normal file
475
client/cmd/set.go
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
osuser "os/user"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/domain"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var setCmd = &cobra.Command{
|
||||||
|
Use: "set <setting> <value>",
|
||||||
|
Short: "Set a configuration value without running up",
|
||||||
|
Long: `Set a configuration value in the Netbird config file without running 'up'.
|
||||||
|
|
||||||
|
You can also set values via environment variables NB_<SETTING> or WT_<SETTING> (e.g. NB_INTERFACE_NAME=utun5 netbird set interface-name).
|
||||||
|
|
||||||
|
Supported settings:
|
||||||
|
management-url (string) e.g. https://api.netbird.io:443
|
||||||
|
admin-url (string) e.g. https://app.netbird.io:443
|
||||||
|
interface-name (string) e.g. utun5
|
||||||
|
external-ip-map (list) comma-separated, e.g. 12.34.56.78,12.34.56.79/eth0
|
||||||
|
extra-iface-blacklist (list) comma-separated, e.g. eth1,eth2
|
||||||
|
dns-resolver-address (string) e.g. 127.0.0.1:5053
|
||||||
|
extra-dns-labels (list) comma-separated, e.g. vpc1,mgmt1
|
||||||
|
preshared-key (string)
|
||||||
|
enable-rosenpass (bool) true/false
|
||||||
|
rosenpass-permissive (bool) true/false
|
||||||
|
allow-server-ssh (bool) true/false
|
||||||
|
network-monitor (bool) true/false
|
||||||
|
disable-auto-connect (bool) true/false
|
||||||
|
disable-client-routes (bool) true/false
|
||||||
|
disable-server-routes (bool) true/false
|
||||||
|
disable-dns (bool) true/false
|
||||||
|
disable-firewall (bool) true/false
|
||||||
|
block-lan-access (bool) true/false
|
||||||
|
block-inbound (bool) true/false
|
||||||
|
enable-lazy-connection (bool) true/false
|
||||||
|
wireguard-port (int) e.g. 51820
|
||||||
|
dns-router-interval (duration) e.g. 1m, 30s
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
NB_INTERFACE_NAME=utun5 netbird set interface-name
|
||||||
|
netbird set wireguard-port 51820
|
||||||
|
netbird set external-ip-map 12.34.56.78,12.34.56.79/eth0
|
||||||
|
netbird set enable-rosenpass true
|
||||||
|
netbird set dns-router-interval 2m
|
||||||
|
netbird set extra-dns-labels vpc1,mgmt1
|
||||||
|
netbird set disable-firewall true
|
||||||
|
`,
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
|
RunE: setFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(setCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFunc(cmd *cobra.Command, args []string) error {
|
||||||
|
setting := args[0]
|
||||||
|
var value string
|
||||||
|
|
||||||
|
// Check environment variables first
|
||||||
|
upper := strings.ToUpper(strings.ReplaceAll(setting, "-", "_"))
|
||||||
|
if v, ok := os.LookupEnv("NB_" + upper); ok {
|
||||||
|
value = v
|
||||||
|
} else if v, ok := os.LookupEnv("WT_" + upper); ok {
|
||||||
|
value = v
|
||||||
|
} else {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return fmt.Errorf("missing value for setting %s", setting)
|
||||||
|
}
|
||||||
|
value = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not root, try to use the daemon (only if cmd is not nil)
|
||||||
|
if cmd != nil {
|
||||||
|
if u, err := osuser.Current(); err == nil && u.Uid != "0" {
|
||||||
|
conn, err := getClient(cmd)
|
||||||
|
if err == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
client := proto.NewDaemonServiceClient(conn)
|
||||||
|
_, err = client.SetConfigValue(cmd.Context(), &proto.SetConfigValueRequest{Setting: setting, Value: value})
|
||||||
|
if err == nil {
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Println("Configuration updated via daemon.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Configuration updated via daemon.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return fmt.Errorf("daemon error: %v", s.Message())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to update config via daemon: %v", err)
|
||||||
|
}
|
||||||
|
// else: fall back to direct file write
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch setting {
|
||||||
|
case "management-url":
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, ManagementURL: value}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set management-url: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set management-url to: %s\n", value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set management-url to: %s\n", value)
|
||||||
|
}
|
||||||
|
case "admin-url":
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, AdminURL: value}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set admin-url: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set admin-url to: %s\n", value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set admin-url to: %s\n", value)
|
||||||
|
}
|
||||||
|
case "interface-name":
|
||||||
|
if err := parseInterfaceName(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, InterfaceName: &value}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set interface-name: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set interface-name to: %s\n", value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set interface-name to: %s\n", value)
|
||||||
|
}
|
||||||
|
case "external-ip-map":
|
||||||
|
var ips []string
|
||||||
|
if value == "" {
|
||||||
|
ips = []string{}
|
||||||
|
} else {
|
||||||
|
ips = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
if err := validateNATExternalIPs(ips); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, NATExternalIPs: ips}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set external-ip-map: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set external-ip-map to: %v\n", ips)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set external-ip-map to: %v\n", ips)
|
||||||
|
}
|
||||||
|
case "extra-iface-blacklist":
|
||||||
|
var ifaces []string
|
||||||
|
if value == "" {
|
||||||
|
ifaces = []string{}
|
||||||
|
} else {
|
||||||
|
ifaces = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, ExtraIFaceBlackList: ifaces}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set extra-iface-blacklist: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set extra-iface-blacklist to: %v\n", ifaces)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set extra-iface-blacklist to: %v\n", ifaces)
|
||||||
|
}
|
||||||
|
case "dns-resolver-address":
|
||||||
|
if value != "" && !isValidAddrPort(value) {
|
||||||
|
return fmt.Errorf("%s is invalid, it should be formatted as IP:Port string or as an empty string like \"\"", value)
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, CustomDNSAddress: []byte(value)}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set dns-resolver-address: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set dns-resolver-address to: %s\n", value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set dns-resolver-address to: %s\n", value)
|
||||||
|
}
|
||||||
|
case "extra-dns-labels":
|
||||||
|
var labels []string
|
||||||
|
if value == "" {
|
||||||
|
labels = []string{}
|
||||||
|
} else {
|
||||||
|
labels = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
domains, err := domain.ValidateDomains(labels)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid DNS labels: %v", err)
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DNSLabels: domains}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set extra-dns-labels: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set extra-dns-labels to: %v\n", labels)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set extra-dns-labels to: %v\n", labels)
|
||||||
|
}
|
||||||
|
case "preshared-key":
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, PreSharedKey: &value}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set preshared-key: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set preshared-key to: %s\n", value)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set preshared-key to: %s\n", value)
|
||||||
|
}
|
||||||
|
case "hostname":
|
||||||
|
// Hostname is not persisted in config, so just print a warning
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Warning: hostname is not persisted in config. Use --hostname with up command.\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Warning: hostname is not persisted in config. Use --hostname with up command.\n")
|
||||||
|
}
|
||||||
|
case "enable-rosenpass":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, RosenpassEnabled: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set enable-rosenpass: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set enable-rosenpass to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set enable-rosenpass to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "rosenpass-permissive":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, RosenpassPermissive: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set rosenpass-permissive: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set rosenpass-permissive to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set rosenpass-permissive to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "allow-server-ssh":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, ServerSSHAllowed: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set allow-server-ssh: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set allow-server-ssh to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set allow-server-ssh to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "network-monitor":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, NetworkMonitor: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set network-monitor: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set network-monitor to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set network-monitor to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "disable-auto-connect":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DisableAutoConnect: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set disable-auto-connect: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set disable-auto-connect to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set disable-auto-connect to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "disable-client-routes":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DisableClientRoutes: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set disable-client-routes: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set disable-client-routes to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set disable-client-routes to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "disable-server-routes":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DisableServerRoutes: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set disable-server-routes: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set disable-server-routes to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set disable-server-routes to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "disable-dns":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DisableDNS: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set disable-dns: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set disable-dns to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set disable-dns to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "disable-firewall":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DisableFirewall: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set disable-firewall: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set disable-firewall to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set disable-firewall to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "block-lan-access":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, BlockLANAccess: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set block-lan-access: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set block-lan-access to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set block-lan-access to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "block-inbound":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, BlockInbound: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set block-inbound: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set block-inbound to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set block-inbound to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "enable-lazy-connection":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, LazyConnectionEnabled: &b}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set enable-lazy-connection: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set enable-lazy-connection to: %v\n", b)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set enable-lazy-connection to: %v\n", b)
|
||||||
|
}
|
||||||
|
case "wireguard-port":
|
||||||
|
p, err := parseUint16(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pi := int(p)
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, WireguardPort: &pi}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set wireguard-port: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set wireguard-port to: %d\n", p)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set wireguard-port to: %d\n", p)
|
||||||
|
}
|
||||||
|
case "dns-router-interval":
|
||||||
|
d, err := time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid duration: %v", err)
|
||||||
|
}
|
||||||
|
input := internal.ConfigInput{ConfigPath: configPath, DNSRouteInterval: &d}
|
||||||
|
_, err = internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set dns-router-interval: %v", err)
|
||||||
|
}
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Printf("Set dns-router-interval to: %s\n", d)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Set dns-router-interval to: %s\n", d)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown setting: %s", setting)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd != nil {
|
||||||
|
cmd.Println("Configuration updated successfully.")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Configuration updated successfully.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBool(val string) (bool, error) {
|
||||||
|
v := strings.ToLower(val)
|
||||||
|
if v == "true" || v == "1" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if v == "false" || v == "0" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("invalid boolean value: %s", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUint16(val string) (uint16, error) {
|
||||||
|
var p uint16
|
||||||
|
_, err := fmt.Sscanf(val, "%d", &p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid uint16 value: %s", val)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
162
client/cmd/set_test.go
Normal file
162
client/cmd/set_test.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetCommand_AllSettings(t *testing.T) {
|
||||||
|
tempFile, err := os.CreateTemp("", "config.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
// Write empty JSON object to the config file to avoid JSON parse errors
|
||||||
|
_, err = tempFile.WriteString("{}")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
configPath = tempFile.Name()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
setting string
|
||||||
|
value string
|
||||||
|
verify func(*testing.T, *internal.Config)
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"management-url", "https://test.mgmt:443", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, "https://test.mgmt:443", c.ManagementURL.String())
|
||||||
|
}, false},
|
||||||
|
{"admin-url", "https://test.admin:443", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, "https://test.admin:443", c.AdminURL.String())
|
||||||
|
}, false},
|
||||||
|
{"interface-name", "utun99", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, "utun99", c.WgIface)
|
||||||
|
}, false},
|
||||||
|
{"external-ip-map", "12.34.56.78,12.34.56.79", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, []string{"12.34.56.78", "12.34.56.79"}, c.NATExternalIPs)
|
||||||
|
}, false},
|
||||||
|
{"extra-iface-blacklist", "eth1,eth2", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Contains(t, c.IFaceBlackList, "eth1")
|
||||||
|
require.Contains(t, c.IFaceBlackList, "eth2")
|
||||||
|
}, false},
|
||||||
|
{"dns-resolver-address", "127.0.0.1:5053", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, "127.0.0.1:5053", c.CustomDNSAddress)
|
||||||
|
}, false},
|
||||||
|
{"extra-dns-labels", "vpc1,mgmt1", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, strings.Contains(c.DNSLabels.SafeString(), "vpc1"))
|
||||||
|
require.True(t, strings.Contains(c.DNSLabels.SafeString(), "mgmt1"))
|
||||||
|
}, false},
|
||||||
|
{"preshared-key", "testkey", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, "testkey", c.PreSharedKey)
|
||||||
|
}, false},
|
||||||
|
{"enable-rosenpass", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.RosenpassEnabled)
|
||||||
|
}, false},
|
||||||
|
{"rosenpass-permissive", "false", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.False(t, c.RosenpassPermissive)
|
||||||
|
}, false},
|
||||||
|
{"allow-server-ssh", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.NotNil(t, c.ServerSSHAllowed)
|
||||||
|
require.True(t, *c.ServerSSHAllowed)
|
||||||
|
}, false},
|
||||||
|
{"network-monitor", "false", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.NotNil(t, c.NetworkMonitor)
|
||||||
|
require.False(t, *c.NetworkMonitor)
|
||||||
|
}, false},
|
||||||
|
{"disable-auto-connect", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.DisableAutoConnect)
|
||||||
|
}, false},
|
||||||
|
{"disable-client-routes", "false", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.False(t, c.DisableClientRoutes)
|
||||||
|
}, false},
|
||||||
|
{"disable-server-routes", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.DisableServerRoutes)
|
||||||
|
}, false},
|
||||||
|
{"disable-dns", "false", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.False(t, c.DisableDNS)
|
||||||
|
}, false},
|
||||||
|
{"disable-firewall", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.DisableFirewall)
|
||||||
|
}, false},
|
||||||
|
{"block-lan-access", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.BlockLANAccess)
|
||||||
|
}, false},
|
||||||
|
{"block-inbound", "false", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.False(t, c.BlockInbound)
|
||||||
|
}, false},
|
||||||
|
{"enable-lazy-connection", "true", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.True(t, c.LazyConnectionEnabled)
|
||||||
|
}, false},
|
||||||
|
{"wireguard-port", "51820", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, 51820, c.WgPort)
|
||||||
|
}, false},
|
||||||
|
{"dns-router-interval", "2m", func(t *testing.T, c *internal.Config) {
|
||||||
|
require.Equal(t, 2*time.Minute, c.DNSRouteInterval)
|
||||||
|
}, false},
|
||||||
|
// Invalid cases
|
||||||
|
{"enable-rosenpass", "notabool", nil, true},
|
||||||
|
{"wireguard-port", "notanint", nil, true},
|
||||||
|
{"dns-router-interval", "notaduration", nil, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.setting+"="+tt.value, func(t *testing.T) {
|
||||||
|
args := []string{tt.setting, tt.value}
|
||||||
|
err := setFunc(nil, args)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err := internal.ReadConfig(configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if tt.verify != nil {
|
||||||
|
tt.verify(t, config)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCommand_EnvVars(t *testing.T) {
|
||||||
|
tempFile, err := os.CreateTemp("", "config.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
// Write empty JSON object to the config file to avoid JSON parse errors
|
||||||
|
_, err = tempFile.WriteString("{}")
|
||||||
|
require.NoError(t, err)
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
configPath = tempFile.Name()
|
||||||
|
|
||||||
|
os.Setenv("NB_INTERFACE_NAME", "utun77")
|
||||||
|
defer os.Unsetenv("NB_INTERFACE_NAME")
|
||||||
|
args := []string{"interface-name", "utun99"}
|
||||||
|
err = setFunc(nil, args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err := internal.ReadConfig(configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "utun77", config.WgIface)
|
||||||
|
|
||||||
|
os.Unsetenv("NB_INTERFACE_NAME")
|
||||||
|
os.Setenv("WT_INTERFACE_NAME", "utun88")
|
||||||
|
defer os.Unsetenv("WT_INTERFACE_NAME")
|
||||||
|
err = setFunc(nil, args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err = internal.ReadConfig(configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "utun88", config.WgIface)
|
||||||
|
|
||||||
|
os.Unsetenv("WT_INTERFACE_NAME")
|
||||||
|
// No env var, should use CLI value
|
||||||
|
err = setFunc(nil, args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
config, err = internal.ReadConfig(configPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "utun99", config.WgIface)
|
||||||
|
}
|
||||||
@@ -120,7 +120,7 @@ func getStatus(ctx context.Context) (*proto.StatusResponse, error) {
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
resp, err := proto.NewDaemonServiceClient(conn).Status(ctx, &proto.StatusRequest{GetFullPeerStatus: true, ShouldRunProbes: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
return nil, fmt.Errorf("status failed: %v", status.Convert(err).Message())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,10 +319,6 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
|
|||||||
*input.WireguardPort, config.WgPort)
|
*input.WireguardPort, config.WgPort)
|
||||||
config.WgPort = *input.WireguardPort
|
config.WgPort = *input.WireguardPort
|
||||||
updated = true
|
updated = true
|
||||||
} else if config.WgPort == 0 {
|
|
||||||
config.WgPort = iface.DefaultWgPort
|
|
||||||
log.Infof("using default Wireguard port %d", config.WgPort)
|
|
||||||
updated = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.InterfaceName != nil && *input.InterfaceName != config.WgIface {
|
if input.InterfaceName != nil && *input.InterfaceName != config.WgIface {
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
gstatus "google.golang.org/grpc/status"
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
|
||||||
"github.com/netbirdio/netbird/client/iface/device"
|
"github.com/netbirdio/netbird/client/iface/device"
|
||||||
"github.com/netbirdio/netbird/client/internal/dns"
|
"github.com/netbirdio/netbird/client/internal/dns"
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
@@ -526,17 +525,13 @@ func statusRecorderToSignalConnStateNotifier(statusRecorder *peer.Status) signal
|
|||||||
|
|
||||||
// freePort attempts to determine if the provided port is available, if not it will ask the system for a free port.
|
// freePort attempts to determine if the provided port is available, if not it will ask the system for a free port.
|
||||||
func freePort(initPort int) (int, error) {
|
func freePort(initPort int) (int, error) {
|
||||||
addr := net.UDPAddr{}
|
addr := net.UDPAddr{Port: initPort}
|
||||||
if initPort == 0 {
|
|
||||||
initPort = iface.DefaultWgPort
|
|
||||||
}
|
|
||||||
|
|
||||||
addr.Port = initPort
|
|
||||||
|
|
||||||
conn, err := net.ListenUDP("udp", &addr)
|
conn, err := net.ListenUDP("udp", &addr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
returnPort := conn.LocalAddr().(*net.UDPAddr).Port
|
||||||
closeConnWithLog(conn)
|
closeConnWithLog(conn)
|
||||||
return initPort, nil
|
return returnPort, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the port is already in use, ask the system for a free port
|
// if the port is already in use, ask the system for a free port
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ func Test_freePort(t *testing.T) {
|
|||||||
shouldMatch bool
|
shouldMatch bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "not provided, fallback to default",
|
name: "when port is 0 use random port",
|
||||||
port: 0,
|
port: 0,
|
||||||
want: 51820,
|
want: 0,
|
||||||
shouldMatch: true,
|
shouldMatch: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "provided and available",
|
name: "provided and available",
|
||||||
@@ -31,7 +31,7 @@ func Test_freePort(t *testing.T) {
|
|||||||
shouldMatch: false,
|
shouldMatch: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 51830})
|
c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("freePort error = %v", err)
|
t.Errorf("freePort error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,14 @@ func Test_freePort(t *testing.T) {
|
|||||||
_ = c1.Close()
|
_ = c1.Close()
|
||||||
}(c1)
|
}(c1)
|
||||||
|
|
||||||
|
if tests[1].port == c1.LocalAddr().(*net.UDPAddr).Port {
|
||||||
|
tests[1].port++
|
||||||
|
tests[1].want++
|
||||||
|
}
|
||||||
|
|
||||||
|
tests[2].port = c1.LocalAddr().(*net.UDPAddr).Port
|
||||||
|
tests[2].want = c1.LocalAddr().(*net.UDPAddr).Port
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,12 @@ service DaemonService {
|
|||||||
rpc SubscribeEvents(SubscribeRequest) returns (stream SystemEvent) {}
|
rpc SubscribeEvents(SubscribeRequest) returns (stream SystemEvent) {}
|
||||||
|
|
||||||
rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) {}
|
rpc GetEvents(GetEventsRequest) returns (GetEventsResponse) {}
|
||||||
|
|
||||||
|
// Reloads the configuration from disk
|
||||||
|
rpc ReloadConfig(ReloadConfigRequest) returns (ReloadConfigResponse) {}
|
||||||
|
|
||||||
|
// Sets a configuration value (for use by regular users via the daemon)
|
||||||
|
rpc SetConfigValue(SetConfigValueRequest) returns (SetConfigValueResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -158,6 +164,7 @@ message UpResponse {}
|
|||||||
|
|
||||||
message StatusRequest{
|
message StatusRequest{
|
||||||
bool getFullPeerStatus = 1;
|
bool getFullPeerStatus = 1;
|
||||||
|
bool shouldRunProbes = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StatusResponse{
|
message StatusResponse{
|
||||||
@@ -495,3 +502,12 @@ message GetEventsRequest {}
|
|||||||
message GetEventsResponse {
|
message GetEventsResponse {
|
||||||
repeated SystemEvent events = 1;
|
repeated SystemEvent events = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ReloadConfigRequest {}
|
||||||
|
message ReloadConfigResponse {}
|
||||||
|
|
||||||
|
message SetConfigValueRequest {
|
||||||
|
string setting = 1;
|
||||||
|
string value = 2;
|
||||||
|
}
|
||||||
|
message SetConfigValueResponse {}
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ type DaemonServiceClient interface {
|
|||||||
TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error)
|
TracePacket(ctx context.Context, in *TracePacketRequest, opts ...grpc.CallOption) (*TracePacketResponse, error)
|
||||||
SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error)
|
SubscribeEvents(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (DaemonService_SubscribeEventsClient, error)
|
||||||
GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error)
|
GetEvents(ctx context.Context, in *GetEventsRequest, opts ...grpc.CallOption) (*GetEventsResponse, error)
|
||||||
|
// Reloads the configuration from disk
|
||||||
|
ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error)
|
||||||
|
// Sets a configuration value (for use by regular users via the daemon)
|
||||||
|
SetConfigValue(ctx context.Context, in *SetConfigValueRequest, opts ...grpc.CallOption) (*SetConfigValueResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type daemonServiceClient struct {
|
type daemonServiceClient struct {
|
||||||
@@ -268,6 +272,24 @@ func (c *daemonServiceClient) GetEvents(ctx context.Context, in *GetEventsReques
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) ReloadConfig(ctx context.Context, in *ReloadConfigRequest, opts ...grpc.CallOption) (*ReloadConfigResponse, error) {
|
||||||
|
out := new(ReloadConfigResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/ReloadConfig", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *daemonServiceClient) SetConfigValue(ctx context.Context, in *SetConfigValueRequest, opts ...grpc.CallOption) (*SetConfigValueResponse, error) {
|
||||||
|
out := new(SetConfigValueResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetConfigValue", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonServiceServer is the server API for DaemonService service.
|
// DaemonServiceServer is the server API for DaemonService service.
|
||||||
// All implementations must embed UnimplementedDaemonServiceServer
|
// All implementations must embed UnimplementedDaemonServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -309,6 +331,10 @@ type DaemonServiceServer interface {
|
|||||||
TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error)
|
TracePacket(context.Context, *TracePacketRequest) (*TracePacketResponse, error)
|
||||||
SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error
|
SubscribeEvents(*SubscribeRequest, DaemonService_SubscribeEventsServer) error
|
||||||
GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error)
|
GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error)
|
||||||
|
// Reloads the configuration from disk
|
||||||
|
ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error)
|
||||||
|
// Sets a configuration value (for use by regular users via the daemon)
|
||||||
|
SetConfigValue(context.Context, *SetConfigValueRequest) (*SetConfigValueResponse, error)
|
||||||
mustEmbedUnimplementedDaemonServiceServer()
|
mustEmbedUnimplementedDaemonServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,6 +402,12 @@ func (UnimplementedDaemonServiceServer) SubscribeEvents(*SubscribeRequest, Daemo
|
|||||||
func (UnimplementedDaemonServiceServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) {
|
func (UnimplementedDaemonServiceServer) GetEvents(context.Context, *GetEventsRequest) (*GetEventsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetEvents not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) ReloadConfig(context.Context, *ReloadConfigRequest) (*ReloadConfigResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ReloadConfig not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedDaemonServiceServer) SetConfigValue(context.Context, *SetConfigValueRequest) (*SetConfigValueResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method SetConfigValue not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
func (UnimplementedDaemonServiceServer) mustEmbedUnimplementedDaemonServiceServer() {}
|
||||||
|
|
||||||
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeDaemonServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@@ -752,6 +784,42 @@ func _DaemonService_GetEvents_Handler(srv interface{}, ctx context.Context, dec
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _DaemonService_ReloadConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ReloadConfigRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).ReloadConfig(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/ReloadConfig",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).ReloadConfig(ctx, req.(*ReloadConfigRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _DaemonService_SetConfigValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetConfigValueRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(DaemonServiceServer).SetConfigValue(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/daemon.DaemonService/SetConfigValue",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(DaemonServiceServer).SetConfigValue(ctx, req.(*SetConfigValueRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
// DaemonService_ServiceDesc is the grpc.ServiceDesc for DaemonService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -835,6 +903,14 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetEvents",
|
MethodName: "GetEvents",
|
||||||
Handler: _DaemonService_GetEvents_Handler,
|
Handler: _DaemonService_GetEvents_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ReloadConfig",
|
||||||
|
Handler: _DaemonService_ReloadConfig_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetConfigValue",
|
||||||
|
Handler: _DaemonService_SetConfigValue_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
@@ -95,6 +98,8 @@ func (s *Server) Start() error {
|
|||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
state := internal.CtxGetState(s.rootCtx)
|
state := internal.CtxGetState(s.rootCtx)
|
||||||
|
|
||||||
|
s.setupReloadSignal()
|
||||||
|
|
||||||
if err := handlePanicLog(); err != nil {
|
if err := handlePanicLog(); err != nil {
|
||||||
log.Warnf("failed to redirect stderr: %v", err)
|
log.Warnf("failed to redirect stderr: %v", err)
|
||||||
}
|
}
|
||||||
@@ -707,7 +712,9 @@ func (s *Server) Status(
|
|||||||
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
||||||
|
|
||||||
if msg.GetFullPeerStatus {
|
if msg.GetFullPeerStatus {
|
||||||
s.runProbes()
|
if msg.ShouldRunProbes {
|
||||||
|
s.runProbes()
|
||||||
|
}
|
||||||
|
|
||||||
fullStatus := s.statusRecorder.GetFullStatus()
|
fullStatus := s.statusRecorder.GetFullStatus()
|
||||||
pbFullStatus := toProtoFullStatus(fullStatus)
|
pbFullStatus := toProtoFullStatus(fullStatus)
|
||||||
@@ -916,3 +923,186 @@ func sendTerminalNotification() error {
|
|||||||
|
|
||||||
return wallCmd.Wait()
|
return wallCmd.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a gRPC method to reload config from disk
|
||||||
|
func (s *Server) ReloadConfig(_ context.Context, _ *proto.ReloadConfigRequest) (*proto.ReloadConfigResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
config, err := internal.ReadConfig(s.latestConfigInput.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to reload config: %v", err)
|
||||||
|
}
|
||||||
|
s.config = config
|
||||||
|
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
||||||
|
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
|
||||||
|
s.statusRecorder.UpdateLazyConnection(config.LazyConnectionEnabled)
|
||||||
|
log.Infof("Reloaded config from disk")
|
||||||
|
return &proto.ReloadConfigResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, handle SIGHUP to reload config
|
||||||
|
func (s *Server) setupReloadSignal() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGHUP)
|
||||||
|
go func() {
|
||||||
|
for range c {
|
||||||
|
_, err := s.ReloadConfig(context.Background(), &proto.ReloadConfigRequest{})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to reload config on SIGHUP: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SetConfigValue(_ context.Context, req *proto.SetConfigValueRequest) (*proto.SetConfigValueResponse, error) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
setting := req.Setting
|
||||||
|
value := req.Value
|
||||||
|
input := internal.ConfigInput{ConfigPath: s.latestConfigInput.ConfigPath}
|
||||||
|
switch setting {
|
||||||
|
case "management-url":
|
||||||
|
input.ManagementURL = value
|
||||||
|
case "admin-url":
|
||||||
|
input.AdminURL = value
|
||||||
|
case "interface-name":
|
||||||
|
input.InterfaceName = &value
|
||||||
|
case "external-ip-map":
|
||||||
|
if value == "" {
|
||||||
|
input.NATExternalIPs = []string{}
|
||||||
|
} else {
|
||||||
|
input.NATExternalIPs = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
case "extra-iface-blacklist":
|
||||||
|
if value == "" {
|
||||||
|
input.ExtraIFaceBlackList = []string{}
|
||||||
|
} else {
|
||||||
|
input.ExtraIFaceBlackList = strings.Split(value, ",")
|
||||||
|
}
|
||||||
|
case "dns-resolver-address":
|
||||||
|
input.CustomDNSAddress = []byte(value)
|
||||||
|
case "extra-dns-labels":
|
||||||
|
if value == "" {
|
||||||
|
input.DNSLabels = nil
|
||||||
|
} else {
|
||||||
|
labels := strings.Split(value, ",")
|
||||||
|
domains, err := domain.ValidateDomains(labels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid DNS labels: %v", err)
|
||||||
|
}
|
||||||
|
input.DNSLabels = domains
|
||||||
|
}
|
||||||
|
case "preshared-key":
|
||||||
|
input.PreSharedKey = &value
|
||||||
|
case "enable-rosenpass":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.RosenpassEnabled = &b
|
||||||
|
case "rosenpass-permissive":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.RosenpassPermissive = &b
|
||||||
|
case "allow-server-ssh":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.ServerSSHAllowed = &b
|
||||||
|
case "network-monitor":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.NetworkMonitor = &b
|
||||||
|
case "disable-auto-connect":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.DisableAutoConnect = &b
|
||||||
|
case "disable-client-routes":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.DisableClientRoutes = &b
|
||||||
|
case "disable-server-routes":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.DisableServerRoutes = &b
|
||||||
|
case "disable-dns":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.DisableDNS = &b
|
||||||
|
case "disable-firewall":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.DisableFirewall = &b
|
||||||
|
case "block-lan-access":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.BlockLANAccess = &b
|
||||||
|
case "block-inbound":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.BlockInbound = &b
|
||||||
|
case "enable-lazy-connection":
|
||||||
|
b, err := parseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
input.LazyConnectionEnabled = &b
|
||||||
|
case "wireguard-port":
|
||||||
|
var p int
|
||||||
|
_, err := fmt.Sscanf(value, "%d", &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid wireguard-port: %s", value)
|
||||||
|
}
|
||||||
|
input.WireguardPort = &p
|
||||||
|
case "dns-router-interval":
|
||||||
|
d, err := time.ParseDuration(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid duration: %v", err)
|
||||||
|
}
|
||||||
|
input.DNSRouteInterval = &d
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown setting: %s", setting)
|
||||||
|
}
|
||||||
|
_, err := internal.UpdateOrCreateConfig(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update config: %v", err)
|
||||||
|
}
|
||||||
|
// Reload config in memory
|
||||||
|
config, err := internal.ReadConfig(s.latestConfigInput.ConfigPath)
|
||||||
|
if err == nil {
|
||||||
|
s.config = config
|
||||||
|
}
|
||||||
|
return &proto.SetConfigValueResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBool(val string) (bool, error) {
|
||||||
|
v := strings.ToLower(val)
|
||||||
|
if v == "true" || v == "1" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if v == "false" || v == "0" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("invalid boolean value: %s", val)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user