feat: fix proxy compatibility and establish TUI foundation

This commit is contained in:
proxyctl coordinator
2026-07-17 20:30:52 +08:00
parent 0f4c122eff
commit daeb788eac
34 changed files with 1521 additions and 176 deletions
+3 -3
View File
@@ -28,18 +28,18 @@ func TestConfigurationTargetsSudoInvokingUsersSessionAndState(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if got.UID != 1000 || got.GID != 1001 || got.HomeDir != "/home/alice" || got.RuntimeDir != "/run/user/1000" || got.StateFile != "/home/alice/.local/state/proxyctl/gnome.json" {
if got.UID != 1000 || got.GID != 1001 || got.HomeDir != "/home/alice" || got.ConfigDir != "/home/alice/.config" || got.RuntimeDir != "/run/user/1000" || got.StateFile != "/home/alice/.local/state/proxyctl/gnome.json" {
t.Fatalf("%#v", got)
}
}
func TestConfigurationKeepsDirectUsersSafeAbsoluteXDGPaths(t *testing.T) {
user := profile.UserIdentity{Username: "alice", HomeDir: "/home/alice", UID: 1000, GID: 1001}
got, err := gnome.ResolveConfiguration(identityEnvironment{current: user, env: map[string]string{"XDG_STATE_HOME": "/home/alice/state", "XDG_RUNTIME_DIR": "/run/user/1000", "DBUS_SESSION_BUS_ADDRESS": "unix:path=/custom/bus"}})
got, err := gnome.ResolveConfiguration(identityEnvironment{current: user, env: map[string]string{"XDG_CONFIG_HOME": "/home/alice/config", "XDG_STATE_HOME": "/home/alice/state", "XDG_RUNTIME_DIR": "/run/user/1000", "DBUS_SESSION_BUS_ADDRESS": "unix:path=/custom/bus"}})
if err != nil {
t.Fatal(err)
}
if got.StateFile != "/home/alice/state/proxyctl/gnome.json" || got.RuntimeDir != "/run/user/1000" || got.SessionBus != "unix:path=/custom/bus" {
if got.ConfigDir != "/home/alice/config" || got.StateFile != "/home/alice/state/proxyctl/gnome.json" || got.RuntimeDir != "/run/user/1000" || got.SessionBus != "unix:path=/custom/bus" {
t.Fatalf("%#v", got)
}
}
+7 -2
View File
@@ -168,7 +168,9 @@ func ResolveConfiguration(environment profile.IdentityEnvironment) (Configuratio
if stateDir == "" {
stateDir = filepath.Join(owner.HomeDir, ".local", "state")
}
configDir := filepath.Dir(profileConfiguration.Path)
// Profile storage lives below <XDG_CONFIG_HOME>/proxyctl, while gsettings
// must receive the XDG base itself or dconf opens a proxyctl-private database.
configDir := filepath.Dir(filepath.Dir(profileConfiguration.Path))
dataDir := filepath.Join(owner.HomeDir, ".local", "share")
if value, ok := environment.LookupEnv("XDG_DATA_HOME"); ok && filepath.IsAbs(value) && (!invoking || pathWithin(value, owner.HomeDir)) {
dataDir = value
@@ -691,7 +693,7 @@ func translate(input profile.Profile) (settings, error) {
if err != nil {
return settings{}, err
}
result := settings{Mode: "manual"}
result := settings{Mode: "manual", IgnoreHosts: []string{}}
if p.Mode == profile.SingleMode {
if result.SocksHost, result.SocksPort, _, _, err = endpoint(p.SingleURL, "Single proxy URL", "socks5", false); err != nil {
return settings{}, err
@@ -874,6 +876,9 @@ func parseQuoted(raw string) (string, int, error) {
func parseStrings(raw string) ([]string, error) {
raw = strings.TrimSpace(raw)
// GVariant prints an empty string array with its otherwise-inferable type
// annotation ("@as []") on some gsettings versions.
raw = strings.TrimSpace(strings.TrimPrefix(raw, "@as"))
if len(raw) < 2 || raw[0] != '[' || raw[len(raw)-1] != ']' {
return nil, fmt.Errorf("invalid gsettings string array %q", raw)
}
+23 -1
View File
@@ -30,7 +30,7 @@ func newRunner() *fakeRunner {
return &fakeRunner{schemas: []string{"org.gnome.system.proxy", "org.gnome.system.proxy.http", "org.gnome.system.proxy.https", "org.gnome.system.proxy.socks"}, values: map[string]string{
"org.gnome.system.proxy mode": "'none'",
"org.gnome.system.proxy autoconfig-url": "''",
"org.gnome.system.proxy ignore-hosts": "[]",
"org.gnome.system.proxy ignore-hosts": "@as []",
"org.gnome.system.proxy.http host": "''", "org.gnome.system.proxy.http port": "0",
"org.gnome.system.proxy.http use-authentication": "false", "org.gnome.system.proxy.http authentication-user": "''", "org.gnome.system.proxy.http authentication-password": "''",
"org.gnome.system.proxy.https host": "''", "org.gnome.system.proxy.https port": "0",
@@ -439,6 +439,28 @@ func TestSingleSOCKSProxyUsesGNOMESOCKSForAllProtocols(t *testing.T) {
}
}
func TestDualProtocolEndpointConfiguresHTTPAndSOCKSAtSameAddress(t *testing.T) {
r, s := newRunner(), &memoryState{}
p := &profile.Profile{
Name: "dual", Mode: profile.AdvancedMode,
HTTPURL: "http://localhost:7890", HTTPSURL: "http://localhost:7890", AllURL: "socks5://localhost:7890",
}
got := adapter(r, s).SetDesired(context.Background(), true, p)
if got.Err != nil || got.Inspection.State != target.Enabled {
t.Fatalf("dual-protocol result = %#v", got)
}
for _, key := range []string{"org.gnome.system.proxy.http host", "org.gnome.system.proxy.https host", "org.gnome.system.proxy.socks host"} {
if r.values[key] != "'localhost'" {
t.Errorf("%s = %q", key, r.values[key])
}
}
for _, key := range []string{"org.gnome.system.proxy.http port", "org.gnome.system.proxy.https port", "org.gnome.system.proxy.socks port"} {
if r.values[key] != "7890" {
t.Errorf("%s = %q", key, r.values[key])
}
}
}
func TestAdvancedHTTPProxyCredentialsArePreserved(t *testing.T) {
r, s := newRunner(), &memoryState{}
p := *active()