set command with cursor

This commit is contained in:
Maycon Santos
2025-07-02 12:20:43 +02:00
parent 6922826919
commit 355bab9bb4
8 changed files with 3369 additions and 939 deletions

View File

@@ -22,6 +22,7 @@ import (
"google.golang.org/grpc/credentials/insecure"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/upload-server/types"
)
@@ -87,6 +88,30 @@ var (
Long: "",
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.
@@ -152,6 +177,9 @@ func init() {
rootCmd.AddCommand(networksCMD)
rootCmd.AddCommand(forwardingRulesCmd)
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(installCmd, uninstallCmd) // service installer commands are subcommands of service
@@ -408,3 +436,167 @@ func getClient(cmd *cobra.Command) (*grpc.ClientConn, error) {
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
View 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
View 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)
}