返回 DeepSeek-Reasonix
versioned_windows.go
根目录 / desktop / cmd / update-helper / versioned_windows.go
1 //go:build windows
2
3 package main
4
5 import (
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "reasonix/internal/installlayout"
12 "reasonix/internal/repair"
13 )
14
15 // activateVersionedWindowsFromStaging publishes the versioned-v1 layout from a
16 // staged NSIS payload:
17 //
18 // InstallRoot/
19 // reasonix-launcher.exe
20 // Reasonix.exe (launcher alias when present or portable)
21 // reasonix-cli.exe (CLI entry; full binary for now)
22 // current.json
23 // versions/<version>/
24 // reasonix-desktop.exe
25 // reasonix-cli.exe
26 // reasonix-update-helper.exe
27 //
28 // Any failure before the current.json pointer swap leaves the previous active
29 // version unchanged. The helper never counts crashes or selects prior versions.
30 func activateVersionedWindowsFromStaging(claimed *repair.UpdateTransaction, stagingDir string) error {
31 if claimed == nil {
32 return fmt.Errorf("versioned activate: transaction is nil")
33 }
34 installRoot := filepath.Clean(strings.TrimSpace(filepath.Dir(claimed.TargetPath)))
35 // When the claimed primary is already under versions/<ver>/, climb to root.
36 if root, err := installlayout.ResolveInstallRoot(claimed.TargetPath); err == nil && root != "" {
37 installRoot = root
38 }
39 version := strings.TrimSpace(claimed.ToVersion)
40 if err := installlayout.ValidateVersionName(version); err != nil {
41 // Accept bare product versions from NSIS (1.20.0 → v1.20.0).
42 if !strings.HasPrefix(version, "v") {
43 version = "v" + version
44 }
45 if err := installlayout.ValidateVersionName(version); err != nil {
46 return fmt.Errorf("versioned activate: %w", err)
47 }
48 }
49 stagingDir = filepath.Clean(strings.TrimSpace(stagingDir))
50
51 desktopSrc := filepath.Join(stagingDir, "reasonix-desktop.exe")
52 cliSrc := filepath.Join(stagingDir, "reasonix-cli.exe")
53 helperSrc := filepath.Join(stagingDir, "reasonix-update-helper.exe")
54 launcherSrc := filepath.Join(stagingDir, "reasonix-launcher.exe")
55 for _, path := range []string{desktopSrc, cliSrc, helperSrc, launcherSrc} {
56 info, err := os.Lstat(path)
57 if err != nil {
58 return fmt.Errorf("versioned activate: staged %s: %w", filepath.Base(path), err)
59 }
60 if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
61 return fmt.Errorf("versioned activate: staged %s is not a regular file", filepath.Base(path))
62 }
63 }
64
65 requestID := repair.UpdateTransactionID(claimed)
66 if requestID == "" {
67 requestID = "helper-" + version
68 }
69 if err := installlayout.ActivateVersion(installlayout.ActivationRequest{
70 InstallRoot: installRoot,
71 Version: version,
72 RequestID: requestID,
73 Members: []installlayout.Member{
74 {Name: "reasonix-desktop.exe", Path: desktopSrc, Mode: 0o700},
75 {Name: "reasonix-cli.exe", Path: cliSrc, Mode: 0o700},
76 {Name: "reasonix-update-helper.exe", Path: helperSrc, Mode: 0o700},
77 },
78 RootMembers: []installlayout.Member{
79 {Name: "reasonix-launcher.exe", Path: launcherSrc, Mode: 0o700},
80 {Name: "Reasonix.exe", Path: launcherSrc, Mode: 0o700},
81 {Name: "reasonix-cli.exe", Path: cliSrc, Mode: 0o700},
82 },
83 RequiredRootNames: []string{"reasonix-launcher.exe", "Reasonix.exe", "reasonix-cli.exe"},
84 }); err != nil {
85 return err
86 }
87
88 // Remove flat release-unit leftovers so the install root is the thin layout.
89 // Do not remove the launcher/CLI/alias we just wrote.
90 for _, name := range []string{
91 "reasonix-desktop.exe",
92 "reasonix-guard.exe",
93 "reasonix-update-helper.exe", // helper lives only under versions/
94 } {
95 _ = os.Remove(filepath.Join(installRoot, name))
96 }
97 // Best-effort retention GC of older version trees.
98 _ = installlayout.RetainPreviousVersions(installRoot, 0)
99 _ = installlayout.CleanupStaleStaging(installRoot, 0)
100 return nil
101 }
102
103 // preferVersionedWindowsActivation reports whether the staged payload is
104 // complete enough for versioned-v1 activation.
105 func preferVersionedWindowsActivation(stagingDir string) bool {
106 for _, name := range []string{
107 "reasonix-desktop.exe",
108 "reasonix-cli.exe",
109 "reasonix-update-helper.exe",
110 "reasonix-launcher.exe",
111 } {
112 info, err := os.Lstat(filepath.Join(stagingDir, name))
113 if err != nil || !info.Mode().IsRegular() {
114 return false
115 }
116 }
117 return true
118 }
119
119 lines GO