返回 DeepSeek-Reasonix
shell_bootstrap.go
根目录 / desktop / shell_bootstrap.go
1 package main
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9
10 "reasonix/internal/installlayout"
11 "reasonix/internal/proc"
12 )
13
14 const shellServiceEnv = "REASONIX_DESKTOP_SERVICE"
15
16 // exitIfShellBootstrapped hands a plain desktop launch to the Electron shell
17 // installed beside this binary and exits; the shell restarts this binary as
18 // its --host-rpc service.
19 func exitIfShellBootstrapped(args []string) {
20 if handled, exitCode := maybeBootstrapShell(args); handled {
21 os.Exit(exitCode)
22 }
23 }
24
25 // maybeBootstrapShell reports handled=false when the launch is already a
26 // service or contract mode, or when no shell executable is installed beside
27 // this binary.
28 func maybeBootstrapShell(args []string) (handled bool, exitCode int) {
29 if hostRPCRequested(args) {
30 return false, 0
31 }
32 if _, ok := emitContractDir(args); ok {
33 return false, 0
34 }
35 exe, err := os.Executable()
36 if err != nil {
37 return false, 0
38 }
39 return bootstrapShell(exe, runtime.GOOS, args, os.Environ())
40 }
41
42 func bootstrapShell(exe, goos string, args, env []string) (handled bool, exitCode int) {
43 shell, ok := shellExecutableBeside(exe, goos)
44 if !ok {
45 return false, 0
46 }
47 cmd := proc.VisibleCommand(shell, args...)
48 cmd.Env = processEnvWithOverrides(env, map[string]string{shellServiceEnv: exe})
49 cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
50 detachShellProcess(cmd)
51 if err := cmd.Start(); err != nil {
52 fmt.Fprintln(os.Stderr, "start desktop shell:", err)
53 return true, 1
54 }
55 return true, 0
56 }
57
58 // shellExecutableBeside resolves app/<shell> next to exe, or Contents/MacOS/
59 // Reasonix beside the service inside a macOS bundle, and never exe itself.
60 func shellExecutableBeside(exe, goos string) (string, bool) {
61 exe = filepath.Clean(exe)
62 shell := shellPathForExecutable(exe, goos)
63 info, err := os.Lstat(shell)
64 if err != nil || !info.Mode().IsRegular() {
65 return "", false
66 }
67 if same, err := installlayout.SameRegularFile(exe, shell); err != nil || same {
68 return "", false
69 }
70 return shell, true
71 }
72
73 func shellPathForExecutable(exe, goos string) string {
74 dir := filepath.Dir(exe)
75 name := installlayout.ShellExecutableNameFor(goos)
76 shell := filepath.Join(dir, installlayout.AppShellDirName, name)
77 if goos == "darwin" {
78 if contents, ok := macAppContentsForExecutable(exe); ok {
79 shell = filepath.Join(contents, "MacOS", name)
80 }
81 } else if goos == "linux" && dir == "/usr/bin" {
82 // The native package keeps executables in /usr/bin and Chromium in /usr/lib.
83 shell = filepath.Join("/usr/lib/reasonix", installlayout.AppShellDirName, name)
84 }
85 return shell
86 }
87
88 // macAppContentsForExecutable recognizes every supported service location in
89 // a macOS application bundle without relying on the name of the .app itself.
90 // It is deliberately lexical: callers that mutate or trust the bundle must
91 // additionally resolve symlinks and validate Info.plist.
92 func macAppContentsForExecutable(exe string) (string, bool) {
93 exe = filepath.Clean(strings.TrimSpace(exe))
94 if exe == "" || !filepath.IsAbs(exe) {
95 return "", false
96 }
97 dir := filepath.Dir(exe)
98 for dir != filepath.Dir(dir) {
99 if filepath.Base(dir) == "Contents" && strings.HasSuffix(filepath.Dir(dir), ".app") {
100 rel, err := filepath.Rel(dir, exe)
101 if err != nil {
102 return "", false
103 }
104 rel = filepath.ToSlash(rel)
105 if strings.HasPrefix(rel, "MacOS/") || strings.HasPrefix(rel, "Resources/service/") {
106 return dir, true
107 }
108 return "", false
109 }
110 dir = filepath.Dir(dir)
111 }
112 return "", false
113 }
114
114 lines GO