From 79b531a3d0bd53b838b37515c10a3bf2b098a47b Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Jul 2026 16:41:34 -0400 Subject: [PATCH 1/7] Add match domains to config --- cmd/up/client/client.go | 17 +++++++++++++++++ internal/config/config.go | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/cmd/up/client/client.go b/cmd/up/client/client.go index 21d262e..6c6c457 100644 --- a/cmd/up/client/client.go +++ b/cmd/up/client/client.go @@ -53,6 +53,7 @@ type ClientUpCmdOpts struct { OverrideDNS bool TunnelDNS bool UpstreamDNS []string + MatchDomains []string } // validateDNSIP ensures the given DNS server string is a valid IP address. @@ -133,6 +134,7 @@ func ClientUpCmd() *cobra.Command { cmd.Flags().BoolVar(&opts.OverrideDNS, "override-dns", true, "When enabled, the client uses custom DNS servers to resolve internal resources and aliases. This overrides your system's default DNS settings. Queries that cannot be resolved as a Pangolin resource will be forwarded to your configured Upstream DNS Server.") cmd.Flags().BoolVar(&opts.TunnelDNS, "tunnel-dns", false, "When enabled, DNS queries are routed through the tunnel for remote resolution. To ensure queries are tunneled correctly, you must define the DNS server as a Pangolin resource and enter its address as an Upstream DNS Server.") cmd.Flags().StringSliceVar(&opts.UpstreamDNS, "upstream-dns", []string{}, "List of DNS servers to use for external DNS resolution if overriding system DNS") + cmd.Flags().StringSliceVar(&opts.MatchDomains, "match-domains", nil, "FQDN wildcard patterns (e.g. '*.proxy.internal') to check against local records/upstream DNS; queries for non-matching domains go directly to the system's DNS servers (default: match all domains, or the value from config if set)") cmd.Flags().BoolVar(&opts.Attached, "attach", false, "Run in attached (foreground) mode, (default: detached (background) mode)") cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Disable TUI and run silently when detached") @@ -161,6 +163,14 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) accountStore := config.AccountStoreFromContext(cmd.Context()) cfg := config.ConfigFromContext(cmd.Context()) + // Fall back to the persisted config value when --match-domains wasn't + // explicitly passed. Resolved here (rather than left to the subprocess, + // which runs as root and may not have access to the user's config) so it + // can be forwarded to the subprocess unconditionally below. + if !cmd.Flags().Changed("match-domains") && len(cfg.MatchDomains) > 0 { + opts.MatchDomains = cfg.MatchDomains + } + if runtime.GOOS == "windows" { err := errors.New("this command is currently unsupported on Windows") logger.Error("Error: %v", err) @@ -366,6 +376,12 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) // Comma sep cmdArgs = append(cmdArgs, "--upstream-dns", strings.Join(opts.UpstreamDNS, ",")) } + if len(opts.MatchDomains) > 0 { + // Always forwarded (rather than gated on Changed) since it may have + // come from the persisted config rather than the flag, and the + // subprocess (running as root) may not have access to that config. + cmdArgs = append(cmdArgs, "--match-domains", strings.Join(opts.MatchDomains, ",")) + } // Add positional args if any cmdArgs = append(cmdArgs, extraArgs...) @@ -605,6 +621,7 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) OverrideDNS: opts.OverrideDNS, TunnelDNS: opts.TunnelDNS, UpstreamDNS: upstreamDNS, + MatchDomains: opts.MatchDomains, UserToken: userToken, InitialFingerprint: initialFingerprint, InitialPostures: initialPostures, diff --git a/internal/config/config.go b/internal/config/config.go index d040fec..7fa6f2b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -32,6 +32,14 @@ type UpConfig struct { TunnelDNS *bool `mapstructure:"tunnel_dns" json:"tunnel_dns,omitempty"` UpstreamDNS []string `mapstructure:"upstream_dns" json:"upstream_dns,omitempty"` OverrideDNS *bool `mapstructure:"override_dns" json:"override_dns,omitempty"` + + // MatchDomains lists FQDN wildcard patterns (using * and ? wildcards, e.g. + // "*.proxy.internal") that olm should check against local records/upstream + // DNS. Queries for domains that don't match any pattern are sent directly to + // the host's system DNS servers. Used as the default for `pangolin up`'s + // --match-domains flag when the flag isn't passed explicitly. Empty means + // match every domain (the feature is disabled). + MatchDomains []string `mapstructure:"match_domains" json:"match_domains"` } // CompanionAppDataDirs holds per-platform overrides for the desktop app data directory. @@ -98,6 +106,7 @@ func newConfigViper() (*viper.Viper, error) { v.SetDefault("disable_update_check", false) v.SetDefault("disable_companion_mode", false) v.SetDefault("companion_app_data_dirs", map[string]string{}) + v.SetDefault("match_domains", []string{}) return v, nil } @@ -292,6 +301,7 @@ func (c *Config) Save() error { c.v.Set("disable_update_check", c.DisableUpdateCheck) c.v.Set("disable_companion_mode", c.DisableCompanionMode) c.v.Set("companion_app_data_dirs", c.CompanionAppDataDirs) + c.v.Set("match_domains", c.Up.MatchDomains) // Only persist up keys that were explicitly set so we do not write // zero-value bools that would later look like intentional overrides. From 1ac23ad48f76a7cfe0dc4824778c9fd52f9c0e1f Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 17 Jul 2026 13:54:01 -0400 Subject: [PATCH 2/7] Fix matched domains not compiling --- cmd/up/client/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/up/client/client.go b/cmd/up/client/client.go index 6c6c457..6c1519e 100644 --- a/cmd/up/client/client.go +++ b/cmd/up/client/client.go @@ -167,8 +167,8 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) // explicitly passed. Resolved here (rather than left to the subprocess, // which runs as root and may not have access to the user's config) so it // can be forwarded to the subprocess unconditionally below. - if !cmd.Flags().Changed("match-domains") && len(cfg.MatchDomains) > 0 { - opts.MatchDomains = cfg.MatchDomains + if !cmd.Flags().Changed("match-domains") && cfg.IsSet("up.match_domains") { + opts.MatchDomains = cfg.GetStringSlice("up.match_domains") } if runtime.GOOS == "windows" { From b05d9bae63c58571fe69ee389a676fe73889506f Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 17 Jul 2026 13:58:24 -0400 Subject: [PATCH 3/7] Reflect local status in the status command --- cmd/status/client/client.go | 18 ++++++++++++++++-- internal/olm/client.go | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cmd/status/client/client.go b/cmd/status/client/client.go index 1ccd925..3fd72c3 100644 --- a/cmd/status/client/client.go +++ b/cmd/status/client/client.go @@ -90,7 +90,7 @@ func printStatusTable(status *olm.StatusResponse) { // Print peers if there are any if len(status.PeerStatuses) > 0 { fmt.Println("") - peerHeaders := []string{"SITE", "ENDPOINT", "STATUS", "LAST SEEN", "RELAY"} + peerHeaders := []string{"SITE", "ENDPOINT", "STATUS", "LAST SEEN", "CONNECTION"} peerRows := [][]string{} for _, peer := range status.PeerStatuses { @@ -101,7 +101,7 @@ func printStatusTable(status *olm.StatusResponse) { peer.Endpoint, formatStatus(peer.Connected, true), // Peers don't have registered field, use true lastSeen, - fmt.Sprintf("%t", peer.IsRelay), + formatConnectionMode(peer.IsLocal, peer.IsRelay), }) } @@ -111,6 +111,20 @@ func printStatusTable(status *olm.StatusResponse) { } } +// formatConnectionMode summarizes how a peer is currently connected. Local and relay are +// mutually exclusive; when neither applies the peer is connected directly to its public +// endpoint. +func formatConnectionMode(isLocal, isRelay bool) string { + switch { + case isLocal: + return "Local" + case isRelay: + return "Relay" + default: + return "Direct" + } +} + // formatStatus formats the connection status // Status is only "Connected" when both connected and registered are true func formatStatus(connected, registered bool) string { diff --git a/internal/olm/client.go b/internal/olm/client.go index 4263ca9..323f2c7 100644 --- a/internal/olm/client.go +++ b/internal/olm/client.go @@ -47,6 +47,7 @@ type OLMPeerStatus struct { LastSeen time.Time `json:"lastSeen"` Endpoint string `json:"endpoint,omitempty"` IsRelay bool `json:"isRelay"` + IsLocal bool `json:"isLocal"` // true when connected via a local network endpoint, bypassing both the public endpoint and relay PeerIP string `json:"peerAddress,omitempty"` } From 0f5968c5338fd7f4ed5a8db981172f646bd0b55c Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 17 Jul 2026 16:41:57 -0400 Subject: [PATCH 4/7] Properly allow setting match domains --- go.mod | 5 +++-- go.sum | 4 ---- internal/config/config.go | 16 ++++++++++++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 2d38a98..062a4e7 100644 --- a/go.mod +++ b/go.mod @@ -78,5 +78,6 @@ require ( // If changes to Olm or Newt are required, use these // replace directives during development. // -// replace github.com/fosrl/olm => ../olm -// replace github.com/fosrl/newt => ../newt +replace github.com/fosrl/olm => ../olm + +replace github.com/fosrl/newt => ../newt diff --git a/go.sum b/go.sum index 87e9625..617329f 100644 --- a/go.sum +++ b/go.sum @@ -50,10 +50,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/fosrl/newt v1.14.0 h1:9jpyfCNAtsH7rPojyIGJwqOqnLZdxm8b+njY5ZuY/6c= -github.com/fosrl/newt v1.14.0/go.mod h1:l6kWoZPSaXT+ZRUjiyPgwflRqZWYaXpUj9oQ0sOPh4o= -github.com/fosrl/olm v1.7.0 h1:Om/wXKNcbNz1hN46olKrkwQd3bUknzqP2qxCYPDEyXI= -github.com/fosrl/olm v1.7.0/go.mod h1:6TXwb4EXb1DfqB3Ks2QPZotsOtg+aJsa/t4VWaMIPHI= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/internal/config/config.go b/internal/config/config.go index 7fa6f2b..cb25514 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -56,6 +56,7 @@ var ConfigOptions = []string{ "up.tunnel_dns", "up.upstream_dns", "up.override_dns", + "up.match_domains", } // SupportedConfigKeys returns the settable config keys. @@ -106,7 +107,7 @@ func newConfigViper() (*viper.Viper, error) { v.SetDefault("disable_update_check", false) v.SetDefault("disable_companion_mode", false) v.SetDefault("companion_app_data_dirs", map[string]string{}) - v.SetDefault("match_domains", []string{}) + v.SetDefault("up.match_domains", []string{}) return v, nil } @@ -231,6 +232,10 @@ func (c *Config) SetKey(key, value string) error { servers := splitCommaList(value) c.Up.UpstreamDNS = servers c.v.Set(key, servers) + case "up.match_domains": + domains := splitCommaList(value) + c.Up.MatchDomains = domains + c.v.Set(key, domains) default: return fmt.Errorf("unknown config key %q; supported keys: %s", key, strings.Join(SupportedConfigKeys(), ", ")) } @@ -263,6 +268,11 @@ func (c *Config) GetKey(key string) (string, error) { return "", errConfigKeyUnset(key) } return strings.Join(c.GetStringSlice(key), ","), nil + case "up.match_domains": + if !c.IsSet(key) { + return "", errConfigKeyUnset(key) + } + return strings.Join(c.GetStringSlice(key), ","), nil default: return "", fmt.Errorf("unknown config key %q; supported keys: %s", key, strings.Join(SupportedConfigKeys(), ", ")) } @@ -301,7 +311,6 @@ func (c *Config) Save() error { c.v.Set("disable_update_check", c.DisableUpdateCheck) c.v.Set("disable_companion_mode", c.DisableCompanionMode) c.v.Set("companion_app_data_dirs", c.CompanionAppDataDirs) - c.v.Set("match_domains", c.Up.MatchDomains) // Only persist up keys that were explicitly set so we do not write // zero-value bools that would later look like intentional overrides. @@ -314,6 +323,9 @@ func (c *Config) Save() error { if c.Up.UpstreamDNS != nil { c.v.Set("up.upstream_dns", c.Up.UpstreamDNS) } + if c.Up.MatchDomains != nil { + c.v.Set("up.match_domains", c.Up.MatchDomains) + } dir, err := GetPangolinConfigDir() if err != nil { From 906fca6d2ba01c8a6e3d9450760f841cda7eb57f Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 17 Jul 2026 17:17:04 -0400 Subject: [PATCH 5/7] Rename to match_domains_dns --- cmd/up/client/client.go | 4 ++-- internal/config/config.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/up/client/client.go b/cmd/up/client/client.go index 6c1519e..fe3c10a 100644 --- a/cmd/up/client/client.go +++ b/cmd/up/client/client.go @@ -167,8 +167,8 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) // explicitly passed. Resolved here (rather than left to the subprocess, // which runs as root and may not have access to the user's config) so it // can be forwarded to the subprocess unconditionally below. - if !cmd.Flags().Changed("match-domains") && cfg.IsSet("up.match_domains") { - opts.MatchDomains = cfg.GetStringSlice("up.match_domains") + if !cmd.Flags().Changed("match-domains") && cfg.IsSet("up.match_domains_dns") { + opts.MatchDomains = cfg.GetStringSlice("up.match_domains_dns") } if runtime.GOOS == "windows" { diff --git a/internal/config/config.go b/internal/config/config.go index cb25514..77da1ed 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,7 +39,7 @@ type UpConfig struct { // the host's system DNS servers. Used as the default for `pangolin up`'s // --match-domains flag when the flag isn't passed explicitly. Empty means // match every domain (the feature is disabled). - MatchDomains []string `mapstructure:"match_domains" json:"match_domains"` + MatchDomains []string `mapstructure:"match_domains_dns" json:"match_domains_dns"` } // CompanionAppDataDirs holds per-platform overrides for the desktop app data directory. @@ -56,7 +56,7 @@ var ConfigOptions = []string{ "up.tunnel_dns", "up.upstream_dns", "up.override_dns", - "up.match_domains", + "up.match_domains_dns", } // SupportedConfigKeys returns the settable config keys. @@ -107,7 +107,7 @@ func newConfigViper() (*viper.Viper, error) { v.SetDefault("disable_update_check", false) v.SetDefault("disable_companion_mode", false) v.SetDefault("companion_app_data_dirs", map[string]string{}) - v.SetDefault("up.match_domains", []string{}) + v.SetDefault("up.match_domains_dns", []string{}) return v, nil } @@ -232,7 +232,7 @@ func (c *Config) SetKey(key, value string) error { servers := splitCommaList(value) c.Up.UpstreamDNS = servers c.v.Set(key, servers) - case "up.match_domains": + case "up.match_domains_dns": domains := splitCommaList(value) c.Up.MatchDomains = domains c.v.Set(key, domains) @@ -268,7 +268,7 @@ func (c *Config) GetKey(key string) (string, error) { return "", errConfigKeyUnset(key) } return strings.Join(c.GetStringSlice(key), ","), nil - case "up.match_domains": + case "up.match_domains_dns": if !c.IsSet(key) { return "", errConfigKeyUnset(key) } @@ -324,7 +324,7 @@ func (c *Config) Save() error { c.v.Set("up.upstream_dns", c.Up.UpstreamDNS) } if c.Up.MatchDomains != nil { - c.v.Set("up.match_domains", c.Up.MatchDomains) + c.v.Set("up.match_domains_dns", c.Up.MatchDomains) } dir, err := GetPangolinConfigDir() From d082617ada25a35dd51043205b39243294140e5f Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 18 Jul 2026 17:38:33 -0400 Subject: [PATCH 6/7] add prefer local routes config --- cmd/config/config.go | 3 +++ cmd/up/client/client.go | 49 ++++++++++++++++++++++++--------------- internal/config/config.go | 23 ++++++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index 4d5a93e..50255bd 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -134,6 +134,9 @@ func dumpConfig(cfg *config.Config) error { if cfg.IsSet("up.upstream_dns") { up["upstream_dns"] = cfg.GetStringSlice("up.upstream_dns") } + if cfg.IsSet("up.prefer_local_routes") { + up["prefer_local_routes"] = cfg.GetBool("up.prefer_local_routes") + } if len(up) > 0 { out["up"] = up } diff --git a/cmd/up/client/client.go b/cmd/up/client/client.go index fe3c10a..09b3db2 100644 --- a/cmd/up/client/client.go +++ b/cmd/up/client/client.go @@ -35,25 +35,26 @@ const ( ) type ClientUpCmdOpts struct { - ID string - Secret string - Endpoint string - OrgID string - MTU int - DNS string - InterfaceName string - LogLevel string - HTTPAddr string - PingInterval time.Duration - PingTimeout time.Duration - Holepunch bool - TlsClientCert string - Attached bool - Silent bool - OverrideDNS bool - TunnelDNS bool - UpstreamDNS []string - MatchDomains []string + ID string + Secret string + Endpoint string + OrgID string + MTU int + DNS string + InterfaceName string + LogLevel string + HTTPAddr string + PingInterval time.Duration + PingTimeout time.Duration + Holepunch bool + TlsClientCert string + Attached bool + Silent bool + OverrideDNS bool + TunnelDNS bool + UpstreamDNS []string + MatchDomains []string + PreferLocalRoutes bool } // validateDNSIP ensures the given DNS server string is a valid IP address. @@ -135,6 +136,7 @@ func ClientUpCmd() *cobra.Command { cmd.Flags().BoolVar(&opts.TunnelDNS, "tunnel-dns", false, "When enabled, DNS queries are routed through the tunnel for remote resolution. To ensure queries are tunneled correctly, you must define the DNS server as a Pangolin resource and enter its address as an Upstream DNS Server.") cmd.Flags().StringSliceVar(&opts.UpstreamDNS, "upstream-dns", []string{}, "List of DNS servers to use for external DNS resolution if overriding system DNS") cmd.Flags().StringSliceVar(&opts.MatchDomains, "match-domains", nil, "FQDN wildcard patterns (e.g. '*.proxy.internal') to check against local records/upstream DNS; queries for non-matching domains go directly to the system's DNS servers (default: match all domains, or the value from config if set)") + cmd.Flags().BoolVar(&opts.PreferLocalRoutes, "prefer-local-routes", false, "Add tunnel routes with a high metric so overlapping local/connected routes take precedence (default false)") cmd.Flags().BoolVar(&opts.Attached, "attach", false, "Run in attached (foreground) mode, (default: detached (background) mode)") cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Disable TUI and run silently when detached") @@ -156,6 +158,9 @@ func applyUpDefaults(cmd *cobra.Command, opts *ClientUpCmdOpts, cfg *config.Conf if !cmd.Flags().Changed("upstream-dns") && cfg.IsSet("up.upstream_dns") { opts.UpstreamDNS = cfg.GetStringSlice("up.upstream_dns") } + if !cmd.Flags().Changed("prefer-local-routes") && cfg.IsSet("up.prefer_local_routes") { + opts.PreferLocalRoutes = cfg.GetBool("up.prefer_local_routes") + } } func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) error { @@ -382,6 +387,11 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) // subprocess (running as root) may not have access to that config. cmdArgs = append(cmdArgs, "--match-domains", strings.Join(opts.MatchDomains, ",")) } + if opts.PreferLocalRoutes { + // Always forwarded when true (rather than gated on Changed) for the + // same reason as MatchDomains above - it may have come from config. + cmdArgs = append(cmdArgs, "--prefer-local-routes") + } // Add positional args if any cmdArgs = append(cmdArgs, extraArgs...) @@ -622,6 +632,7 @@ func clientUpMain(cmd *cobra.Command, opts *ClientUpCmdOpts, extraArgs []string) TunnelDNS: opts.TunnelDNS, UpstreamDNS: upstreamDNS, MatchDomains: opts.MatchDomains, + PreferLocalRoutes: opts.PreferLocalRoutes, UserToken: userToken, InitialFingerprint: initialFingerprint, InitialPostures: initialPostures, diff --git a/internal/config/config.go b/internal/config/config.go index 77da1ed..4b2810c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,6 +40,13 @@ type UpConfig struct { // --match-domains flag when the flag isn't passed explicitly. Empty means // match every domain (the feature is disabled). MatchDomains []string `mapstructure:"match_domains_dns" json:"match_domains_dns"` + + // PreferLocalRoutes, when enabled, adds tunnel routes with a high metric so + // overlapping local/connected routes take precedence over the VPN route to + // the same destination. Used as the default for `pangolin up`'s + // --prefer-local-routes flag when the flag isn't passed explicitly. + // Defaults to false. + PreferLocalRoutes *bool `mapstructure:"prefer_local_routes" json:"prefer_local_routes,omitempty"` } // CompanionAppDataDirs holds per-platform overrides for the desktop app data directory. @@ -57,6 +64,7 @@ var ConfigOptions = []string{ "up.upstream_dns", "up.override_dns", "up.match_domains_dns", + "up.prefer_local_routes", } // SupportedConfigKeys returns the settable config keys. @@ -236,6 +244,13 @@ func (c *Config) SetKey(key, value string) error { domains := splitCommaList(value) c.Up.MatchDomains = domains c.v.Set(key, domains) + case "up.prefer_local_routes": + b, err := parseBool(value) + if err != nil { + return err + } + c.Up.PreferLocalRoutes = &b + c.v.Set(key, b) default: return fmt.Errorf("unknown config key %q; supported keys: %s", key, strings.Join(SupportedConfigKeys(), ", ")) } @@ -273,6 +288,11 @@ func (c *Config) GetKey(key string) (string, error) { return "", errConfigKeyUnset(key) } return strings.Join(c.GetStringSlice(key), ","), nil + case "up.prefer_local_routes": + if !c.IsSet(key) { + return "", errConfigKeyUnset(key) + } + return fmt.Sprintf("%t", c.GetBool(key)), nil default: return "", fmt.Errorf("unknown config key %q; supported keys: %s", key, strings.Join(SupportedConfigKeys(), ", ")) } @@ -326,6 +346,9 @@ func (c *Config) Save() error { if c.Up.MatchDomains != nil { c.v.Set("up.match_domains_dns", c.Up.MatchDomains) } + if c.Up.PreferLocalRoutes != nil { + c.v.Set("up.prefer_local_routes", *c.Up.PreferLocalRoutes) + } dir, err := GetPangolinConfigDir() if err != nil { From 7c481c65c913290f8fb6a980a4249955731b9e1a Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 19 Jul 2026 14:37:13 -0400 Subject: [PATCH 7/7] Update go mod and flake and add script --- flake.nix | 2 +- go.mod | 9 ++++----- go.sum | 4 ++++ scripts/update-vendor-hash.sh | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100755 scripts/update-vendor-hash.sh diff --git a/flake.nix b/flake.nix index 64dc9b6..13e1cc9 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ version = "0.13.0"; src = ./.; - vendorHash = "sha256-yHU/xfO+I5YncycGkPkpKGUM11YyMrAkS17N/nAmUc0="; + vendorHash = "sha256-S4di9hVoExIgtlTpV9cEHI2Ah6GGSTmsojFE+tPfCb0="; ldflags = [ "-s" diff --git a/go.mod b/go.mod index 062a4e7..4f01339 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,8 @@ require ( github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/creack/pty v1.1.24 - github.com/fosrl/newt v1.14.0 - github.com/fosrl/olm v1.7.0 + github.com/fosrl/newt v1.15.0 + github.com/fosrl/olm v1.8.0 github.com/mattn/go-isatty v0.0.20 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/cobra v1.10.2 @@ -78,6 +78,5 @@ require ( // If changes to Olm or Newt are required, use these // replace directives during development. // -replace github.com/fosrl/olm => ../olm - -replace github.com/fosrl/newt => ../newt +// replace github.com/fosrl/olm => ../olm +// replace github.com/fosrl/newt => ../newt diff --git a/go.sum b/go.sum index 617329f..dfdc6e0 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/fosrl/newt v1.15.0 h1:WpL0whZM1FMjUe2Vy5jSH1bgbxm1O9k1qCyF/mqZT+s= +github.com/fosrl/newt v1.15.0/go.mod h1:l6kWoZPSaXT+ZRUjiyPgwflRqZWYaXpUj9oQ0sOPh4o= +github.com/fosrl/olm v1.8.0 h1:9X+3GRLZzVYXrdvJmL3czRuDK/RD7Y2BLbzonm7SpVM= +github.com/fosrl/olm v1.8.0/go.mod h1:r4GTKfN0sf5L6AQgStwx9lyURsNcog+faX/dn94JOLM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= diff --git a/scripts/update-vendor-hash.sh b/scripts/update-vendor-hash.sh new file mode 100755 index 0000000..47eba60 --- /dev/null +++ b/scripts/update-vendor-hash.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Recompute flake.nix's vendorHash using an ephemeral nixos/nix container, +# so no Nix install touches the host system. Requires docker (or podman). +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +FLAKE="$REPO_ROOT/flake.nix" +RUNTIME="${CONTAINER_RUNTIME:-docker}" + +current_hash="$(grep -oP '(?<=vendorHash = ")[^"]+' "$FLAKE")" + +cleanup() { + sed -i "s|vendorHash = pkgs.lib.fakeHash;|vendorHash = \"$current_hash\";|" "$FLAKE" +} +trap cleanup EXIT + +sed -i "s|vendorHash = \"$current_hash\";|vendorHash = pkgs.lib.fakeHash;|" "$FLAKE" + +output="$("$RUNTIME" run --rm -e NIXPKGS_ALLOW_UNFREE=1 -v "$REPO_ROOT":/src -w /src nixos/nix \ + sh -c "git config --global --add safe.directory /src && nix --extra-experimental-features 'nix-command flakes' build --impure .#pangolin-cli 2>&1" || true)" + +new_hash="$(echo "$output" | grep -oP '(?<=got:\s{4})sha256-\S+' || true)" + +if [ -z "$new_hash" ]; then + echo "Could not determine new vendorHash. Full output:" >&2 + echo "$output" >&2 + exit 1 +fi + +sed -i "s|vendorHash = pkgs.lib.fakeHash;|vendorHash = \"$new_hash\";|" "$FLAKE" +trap - EXIT + +echo "vendorHash updated to: $new_hash"