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
+37 -3
View File
@@ -21,8 +21,14 @@ import (
)
const (
overlayName = "99proxyctl"
overlayHeader = "// Generated by proxyctl. Do not edit.\n"
// overlayName sorts after digit-prefixed and common letter-named fragments
// (such as a hand-written proxy.conf) because APT applies apt.conf.d entries
// in lexical order with later values winning; the overlay must be effective
// for Enabled to be truthful. legacyOverlayName is the pre-rename overlay
// removed on the next mutation so upgrades never leave stale fragments.
overlayName = "zz-proxyctl.conf"
legacyOverlayName = "99proxyctl"
overlayHeader = "// Generated by proxyctl. Do not edit.\n"
)
// Configuration names the Debian marker and the isolated APT configuration
@@ -234,6 +240,8 @@ func (a *Adapter) SetDesired(ctx context.Context, enabled bool, active *profile.
mutationErr = a.files.AtomicWrite(a.configuration.OverlayFile, translated.rendered, 0o600)
if mutationErr != nil {
mutationErr = fmt.Errorf("write managed APT overlay: %w", mutationErr)
} else {
mutationErr = a.removeLegacyOverlay()
}
} else {
existing, err := a.files.ReadFile(a.configuration.OverlayFile)
@@ -248,6 +256,9 @@ func (a *Adapter) SetDesired(ctx context.Context, enabled bool, active *profile.
mutationErr = fmt.Errorf("remove managed APT overlay: %w", err)
}
}
if mutationErr == nil {
mutationErr = a.removeLegacyOverlay()
}
}
inspection, inspectErr := a.inspect(ctx, active)
if mutationErr == nil && inspectErr != nil {
@@ -262,6 +273,29 @@ func (a *Adapter) SetDesired(ctx context.Context, enabled bool, active *profile.
return target.OperationResult{Inspection: inspection, Err: redactError(mutationErr, active)}
}
// removeLegacyOverlay deletes the pre-rename overlay when proxyctl owns it, so
// upgrades never leave a stale credential-bearing fragment behind. A legacy
// file without the managed header is user-owned and stays untouched.
func (a *Adapter) removeLegacyOverlay() error {
legacy := filepath.Join(a.configuration.ConfigDirectory, legacyOverlayName)
if legacy == a.configuration.OverlayFile {
return nil
}
data, err := a.files.ReadFile(legacy)
switch {
case errors.Is(err, fs.ErrNotExist):
return nil
case err != nil:
return fmt.Errorf("inspect legacy APT overlay: %w", err)
case !isOwnedOverlay(data):
return nil
}
if err := a.files.Remove(legacy); err != nil && !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("remove legacy APT overlay: %w", err)
}
return nil
}
func (a *Adapter) dump(ctx context.Context) ([]byte, error) {
command, err := a.runner.LookPath("apt-config")
if err != nil {
@@ -293,7 +327,7 @@ func translate(input profile.Profile) (translation, error) {
}
for _, candidate := range []struct {
field, value string
}{{"HTTP proxy", p.HTTPURL}, {"HTTPS proxy", p.HTTPSURL}, {"ALL/SOCKS proxy", p.AllURL}} {
}{{"effective HTTP proxy", httpValue}, {"effective HTTPS proxy", httpsValue}} {
if candidate.value == "" {
continue
}