Skip to content

Commit 08cca8d

Browse files
authored
Merge pull request #28 from fosrl/dev
0.7.1
2 parents 602b939 + c8deeb9 commit 08cca8d

File tree

19 files changed

+509
-234
lines changed

19 files changed

+509
-234
lines changed

api/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func (c *APIClient) makeRequest(method, path string, body []byte) ([]byte, *http
188188
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", c.sessionCookieName, c.sessionToken))
189189
}
190190

191-
logger.Info("Making request to: %s", fullURL)
191+
logger.Debug("Making request to: %s", fullURL)
192192

193193
resp, err := c.client.Do(req)
194194
if err != nil {

auth/manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ func (am *AuthManager) RefreshOrganizations() error {
475475
am.organizations = newOrgs
476476
am.mu.Unlock()
477477

478-
logger.Info("Organizations refreshed successfully: %d orgs", len(newOrgs))
478+
logger.Debug("Organizations refreshed successfully: %d orgs", len(newOrgs))
479479
return nil
480480
}
481481

config/config.go

Lines changed: 157 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"os"
88
"path/filepath"
9+
"strings"
910
"sync"
1011
"unsafe"
1112

@@ -17,18 +18,29 @@ const (
1718
AppName = "Pangolin"
1819
DefaultHostname = "https://app.pangolin.net"
1920
ConfigFileName = "pangolin.json"
20-
LogLevel = "debug" // Centralized log level for the application
21-
DefaultPrimaryDNS = "9.9.9.9"
21+
LogLevel = "info"
22+
DefaultPrimaryDNS = "1.1.1.1"
2223
DefaultDNSOverride = true
2324
DefaultDNSTunnel = false
2425
)
2526

26-
// Config represents the application configuration
27+
// Config represents the per-user application configuration stored under
28+
// %LOCALAPPDATA%\Pangolin\pangolin.json (or %APPDATA% as a fallback).
2729
type Config struct {
28-
DNSOverride *bool `json:"dnsOverride,omitempty"`
29-
DNSTunnel *bool `json:"dnsTunnel,omitempty"`
30-
PrimaryDNS *string `json:"primaryDNS,omitempty"`
31-
SecondaryDNS *string `json:"secondaryDNS,omitempty"`
30+
DNSOverride *bool `json:"dnsOverride,omitempty"`
31+
DNSTunnel *bool `json:"dnsTunnel,omitempty"`
32+
PrimaryDNS *string `json:"primaryDNS,omitempty"`
33+
SecondaryDNS *string `json:"secondaryDNS,omitempty"`
34+
DefaultServerURL *string `json:"defaultServerURL,omitempty"`
35+
UserSettingsDisabled *bool `json:"userSettingsDisabled,omitempty"`
36+
AuthPath *string `json:"authPath,omitempty"`
37+
}
38+
39+
// SystemConfig represents machine-wide configuration stored under
40+
// %ProgramData%\Pangolin\pangolin.json. This is used by background
41+
// services (manager, tunnel, etc.) and for global settings like log level.
42+
type SystemConfig struct {
43+
LogLevel *string `json:"logLevel,omitempty"`
3244
}
3345

3446
// ConfigManager manages loading and saving of application configuration
@@ -62,13 +74,21 @@ func NewConfigManager() *ConfigManager {
6274
return cm
6375
}
6476

65-
// GetConfig returns the current configuration
77+
// GetConfig returns the current configuration (do not modify; use GetConfigCopy for that)
6678
func (cm *ConfigManager) GetConfig() *Config {
6779
cm.mu.RLock()
6880
defer cm.mu.RUnlock()
6981
return cm.config
7082
}
7183

84+
// GetConfigCopy returns a deep copy of the current configuration.
85+
// Callers can modify the copy and pass it to Save to update only specific fields while preserving others.
86+
func (cm *ConfigManager) GetConfigCopy() *Config {
87+
cm.mu.Lock()
88+
defer cm.mu.Unlock()
89+
return cm.getConfigCopy()
90+
}
91+
7292
// load loads the configuration from the file
7393
// Returns a default config if the file doesn't exist or can't be read
7494
func (cm *ConfigManager) load() *Config {
@@ -156,13 +176,13 @@ func (cm *ConfigManager) GetDNSOverride() bool {
156176

157177
// GetDNSTunnel returns the DNS tunnel setting from config or false if not set
158178
func (cm *ConfigManager) GetDNSTunnel() bool {
159-
cm.mu.RLock()
160-
defer cm.mu.RUnlock()
179+
cm.mu.RLock()
180+
defer cm.mu.RUnlock()
161181

162-
if cm.config != nil && cm.config.DNSTunnel != nil {
163-
return *cm.config.DNSTunnel
164-
}
165-
return DefaultDNSTunnel
182+
if cm.config != nil && cm.config.DNSTunnel != nil {
183+
return *cm.config.DNSTunnel
184+
}
185+
return DefaultDNSTunnel
166186
}
167187

168188
// GetPrimaryDNS returns the primary DNS server from config or the default value
@@ -187,6 +207,80 @@ func (cm *ConfigManager) GetSecondaryDNS() string {
187207
return ""
188208
}
189209

210+
// GetDefaultServerURL returns the default server URL from config or empty string if not set
211+
func (cm *ConfigManager) GetDefaultServerURL() string {
212+
cm.mu.RLock()
213+
defer cm.mu.RUnlock()
214+
215+
if cm.config != nil && cm.config.DefaultServerURL != nil {
216+
return strings.TrimSpace(*cm.config.DefaultServerURL)
217+
}
218+
return ""
219+
}
220+
221+
// SetDefaultServerURL sets the default server URL and saves to config
222+
func (cm *ConfigManager) SetDefaultServerURL(value string) bool {
223+
cm.mu.Lock()
224+
defer cm.mu.Unlock()
225+
226+
// Get current config and copy it to preserve all fields
227+
cfg := cm.getConfigCopy()
228+
value = strings.TrimSpace(value)
229+
if value == "" {
230+
cfg.DefaultServerURL = nil
231+
} else {
232+
cfg.DefaultServerURL = &value
233+
}
234+
return cm.save(cfg)
235+
}
236+
237+
// GetUserSettingsDisabled returns whether user settings are disabled (e.g. by admin policy)
238+
func (cm *ConfigManager) GetUserSettingsDisabled() bool {
239+
cm.mu.RLock()
240+
defer cm.mu.RUnlock()
241+
242+
if cm.config != nil && cm.config.UserSettingsDisabled != nil {
243+
return *cm.config.UserSettingsDisabled
244+
}
245+
return false
246+
}
247+
248+
// SetUserSettingsDisabled sets whether user settings are disabled and saves to config
249+
func (cm *ConfigManager) SetUserSettingsDisabled(disabled bool) bool {
250+
cm.mu.Lock()
251+
defer cm.mu.Unlock()
252+
253+
cfg := cm.getConfigCopy()
254+
cfg.UserSettingsDisabled = &disabled
255+
return cm.save(cfg)
256+
}
257+
258+
// GetAuthPath returns the auth path query value for login URLs, or empty string if not set
259+
func (cm *ConfigManager) GetAuthPath() string {
260+
cm.mu.RLock()
261+
defer cm.mu.RUnlock()
262+
263+
if cm.config != nil && cm.config.AuthPath != nil {
264+
return strings.TrimSpace(*cm.config.AuthPath)
265+
}
266+
return ""
267+
}
268+
269+
// SetAuthPath sets the auth path and saves to config
270+
func (cm *ConfigManager) SetAuthPath(value string) bool {
271+
cm.mu.Lock()
272+
defer cm.mu.Unlock()
273+
274+
cfg := cm.getConfigCopy()
275+
value = strings.TrimSpace(value)
276+
if value == "" {
277+
cfg.AuthPath = nil
278+
} else {
279+
cfg.AuthPath = &value
280+
}
281+
return cm.save(cfg)
282+
}
283+
190284
// SetDNSOverride sets the DNS override setting and saves to config
191285
func (cm *ConfigManager) SetDNSOverride(value bool) bool {
192286
cm.mu.Lock()
@@ -200,13 +294,13 @@ func (cm *ConfigManager) SetDNSOverride(value bool) bool {
200294

201295
// SetDNSTunnel sets the DNS tunnel setting and saves to config
202296
func (cm *ConfigManager) SetDNSTunnel(value bool) bool {
203-
cm.mu.Lock()
204-
defer cm.mu.Unlock()
297+
cm.mu.Lock()
298+
defer cm.mu.Unlock()
205299

206-
// Get current config and copy it to preserve all fields
207-
cfg := cm.getConfigCopy()
208-
cfg.DNSTunnel = &value
209-
return cm.save(cfg)
300+
// Get current config and copy it to preserve all fields
301+
cfg := cm.getConfigCopy()
302+
cfg.DNSTunnel = &value
303+
return cm.save(cfg)
210304
}
211305

212306
// SetPrimaryDNS sets the primary DNS server and saves to config
@@ -235,6 +329,34 @@ func (cm *ConfigManager) SetSecondaryDNS(value string) bool {
235329
return cm.save(cfg)
236330
}
237331

332+
func LoadSystemConfig() *SystemConfig {
333+
configPath := filepath.Join(GetProgramDataDir(), ConfigFileName)
334+
335+
data, err := os.ReadFile(configPath)
336+
if err != nil {
337+
return &SystemConfig{}
338+
}
339+
340+
var cfg SystemConfig
341+
if err := json.Unmarshal(data, &cfg); err != nil {
342+
return &SystemConfig{}
343+
}
344+
345+
return &cfg
346+
}
347+
348+
// GetSystemLogLevel returns the log level from the system config file
349+
func GetSystemLogLevel() string {
350+
cfg := LoadSystemConfig()
351+
if cfg.LogLevel != nil {
352+
level := strings.TrimSpace(*cfg.LogLevel)
353+
if level != "" {
354+
return level
355+
}
356+
}
357+
return LogLevel
358+
}
359+
238360
// getConfigCopy creates a deep copy of the current config
239361
// Caller must hold the lock
240362
func (cm *ConfigManager) getConfigCopy() *Config {
@@ -249,9 +371,9 @@ func (cm *ConfigManager) getConfigCopy() *Config {
249371
cfg.DNSOverride = &dnsOverride
250372
}
251373
if cm.config.DNSTunnel != nil {
252-
dnsTunnel := *cm.config.DNSTunnel
253-
cfg.DNSTunnel = &dnsTunnel
254-
}
374+
dnsTunnel := *cm.config.DNSTunnel
375+
cfg.DNSTunnel = &dnsTunnel
376+
}
255377
if cm.config.PrimaryDNS != nil {
256378
primaryDNS := *cm.config.PrimaryDNS
257379
cfg.PrimaryDNS = &primaryDNS
@@ -260,6 +382,18 @@ func (cm *ConfigManager) getConfigCopy() *Config {
260382
secondaryDNS := *cm.config.SecondaryDNS
261383
cfg.SecondaryDNS = &secondaryDNS
262384
}
385+
if cm.config.DefaultServerURL != nil {
386+
defaultServerURL := *cm.config.DefaultServerURL
387+
cfg.DefaultServerURL = &defaultServerURL
388+
}
389+
if cm.config.UserSettingsDisabled != nil {
390+
userSettingsDisabled := *cm.config.UserSettingsDisabled
391+
cfg.UserSettingsDisabled = &userSettingsDisabled
392+
}
393+
if cm.config.AuthPath != nil {
394+
authPath := *cm.config.AuthPath
395+
cfg.AuthPath = &authPath
396+
}
263397
return cfg
264398
}
265399

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ go 1.25
44

55
require (
66
github.com/Microsoft/go-winio v0.6.2
7-
github.com/fosrl/newt v1.9.0
8-
github.com/fosrl/olm v1.4.2
7+
github.com/fosrl/newt v1.10.1
8+
github.com/fosrl/olm v1.4.3
99
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1010
github.com/tailscale/walk v0.0.0-20251016200523-963e260a8227
1111
github.com/tailscale/win v0.0.0-20250213223159-5992cb43ca35

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
88
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
99
github.com/dblohm7/wingoes v0.0.0-20231019175336-f6e33aa7cc34 h1:FBMro26TLQwBk+n4fbTSmSf3QUKb09pvW4fz49lxpl0=
1010
github.com/dblohm7/wingoes v0.0.0-20231019175336-f6e33aa7cc34/go.mod h1:6NCrWM5jRefaG7iN0iMShPalLsljHWBh9v1zxM2f8Xs=
11-
github.com/fosrl/newt v1.9.0 h1:66eJMo6fA+YcBTbddxTfNJXNQo1WWKzmn6zPRP5kSDE=
12-
github.com/fosrl/newt v1.9.0/go.mod h1:d1+yYMnKqg4oLqAM9zdbjthjj2FQEVouiACjqU468ck=
13-
github.com/fosrl/olm v1.4.2 h1:IyMbQvWyswaSaCuRDU1WnafImcOSxtRrYaofVeFgw3s=
14-
github.com/fosrl/olm v1.4.2/go.mod h1:aC1oieI0tadd66zY7RDjXT3PPsR54mYS0FYMsHqFqs8=
11+
github.com/fosrl/newt v1.10.1 h1:3RZqYhJDDBWZHViVeDl4hWwWVJFtvFJB+wL+AF99neA=
12+
github.com/fosrl/newt v1.10.1/go.mod h1:d1+yYMnKqg4oLqAM9zdbjthjj2FQEVouiACjqU468ck=
13+
github.com/fosrl/olm v1.4.3 h1:hmAWfrJzpiwtvzw/B6xmCmeqK1OW0BJ2FUbEa7ztlmU=
14+
github.com/fosrl/olm v1.4.3/go.mod h1:aC1oieI0tadd66zY7RDjXT3PPsR54mYS0FYMsHqFqs8=
1515
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
1616
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
1717
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=

logger.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import (
1414
"github.com/fosrl/newt/logger"
1515
)
1616

17-
// stringToLogLevel converts a string log level to logger.LogLevel
18-
// Returns INFO as default if the string doesn't match any known level
17+
// stringToLogLevel converts a string log level to logger.LogLevel.
18+
// Returns INFO as default if the string doesn't match any known level.
1919
func stringToLogLevel(levelStr string) logger.LogLevel {
20-
switch strings.ToUpper(levelStr) {
20+
switch strings.ToLower(levelStr) {
2121
case "debug":
2222
return logger.DEBUG
2323
case "info":
@@ -29,7 +29,7 @@ func stringToLogLevel(levelStr string) logger.LogLevel {
2929
case "fatal":
3030
return logger.FATAL
3131
default:
32-
return logger.DEBUG // Default to INFO if unknown
32+
return logger.INFO
3333
}
3434
}
3535

@@ -38,8 +38,9 @@ func setupLogging() {
3838
// Initialize the logger and set log level FIRST, before any logging calls
3939
logInstance := logger.GetLogger()
4040

41-
// Set the log level from centralized config immediately
42-
logLevel := stringToLogLevel(config.LogLevel)
41+
// Resolve log level from system config file (with built-in default fallback)
42+
logLevelStr := config.GetSystemLogLevel()
43+
logLevel := stringToLogLevel(logLevelStr)
4344
logInstance.SetLevel(logLevel)
4445

4546
// Create log directory if it doesn't exist
@@ -68,7 +69,7 @@ func setupLogging() {
6869
// Set the custom logger output
6970
logInstance.SetOutput(file)
7071

71-
logger.Info("Pangolin logging initialized - log file: %s, log level: %s", logFile, config.LogLevel)
72+
logger.Info("Pangolin logging initialized - log file: %s, log level: %s", logFile, logLevelStr)
7273
}
7374

7475
// rotateLogFile handles daily log rotation
@@ -101,8 +102,7 @@ func rotateLogFile(logDir string, logFile string) error {
101102
return fmt.Errorf("failed to rotate log file: %v", err)
102103
}
103104

104-
// Clean up old log files (keep last 30 days)
105-
cleanupOldLogFiles(logDir, 30)
105+
cleanupOldLogFiles(logDir, 3)
106106
return nil
107107
}
108108

0 commit comments

Comments
 (0)