Implement self-update recovery (T-403)

This commit is contained in:
ila
2026-07-19 22:00:38 +08:00
parent 2900deba4f
commit 13004218cf
35 changed files with 1754 additions and 16 deletions
+63
View File
@@ -0,0 +1,63 @@
package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"softbox.local/app-modern/platform/windows"
"softbox.local/core/updater"
)
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(arguments []string) error {
for _, option := range []string{"--pid", "--staging", "--target"} {
if err := requireOneOption(arguments, option); err != nil {
return err
}
}
flags := flag.NewFlagSet("SoftBoxUpdater", flag.ContinueOnError)
flags.SetOutput(io.Discard)
pid := flags.Int("pid", 0, "main SoftBox PID")
staging := flags.String("staging", "", "prepared staging directory")
target := flags.String("target", "", "fixed app target directory")
if err := flags.Parse(arguments); err != nil {
return fmt.Errorf("parse updater arguments: %w", err)
}
if flags.NArg() != 0 || *pid <= 0 || *staging == "" || *target == "" || !filepath.IsAbs(*staging) || !filepath.IsAbs(*target) {
return fmt.Errorf("usage: SoftBoxUpdater --pid <positive PID> --staging <absolute staging directory> --target <absolute root/app>")
}
requestID := filepath.Base(filepath.Clean(*staging))
platform := windows.New()
service := updater.NewService(platform, platform, platform, updater.FileHealthWaiter{}, updater.Timeouts{
ParentExit: 2 * time.Minute,
Health: 45 * time.Second,
})
return service.Update(context.Background(), updater.Request{
ParentPID: *pid, StagingDir: *staging, TargetDir: *target, RequestID: requestID,
})
}
func requireOneOption(arguments []string, option string) error {
count := 0
for _, argument := range arguments {
if argument == option || strings.HasPrefix(argument, option+"=") {
count++
}
}
if count != 1 {
return fmt.Errorf("%s must appear exactly once", option)
}
return nil
}
@@ -0,0 +1,14 @@
package main
import "testing"
func TestRunRejectsIncompleteAndDuplicateArguments(t *testing.T) {
if err := run(nil); err == nil {
t.Fatal("run(nil) succeeded")
}
if err := run([]string{
"--pid", "1", "--pid", "2", "--staging", "/root/staging/update-1234", "--target", "/root/app",
}); err == nil {
t.Fatal("run() accepted duplicate --pid")
}
}