//go:build !ios && !android package cmd import ( "context" "fmt" "net/http" "runtime" "strings" "sync" "github.com/kardianos/service" "github.com/spf13/cobra" "google.golang.org/grpc" "github.com/netbirdio/netbird/client/internal" "github.com/netbirdio/netbird/client/internal/ipcauth" "github.com/netbirdio/netbird/client/server" ) var serviceCmd = &cobra.Command{ Use: "service", Short: "Manage the NetBird daemon service", } const defaultJSONSocket = "unix:///var/run/netbird-http.sock" var ( serviceName string serviceEnvVars []string jsonSocket string enableJSONSocket bool // allowGroups holds the --allow-group values as given: group or account // names, or principals already in kind:value form. resolveAllowGroups turns // them into the principals the daemon enforces. allowGroups []string // resolvedAllowGroups holds those principals after the install-time // resolution, for persisting and for the arguments the installed service // runs with. resolvedAllowGroups []string ) type program struct { ctx context.Context cancel context.CancelFunc serv *grpc.Server jsonServ *http.Server // jsonClient is the gateway's own connection to the daemon. It is held so // shutting the gateway down also closes it: nothing else references it once // the handlers are registered, so its transport goroutines would otherwise // outlive the server. jsonClient *grpc.ClientConn jsonServMu sync.Mutex serverInstance *server.Server serverInstanceMu sync.Mutex authzGate *ipcauth.AuthzGate } func init() { defaultServiceName := "netbird" if runtime.GOOS == "windows" { defaultServiceName = "Netbird" } serviceCmd.AddCommand(runCmd, startCmd, stopCmd, restartCmd, svcStatusCmd, installCmd, uninstallCmd, reconfigureCmd, resetParamsCmd) serviceCmd.PersistentFlags().BoolVar(&profilesDisabled, "disable-profiles", false, "Disables profiles feature. If enabled, the client will not be able to change or edit any profile. To persist this setting, use: netbird service install --disable-profiles") serviceCmd.PersistentFlags().BoolVar(&updateSettingsDisabled, "disable-update-settings", false, "Disables update settings feature. If enabled, the client will not be able to change or edit any settings. To persist this setting, use: netbird service install --disable-update-settings") serviceCmd.PersistentFlags().BoolVar(&captureEnabled, "enable-capture", false, "Enables packet capture via 'netbird debug capture'. To persist, use: netbird service install --enable-capture") serviceCmd.PersistentFlags().BoolVar(&networksDisabled, "disable-networks", false, "Disables network selection. If enabled, the client will not allow listing, selecting, or deselecting networks. To persist, use: netbird service install --disable-networks") serviceCmd.PersistentFlags().BoolVar(&enableJSONSocket, "enable-json-socket", false, "Enables the HTTP/JSON API socket served by grpc-gateway. To persist, use: netbird service install --enable-json-socket") serviceCmd.PersistentFlags().StringVar(&jsonSocket, "json-socket", defaultJSONSocket, "HTTP/JSON API socket address [unix|tcp]://[path|host:port]. Requires --enable-json-socket to serve. To persist, use: netbird service install --enable-json-socket --json-socket") allowGroupDesc := `Restricts the daemon control socket and the JSON socket to the given group. ` + `Accounts outside it cannot connect at all, so nothing the daemon exposes is reachable from them. ` + `On Windows the daemon's own account, LocalSystem and elevated administrators keep access regardless. ` + `Takes a group name, or a numeric GID on Unix and a SID on Windows; ` + `Unix accepts a single group, Windows a comma-separated list of groups or accounts. ` + `Names are resolved when the service is installed, LDAP, SSSD and Active Directory groups included. ` + `To persist, use: netbird service install --allow-group ` serviceCmd.PersistentFlags().StringSliceVar(&allowGroups, "allow-group", nil, allowGroupDesc) rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name") serviceEnvDesc := `Sets extra environment variables for the service. ` + `You can specify a comma-separated list of KEY=VALUE pairs. ` + `New keys are merged with previously saved env vars; existing keys are overwritten. ` + `Use --service-env "" to clear all saved env vars. ` + `E.g. --service-env NB_LOG_LEVEL=debug,CUSTOM_VAR=value` installCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc) reconfigureCmd.Flags().StringSliceVar(&serviceEnvVars, "service-env", nil, serviceEnvDesc) rootCmd.AddCommand(serviceCmd) } func newProgram(ctx context.Context, cancel context.CancelFunc) *program { ctx = internal.CtxInitState(ctx) return &program{ctx: ctx, cancel: cancel} } func newSVCConfig() (*service.Config, error) { config := &service.Config{ Name: serviceName, DisplayName: "Netbird", Description: "NetBird mesh network client", Option: make(service.KeyValue), EnvVars: make(map[string]string), } if len(serviceEnvVars) > 0 { extraEnvs, err := parseServiceEnvVars(serviceEnvVars) if err != nil { return nil, fmt.Errorf("parse service environment variables: %w", err) } config.EnvVars = extraEnvs } if runtime.GOOS == "linux" { config.EnvVars["SYSTEMD_UNIT"] = serviceName } return config, nil } func newSVC(prg *program, conf *service.Config) (service.Service, error) { return service.New(prg, conf) } func parseServiceEnvVars(envVars []string) (map[string]string, error) { envMap := make(map[string]string) for _, env := range envVars { if env == "" { continue } parts := strings.SplitN(env, "=", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid environment variable format: %s (expected KEY=VALUE)", env) } key := strings.TrimSpace(parts[0]) value := strings.TrimSpace(parts[1]) if key == "" { return nil, fmt.Errorf("empty environment variable key in: %s", env) } envMap[key] = value } return envMap, nil }