返回 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 "reasonix/internal/config"
10 "reasonix/internal/desktopinstance"
11 "strings"
12
13 "reasonix/desktop/internal/update"
14 "reasonix/internal/installlayout"
15 "reasonix/internal/repair"
16 )
17
18 // activateVersionedWindowsFromStaging publishes the versioned-v1 layout from a
19 // staged NSIS payload whose signed manifest names every member:
20 //
21 // InstallRoot/
22 // Reasonix.exe (canonical GUI entry)
23 // reasonix-launcher.exe (only when preserving an existing entry)
24 // reasonix-cli.exe (small CLI entry)
25 // current.json
26 // versions/<version>/
27 // reasonix-desktop.exe
28 // reasonix-cli.exe
29 // reasonix-update-helper.exe
30 // app/... (Electron shell tree, schema 2 manifests)
31 //
32 // Any failure before the current.json pointer swap keeps the previous version active; the helper never rolls back.
33 func activateVersionedWindowsFromStaging(claimed *repair.UpdateTransaction, stagingDir string) error {
34 if claimed == nil {
35 return fmt.Errorf("versioned activate: transaction is nil")
36 }
37 installRoot := filepath.Clean(strings.TrimSpace(filepath.Dir(claimed.TargetPath)))
38 // When the claimed primary is already under versions/<ver>/, climb to root.
39 if root, err := installlayout.ResolveInstallRoot(claimed.TargetPath); err == nil && root != "" {
40 installRoot = root
41 }
42 version := strings.TrimSpace(claimed.ToVersion)
43 if err := installlayout.ValidateVersionName(version); err != nil {
44 // Accept bare product versions from NSIS (1.20.0 → v1.20.0).
45 if !strings.HasPrefix(version, "v") {
46 version = "v" + version
47 }
48 if err := installlayout.ValidateVersionName(version); err != nil {
49 return fmt.Errorf("versioned activate: %w", err)
50 }
51 }
52 stagingDir = filepath.Clean(strings.TrimSpace(stagingDir))
53
54 hashes, err := loadWindowsPayloadManifest(stagingDir, strings.TrimSpace(claimed.ToVersion))
55 if err != nil {
56 return fmt.Errorf("versioned activate: %w", err)
57 }
58 versionNames := update.WindowsPayloadVersionMembers(hashes)
59 members, err := stagedWindowsPayloadMembers(stagingDir, hashes, versionNames)
60 if err != nil {
61 return fmt.Errorf("versioned activate: %w", err)
62 }
63 rootFiles, err := stagedWindowsPayloadMembers(stagingDir, hashes, []string{"reasonix-launcher.exe"})
64 if err != nil {
65 return fmt.Errorf("versioned activate: %w", err)
66 }
67 launcherSrc := rootFiles[0].Path
68 cliSrc := filepath.Join(stagingDir, "reasonix-cli.exe")
69 const cliEntry = "app/resources/bin/reasonix-cli-launcher.exe"
70 if _, ok := hashes[cliEntry]; ok {
71 entry, entryErr := stagedWindowsPayloadMembers(stagingDir, hashes, []string{cliEntry})
72 if entryErr != nil {
73 return fmt.Errorf("versioned activate: %w", entryErr)
74 }
75 cliSrc = entry[0].Path
76 }
77
78 requestID := repair.UpdateTransactionID(claimed)
79 if requestID == "" {
80 requestID = "helper-" + version
81 }
82 release, err := desktopinstance.PrepareInstall(installRoot, config.ReasonixHomeDir(), false)
83 if err != nil {
84 return err
85 }
86 defer release()
87 if err := installlayout.ActivateVersion(installlayout.ActivationRequest{
88 InstallRoot: installRoot,
89 Version: version,
90 RequestID: requestID,
91 CheckProcesses: func() error { return desktopinstance.CheckInstallVacant(installRoot, config.ReasonixHomeDir()) },
92 Members: members,
93 RequiredNames: versionNames,
94 WindowsRootEntries: &installlayout.WindowsRootEntrySources{
95 LauncherPath: launcherSrc,
96 CLIEntryPath: cliSrc,
97 },
98 }); err != nil {
99 return err
100 }
101
102 // Remove flat release-unit leftovers so the install root is the thin layout.
103 // Do not remove the launcher/CLI/alias we just wrote.
104 for _, name := range []string{
105 "reasonix-desktop.exe",
106 "reasonix-guard.exe",
107 "reasonix-update-helper.exe", // helper lives only under versions/
108 } {
109 _ = os.Remove(filepath.Join(installRoot, name))
110 }
111 // Best-effort retention GC of older version trees.
112 _ = installlayout.CleanupStaleStaging(installRoot, 0)
113 return nil
114 }
115
116 // preferVersionedWindowsActivation reports whether the staged payload is
117 // complete enough for versioned-v1 activation.
118 func preferVersionedWindowsActivation(stagingDir string) bool {
119 for _, name := range []string{
120 "reasonix-desktop.exe",
121 "reasonix-cli.exe",
122 "reasonix-update-helper.exe",
123 "reasonix-launcher.exe",
124 } {
125 info, err := os.Lstat(filepath.Join(stagingDir, name))
126 if err != nil || !info.Mode().IsRegular() {
127 return false
128 }
129 }
130 return true
131 }
132
132 lines GO