Skip to content

Commit 29a1aa2

Browse files
authored
Merge pull request #32 from fosrl/dev
0.8.0
2 parents 2037926 + eec77d7 commit 29a1aa2

10 files changed

Lines changed: 328 additions & 102 deletions

File tree

config/config.go

Lines changed: 125 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ const (
2727
// Config represents the per-user application configuration stored under
2828
// %LOCALAPPDATA%\Pangolin\pangolin.json (or %APPDATA% as a fallback).
2929
type Config struct {
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"`
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+
OpenStatusTabOnConnect *bool `json:"openStatusTabOnConnect,omitempty"`
3738
}
3839

3940
// 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.
41+
// %ProgramData%\Pangolin\pangolin.json. It supports the same settings as
42+
// per-user config plus system-only fields like log level.
4243
type SystemConfig struct {
44+
Config
4345
LogLevel *string `json:"logLevel,omitempty"`
4446
}
4547

@@ -92,26 +94,15 @@ func (cm *ConfigManager) GetConfigCopy() *Config {
9294
// load loads the configuration from the file
9395
// Returns a default config if the file doesn't exist or can't be read
9496
func (cm *ConfigManager) load() *Config {
95-
// Check if file exists
96-
if _, err := os.Stat(cm.configPath); os.IsNotExist(err) {
97-
return &Config{}
98-
}
97+
// Load machine-wide defaults first, then overlay user-specific values.
98+
merged := configFromSystemConfig(LoadSystemConfig())
9999

100-
// Read file
101-
data, err := os.ReadFile(cm.configPath)
102-
if err != nil {
103-
logger.Error("Error loading config: %v", err)
104-
return &Config{}
100+
userCfg, ok := cm.loadUserConfig()
101+
if !ok {
102+
return merged
105103
}
106104

107-
// Parse JSON
108-
var config Config
109-
if err := json.Unmarshal(data, &config); err != nil {
110-
logger.Error("Error parsing config: %v", err)
111-
return &Config{}
112-
}
113-
114-
return &config
105+
return mergeConfig(merged, userCfg)
115106
}
116107

117108
// Load loads the configuration from the file
@@ -266,6 +257,18 @@ func (cm *ConfigManager) GetAuthPath() string {
266257
return ""
267258
}
268259

260+
// GetOpenStatusTabOnConnect returns whether the tray Connect action should
261+
// open the preferences window directly on the Status tab after a successful connect.
262+
func (cm *ConfigManager) GetOpenStatusTabOnConnect() bool {
263+
cm.mu.RLock()
264+
defer cm.mu.RUnlock()
265+
266+
if cm.config != nil && cm.config.OpenStatusTabOnConnect != nil {
267+
return *cm.config.OpenStatusTabOnConnect
268+
}
269+
return false
270+
}
271+
269272
// SetAuthPath sets the auth path and saves to config
270273
func (cm *ConfigManager) SetAuthPath(value string) bool {
271274
cm.mu.Lock()
@@ -360,40 +363,120 @@ func GetSystemLogLevel() string {
360363
// getConfigCopy creates a deep copy of the current config
361364
// Caller must hold the lock
362365
func (cm *ConfigManager) getConfigCopy() *Config {
363-
if cm.config == nil {
366+
return copyConfig(cm.config)
367+
}
368+
369+
// loadUserConfig loads the per-user config from disk.
370+
func (cm *ConfigManager) loadUserConfig() (*Config, bool) {
371+
if _, err := os.Stat(cm.configPath); os.IsNotExist(err) {
372+
return nil, false
373+
}
374+
375+
data, err := os.ReadFile(cm.configPath)
376+
if err != nil {
377+
logger.Error("Error loading config: %v", err)
378+
return nil, false
379+
}
380+
381+
var cfg Config
382+
if err := json.Unmarshal(data, &cfg); err != nil {
383+
logger.Error("Error parsing config: %v", err)
384+
return nil, false
385+
}
386+
387+
return &cfg, true
388+
}
389+
390+
// configFromSystemConfig extracts shared config fields from system config.
391+
func configFromSystemConfig(sys *SystemConfig) *Config {
392+
if sys == nil {
393+
return &Config{}
394+
}
395+
return copyConfig(&sys.Config)
396+
}
397+
398+
// mergeConfig overlays override values onto base values.
399+
func mergeConfig(base, override *Config) *Config {
400+
merged := copyConfig(base)
401+
if override == nil {
402+
return merged
403+
}
404+
405+
if override.DNSOverride != nil {
406+
v := *override.DNSOverride
407+
merged.DNSOverride = &v
408+
}
409+
if override.DNSTunnel != nil {
410+
v := *override.DNSTunnel
411+
merged.DNSTunnel = &v
412+
}
413+
if override.PrimaryDNS != nil {
414+
v := *override.PrimaryDNS
415+
merged.PrimaryDNS = &v
416+
}
417+
if override.SecondaryDNS != nil {
418+
v := *override.SecondaryDNS
419+
merged.SecondaryDNS = &v
420+
}
421+
if override.DefaultServerURL != nil {
422+
v := *override.DefaultServerURL
423+
merged.DefaultServerURL = &v
424+
}
425+
if override.UserSettingsDisabled != nil {
426+
v := *override.UserSettingsDisabled
427+
merged.UserSettingsDisabled = &v
428+
}
429+
if override.AuthPath != nil {
430+
v := *override.AuthPath
431+
merged.AuthPath = &v
432+
}
433+
if override.OpenStatusTabOnConnect != nil {
434+
v := *override.OpenStatusTabOnConnect
435+
merged.OpenStatusTabOnConnect = &v
436+
}
437+
438+
return merged
439+
}
440+
441+
// copyConfig creates a deep copy of all pointer fields.
442+
func copyConfig(src *Config) *Config {
443+
if src == nil {
364444
return &Config{}
365445
}
366446

367-
// Create a new config and copy all pointer fields
368447
cfg := &Config{}
369-
if cm.config.DNSOverride != nil {
370-
dnsOverride := *cm.config.DNSOverride
448+
if src.DNSOverride != nil {
449+
dnsOverride := *src.DNSOverride
371450
cfg.DNSOverride = &dnsOverride
372451
}
373-
if cm.config.DNSTunnel != nil {
374-
dnsTunnel := *cm.config.DNSTunnel
452+
if src.DNSTunnel != nil {
453+
dnsTunnel := *src.DNSTunnel
375454
cfg.DNSTunnel = &dnsTunnel
376455
}
377-
if cm.config.PrimaryDNS != nil {
378-
primaryDNS := *cm.config.PrimaryDNS
456+
if src.PrimaryDNS != nil {
457+
primaryDNS := *src.PrimaryDNS
379458
cfg.PrimaryDNS = &primaryDNS
380459
}
381-
if cm.config.SecondaryDNS != nil {
382-
secondaryDNS := *cm.config.SecondaryDNS
460+
if src.SecondaryDNS != nil {
461+
secondaryDNS := *src.SecondaryDNS
383462
cfg.SecondaryDNS = &secondaryDNS
384463
}
385-
if cm.config.DefaultServerURL != nil {
386-
defaultServerURL := *cm.config.DefaultServerURL
464+
if src.DefaultServerURL != nil {
465+
defaultServerURL := *src.DefaultServerURL
387466
cfg.DefaultServerURL = &defaultServerURL
388467
}
389-
if cm.config.UserSettingsDisabled != nil {
390-
userSettingsDisabled := *cm.config.UserSettingsDisabled
468+
if src.UserSettingsDisabled != nil {
469+
userSettingsDisabled := *src.UserSettingsDisabled
391470
cfg.UserSettingsDisabled = &userSettingsDisabled
392471
}
393-
if cm.config.AuthPath != nil {
394-
authPath := *cm.config.AuthPath
472+
if src.AuthPath != nil {
473+
authPath := *src.AuthPath
395474
cfg.AuthPath = &authPath
396475
}
476+
if src.OpenStatusTabOnConnect != nil {
477+
openStatusTabOnConnect := *src.OpenStatusTabOnConnect
478+
cfg.OpenStatusTabOnConnect = &openStatusTabOnConnect
479+
}
397480
return cfg
398481
}
399482

fingerprint/fingerprint.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,26 @@ func GatherFingerprintInfo() *Fingerprint {
5353
username = u.Username
5454
}
5555

56-
hostname, _ := os.Hostname()
56+
var hostname string
57+
var osVersion string
58+
var kernelVersion string
59+
var deviceModel string
60+
var serialNumber string
61+
var wg sync.WaitGroup
62+
63+
wg.Go(func() {
64+
hostname, _ = os.Hostname()
65+
})
66+
67+
wg.Go(func() {
68+
osVersion, kernelVersion = getWindowsVersion()
69+
})
5770

58-
osVersion, kernelVersion := getWindowsVersion()
71+
wg.Go(func() {
72+
deviceModel, serialNumber = getWindowsModelAndSerial()
73+
})
5974

60-
deviceModel, serialNumber := getWindowsModelAndSerial()
75+
wg.Wait()
6176

6277
return &Fingerprint{
6378
Username: username,

go.mod

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
module github.com/fosrl/windows
22

3-
go 1.25
3+
go 1.25.0
44

55
require (
66
github.com/Microsoft/go-winio v0.6.2
7-
github.com/fosrl/newt v1.10.1
8-
github.com/fosrl/olm v1.4.3
7+
github.com/fosrl/newt v1.10.3
8+
github.com/fosrl/olm v1.4.4
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
1212
github.com/zalando/go-keyring v0.2.6
13-
golang.org/x/crypto v0.46.0
14-
golang.org/x/sys v0.40.0
13+
golang.org/x/crypto v0.48.0
14+
golang.org/x/sys v0.41.0
1515
)
1616

1717
require (
@@ -25,11 +25,11 @@ require (
2525
github.com/vishvananda/netlink v1.3.1 // indirect
2626
github.com/vishvananda/netns v0.0.5 // indirect
2727
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
28-
golang.org/x/mod v0.31.0 // indirect
29-
golang.org/x/net v0.48.0 // indirect
28+
golang.org/x/mod v0.32.0 // indirect
29+
golang.org/x/net v0.51.0 // indirect
3030
golang.org/x/sync v0.19.0 // indirect
3131
golang.org/x/time v0.12.0 // indirect
32-
golang.org/x/tools v0.40.0 // indirect
32+
golang.org/x/tools v0.41.0 // indirect
3333
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
3434
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
3535
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 // indirect

go.sum

Lines changed: 14 additions & 14 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.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=
11+
github.com/fosrl/newt v1.10.3 h1:JO9gFK9LP/w2EeDIn4wU+jKggAFPo06hX5hxFSETqcw=
12+
github.com/fosrl/newt v1.10.3/go.mod h1:iYuuCAG7iabheiogMOX87r61uQN31S39nKxMKRuLS+s=
13+
github.com/fosrl/olm v1.4.4 h1:MoHQO9okqN7NKNtxgAdKsnV70bRzbm4hec6nBHpNic8=
14+
github.com/fosrl/olm v1.4.4/go.mod h1:SxeQNFngDMgwrXpTWjt1gheyoZo2TltmNrQ2vXX7YT4=
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=
@@ -42,25 +42,25 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
4242
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
4343
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
4444
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
45-
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
46-
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
45+
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
46+
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
4747
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
4848
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
49-
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
50-
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
51-
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
52-
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
49+
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
50+
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
51+
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
52+
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
5353
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
5454
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
5555
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5656
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5757
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
58-
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
59-
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
58+
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
59+
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
6060
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
6161
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
62-
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
63-
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
62+
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
63+
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
6464
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
6565
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
6666
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=

pangolin.wxs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
33
<Package Name="Pangolin"
44
Language="1033"
5-
Version="0.7.1"
5+
Version="0.8.0"
66
Manufacturer="Fossorial"
77
UpgradeCode="165F13D3-9A2F-4AF7-9C68-474A8256B274"
88
Compressed="yes">

tunnel/build.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package tunnel
44

55
import (
66
"context"
7+
"sync"
78
"time"
89

910
"github.com/fosrl/newt/logger"
@@ -46,8 +47,19 @@ func (s *tunnelService) buildTunnel(config Config) error {
4647
return err
4748
}
4849

49-
fp := fingerprint.GatherFingerprintInfo().ToMap()
50-
postures := fingerprint.GatherPostureChecks().ToMap()
50+
var fp map[string]any
51+
var postures map[string]any
52+
var gatherWg sync.WaitGroup
53+
gatherWg.Add(2)
54+
go func() {
55+
defer gatherWg.Done()
56+
fp = fingerprint.GatherFingerprintInfo().ToMap()
57+
}()
58+
go func() {
59+
defer gatherWg.Done()
60+
postures = fingerprint.GatherPostureChecks().ToMap()
61+
}()
62+
gatherWg.Wait()
5163

5264
olmConfig := olmpkg.TunnelConfig{
5365
Endpoint: config.Endpoint,

0 commit comments

Comments
 (0)