feat: publish verifiable proxyctl v1

This commit is contained in:
proxyctl coordinator
2026-07-17 09:54:08 +08:00
parent 5ec2b1dae1
commit 2b161e86e6
11 changed files with 536 additions and 28 deletions
+22 -4
View File
@@ -27,6 +27,9 @@ import (
"proxyctl/internal/tui"
)
// version is overridden for releases with -ldflags "-X main.version=<version>".
var version = "dev"
func main() {
if err := run(os.Args[1:], os.Stdin, os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, "proxyctl:", err)
@@ -50,11 +53,17 @@ func runWithSystemTargets(args []string, input io.Reader, output io.Writer, syst
type processEnvironment struct{}
var (
processCurrentUser = profile.SystemCurrentUser
processLookupUser = profile.SystemLookupUser
processProductionTargets = productionTargets
)
func (processEnvironment) CurrentUser() (profile.UserIdentity, error) {
return profile.SystemCurrentUser()
return processCurrentUser()
}
func (processEnvironment) LookupUser(name string) (profile.UserIdentity, error) {
return profile.SystemLookupUser(name)
return processLookupUser(name)
}
func (processEnvironment) LookupEnv(name string) (string, bool) { return os.LookupEnv(name) }
@@ -63,6 +72,15 @@ func runWithEnvironment(args []string, input io.Reader, output io.Writer, enviro
}
func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writer, environment profile.IdentityEnvironment, files shell.FileSystem, executable string, systemTargets []target.Target) error {
if len(args) == 1 && args[0] == "version" {
fmt.Fprintf(output, "proxyctl version %s\n", version)
return nil
}
if len(args) == 2 && args[0] == "version" && args[1] == "--short" {
fmt.Fprintln(output, version)
return nil
}
profileConfiguration, err := profile.ResolveConfiguration(environment)
if err != nil {
if len(args) == 1 && args[0] == "doctor" {
@@ -79,7 +97,7 @@ func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writ
}
shellTarget := shell.New(shellConfiguration, files, environment)
if systemTargets == nil {
systemTargets, err = productionTargets(environment)
systemTargets, err = processProductionTargets(environment)
if err != nil {
return err
}
@@ -129,7 +147,7 @@ func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writ
case len(args) == 1 && args[0] == "reset":
return runMaintenanceCommand(args, input, output, store, targets, mutationLock, logger, nil)
default:
return fmt.Errorf("usage: proxyctl [doctor|reset|shell install|shell uninstall]")
return fmt.Errorf("usage: proxyctl [version [--short]|doctor|reset|shell install|shell uninstall]")
}
}
+29
View File
@@ -11,6 +11,35 @@ import (
"proxyctl/internal/target"
)
func TestVersionOutputIsHumanAndScriptFriendly(t *testing.T) {
previous := version
version = "v1.2.3"
t.Cleanup(func() { version = previous })
var output bytes.Buffer
if err := run([]string{"version"}, strings.NewReader(""), &output); err != nil {
t.Fatalf("run version: %v", err)
}
if got, want := output.String(), "proxyctl version v1.2.3\n"; got != want {
t.Fatalf("human version output = %q, want %q", got, want)
}
output.Reset()
if err := run([]string{"version", "--short"}, strings.NewReader(""), &output); err != nil {
t.Fatalf("run short version: %v", err)
}
if got, want := output.String(), "v1.2.3\n"; got != want {
t.Fatalf("script version output = %q, want %q", got, want)
}
}
func TestVersionDoesNotRequireUserOrTargetResolution(t *testing.T) {
var output bytes.Buffer
if err := runWithEnvironmentAndTargets([]string{"version", "--short"}, strings.NewReader(""), &output, failingIdentityEnvironment{}, nil, "", nil); err != nil {
t.Fatalf("version depended on runtime environment: %v", err)
}
}
func TestProductionCompositionRegistersAPTContainerGNOMEAndDaemonTargets(t *testing.T) {
home := t.TempDir()
environment := commandEnvironment{identity: profile.UserIdentity{Username: "tester", HomeDir: home, UID: os.Getuid(), GID: os.Getgid()}, env: map[string]string{"SHELL": "/bin/bash"}}
+61 -24
View File
@@ -116,39 +116,75 @@ func TestDoctorStoragePermissionCheckIsReadOnly(t *testing.T) {
}
}
func TestMaintenanceCommandsRunThroughBuiltBinaryWithIsolatedXDG(t *testing.T) {
func TestCommandsRunAsRealProcessesInsideIsolatedUserState(t *testing.T) {
root := t.TempDir()
binary := filepath.Join(root, "proxyctl")
build := exec.Command("go", "build", "-o", binary, ".")
binary := filepath.Join(root, "bin", "proxyctl")
if err := os.MkdirAll(filepath.Dir(binary), 0o700); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Join(root, "home"), 0o700); err != nil {
t.Fatal(err)
}
build := exec.Command("go", "build", "-tags", "proxyctl_process_test", "-o", binary, ".")
if output, err := build.CombinedOutput(); err != nil {
t.Fatalf("build proxyctl: %v\n%s", err, output)
}
environment := isolatedProcessEnvironment(root)
doctor := exec.Command(binary, "doctor")
doctor.Env = environment
doctorOutput, doctorErr := doctor.CombinedOutput()
if doctorErr == nil || !strings.Contains(string(doctorOutput), "[FAIL]") || !strings.Contains(string(doctorOutput), "Target availability") {
invoke := func(input string, args ...string) ([]byte, error) {
t.Helper()
command := exec.Command(binary, args...)
command.Env = environment
command.Stdin = strings.NewReader(input)
return command.CombinedOutput()
}
defaultOutput, err := invoke("q")
if err != nil || !strings.Contains(string(defaultOutput), "proxyctl — Target panel") {
t.Fatalf("default process err=%v output=%q", err, defaultOutput)
}
versionOutput, err := invoke("", "version", "--short")
if err != nil || strings.TrimSpace(string(versionOutput)) == "" || strings.Contains(string(versionOutput), " ") {
t.Fatalf("version process err=%v output=%q", err, versionOutput)
}
doctorOutput, doctorErr := invoke("", "doctor")
if !strings.Contains(string(doctorOutput), "Profile validity") || (doctorErr != nil && !strings.Contains(string(doctorOutput), "[FAIL]")) {
t.Fatalf("doctor process err=%v output=%q", doctorErr, doctorOutput)
}
cancel := exec.Command(binary, "reset")
cancel.Env = environment
cancel.Stdin = strings.NewReader("no\n")
cancelOutput, err := cancel.CombinedOutput()
if err != nil || !strings.Contains(string(cancelOutput), "Reset cancelled") {
t.Fatalf("cancel process err=%v output=%q", err, cancelOutput)
resetOutput, err := invoke("no\n", "reset")
if err != nil || !strings.Contains(string(resetOutput), "Reset cancelled") {
t.Fatalf("reset cancellation process err=%v output=%q", err, resetOutput)
}
resetOutput, err = invoke("yes\n", "reset")
if err != nil || !strings.Contains(string(resetOutput), "shell: skipped (Disabled)") {
t.Fatalf("confirmed reset process err=%v output=%q", err, resetOutput)
}
installOutput, err := invoke("", "shell", "install")
if err != nil || !strings.Contains(string(installOutput), "installed") {
t.Fatalf("shell install process err=%v output=%q", err, installOutput)
}
startup := filepath.Join(root, "home", ".bashrc")
startupData, err := os.ReadFile(startup)
if err != nil || !strings.Contains(string(startupData), "proxyctl shell integration") {
t.Fatalf("isolated startup after install err=%v data=%q", err, startupData)
}
uninstallOutput, err := invoke("", "shell", "uninstall")
if err != nil || !strings.Contains(string(uninstallOutput), "uninstalled") {
t.Fatalf("shell uninstall process err=%v output=%q", err, uninstallOutput)
}
startupData, err = os.ReadFile(startup)
if err != nil || strings.Contains(string(startupData), "proxyctl shell integration") {
t.Fatalf("isolated startup after uninstall err=%v data=%q", err, startupData)
}
confirmed := exec.Command(binary, "reset")
confirmed.Env = environment
confirmed.Stdin = strings.NewReader("yes\n")
confirmedOutput, err := confirmed.CombinedOutput()
if err != nil || !strings.Contains(string(confirmedOutput), "shell: skipped (Disabled)") {
t.Fatalf("confirmed process err=%v output=%q", err, confirmedOutput)
}
if _, err := os.Stat(filepath.Join(root, "home", ".bashrc")); !os.IsNotExist(err) {
t.Fatalf("process smoke test touched a Shell startup file: %v", err)
for _, path := range []string{
filepath.Join(root, "etc"),
filepath.Join(root, "var"),
filepath.Join(root, "home", ".docker"),
} {
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("process smoke created prohibited system/Docker fixture %s: %v", path, err)
}
}
}
@@ -165,7 +201,8 @@ func isolatedProcessEnvironment(root string) []string {
"HOME="+filepath.Join(root, "home"),
"XDG_CONFIG_HOME="+filepath.Join(root, "config"),
"XDG_STATE_HOME="+filepath.Join(root, "state"),
"SHELL=/bin/fish",
"SHELL=/bin/bash",
"PROXYCTL_PROCESS_TEST_HOME="+filepath.Join(root, "home"),
)
}
+41
View File
@@ -0,0 +1,41 @@
//go:build proxyctl_process_test
package main
import (
"errors"
"os"
"os/user"
"strconv"
"proxyctl/internal/profile"
"proxyctl/internal/target"
)
// This file is compiled only into the process-smoke binary. It gives that real
// executable an identity whose home is inside the test sandbox and omits
// production system Targets, so no command can reach developer or host state.
func init() {
processProductionTargets = func(profile.IdentityEnvironment) ([]target.Target, error) {
return []target.Target{}, nil
}
processCurrentUser = func() (profile.UserIdentity, error) {
home := os.Getenv("PROXYCTL_PROCESS_TEST_HOME")
if home == "" {
return profile.UserIdentity{}, errors.New("PROXYCTL_PROCESS_TEST_HOME is required by proxyctl_process_test builds")
}
current, err := user.Current()
if err != nil {
return profile.UserIdentity{}, err
}
uid, err := strconv.Atoi(current.Uid)
if err != nil {
return profile.UserIdentity{}, err
}
gid, err := strconv.Atoi(current.Gid)
if err != nil {
return profile.UserIdentity{}, err
}
return profile.UserIdentity{Username: current.Username, HomeDir: home, UID: uid, GID: gid}, nil
}
}