返回 DeepSeek-Reasonix
missing_shell.go
根目录 / cmd / reasonix-legacy-migrator / missing_shell.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "runtime"
10 "time"
11
12 "reasonix/internal/config"
13 "reasonix/internal/installlayout"
14 "reasonix/internal/repair"
15 )
16
17 // errShellMissing identifies a flat release unit whose desktop cannot start:
18 // the desktop needs the Electron app/ tree beside it, and the 1.18–1.19.2 file
19 // updaters publish only the three binaries they know by name.
20 var errShellMissing = errors.New("migrate: flat release unit has no Electron desktop shell")
21
22 const migrateLogName = "legacy-migrate.log"
23
24 // refuseShellMissing commits nothing for an absent or incomplete shell: no
25 // version directory, no current.json, no health mark and no flat cleanup.
26 // Where an old updater's transaction is still pending it restores the release
27 // unit that transaction replaced.
28 func refuseShellMissing(installRoot, activeVersion string, relaunch bool, cause error) error {
29 refusal := fmt.Errorf("%w: %s has no %s/ tree", errShellMissing, installRoot, installlayout.AppShellDirName)
30 if cause != nil {
31 refusal = fmt.Errorf("%w: %s/ tree is unusable: %w", errShellMissing, installlayout.AppShellDirName, cause)
32 }
33 result, rollbackErr := rollbackLegacyPendingUpdate(installRoot, activeVersion)
34 switch {
35 case rollbackErr != nil:
36 refusal = fmt.Errorf("%w; restore the previous release: %w", refusal, rollbackErr)
37 case result.RolledBack:
38 refusal = fmt.Errorf("%w; restored %s", refusal, result.FromVersion)
39 if relaunch {
40 if err := startRestoredRelease(installRoot); err != nil {
41 refusal = fmt.Errorf("%w; start the restored release: %w", refusal, err)
42 }
43 }
44 }
45 logMigrateRefusal(installRoot, result.RolledBack, refusal)
46 return refusal
47 }
48
49 func rollbackLegacyPendingUpdate(installRoot, activeVersion string) (repair.UpdateRollbackResult, error) {
50 // Windows cannot replace the running migrator image, and its installer
51 // carries the shell into update mode, so this state is Unix-only.
52 if runtime.GOOS == "windows" {
53 return repair.UpdateRollbackResult{}, nil
54 }
55 tx, err := readLegacyPendingUpdate(installRoot, activeVersion)
56 if err != nil || tx == nil {
57 return repair.UpdateRollbackResult{}, err
58 }
59 result, err := repair.RollbackPendingUpdateExact(tx)
60 if err == nil && result.MixedInstall {
61 err = fmt.Errorf("rollback left a mix of two releases")
62 }
63 return result, err
64 }
65
66 // startRestoredRelease relaunches the way the legacy updater itself does:
67 // through its Guard when the restored unit has one, else the desktop directly.
68 func startRestoredRelease(installRoot string) error {
69 if guard := optionalRegular(filepath.Join(installRoot, "reasonix-guard")); guard != "" {
70 return startDetached(installRoot, guard, "launch", "--detach")
71 }
72 if desktop := optionalRegular(filepath.Join(installRoot, installlayout.DesktopBinaryName())); desktop != "" {
73 return startDetached(installRoot, desktop)
74 }
75 return fmt.Errorf("restored release has no entry point")
76 }
77
78 func startDetached(dir, path string, args ...string) error {
79 cmd := exec.Command(path, args...)
80 cmd.Dir = dir
81 return cmd.Start()
82 }
83
84 // logMigrateRefusal persists the refusal: a migrator relaunched by an old
85 // updater has no terminal, so stderr alone reaches nobody.
86 func logMigrateRefusal(installRoot string, rolledBack bool, refusal error) {
87 home := config.ReasonixHomeDir()
88 if home == "" {
89 return
90 }
91 path := filepath.Join(home, "desktop-shell", "logs", migrateLogName)
92 if os.MkdirAll(filepath.Dir(path), 0o700) != nil {
93 return
94 }
95 f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
96 if err != nil {
97 return
98 }
99 defer f.Close()
100 _, _ = fmt.Fprintf(f, "%s event=legacy_migrate_refused code=shell_missing version=%s rolled_back=%t root=%q detail=%q\n",
101 time.Now().UTC().Format(time.RFC3339), version, rolledBack, installRoot, refusal.Error())
102 }
103
103 lines GO