返回 DeepSeek-Reasonix
shell_repair_guidance.go
根目录 / desktop / shell_repair_guidance.go
1 package main
2
3 import (
4 "os"
5 "slices"
6 "strconv"
7 "strings"
8 )
9
10 // RepairGuidanceView is a read-only repair hint for platforms where
11 // Reasonix must not run the system package manager. Command is an allowlisted,
12 // copy-only suggestion; it is never passed to a shell by the desktop backend.
13 type RepairGuidanceView struct {
14 Manager string `json:"manager"`
15 Command string `json:"command,omitempty"`
16 }
17
18 func shellRepairGuidanceForGOOS(goos string) *RepairGuidanceView {
19 switch goos {
20 case "linux":
21 contents, _ := os.ReadFile("/etc/os-release")
22 return linuxShellRepairGuidance(contents)
23 default:
24 return nil
25 }
26 }
27
28 func gitRepairGuidanceForGOOS(goos string) *RepairGuidanceView {
29 switch goos {
30 case "darwin":
31 return &RepairGuidanceView{Manager: "homebrew", Command: "brew install git"}
32 case "linux":
33 contents, _ := os.ReadFile("/etc/os-release")
34 return linuxGitRepairGuidance(contents)
35 default:
36 return nil
37 }
38 }
39
40 // linuxShellRepairGuidance maps trusted OS identity metadata to a fixed command.
41 // No os-release value is interpolated into the result, so a modified file cannot
42 // turn the Settings copy button into an arbitrary-command delivery surface.
43 func linuxShellRepairGuidance(osRelease []byte) *RepairGuidanceView {
44 return linuxPackageRepairGuidance(osRelease, "bash")
45 }
46
47 func linuxGitRepairGuidance(osRelease []byte) *RepairGuidanceView {
48 return linuxPackageRepairGuidance(osRelease, "git")
49 }
50
51 func linuxPackageRepairGuidance(osRelease []byte, packageName string) *RepairGuidanceView {
52 identities := linuxOSReleaseIdentities(osRelease)
53 for _, candidate := range []struct {
54 ids []string
55 manager string
56 bash string
57 git string
58 }{
59 {[]string{"debian", "ubuntu", "linuxmint", "pop"}, "apt", "apt-get install bash", "apt-get install git"},
60 {[]string{"fedora", "rhel", "centos", "rocky", "almalinux"}, "dnf", "dnf install bash", "dnf install git"},
61 {[]string{"arch", "manjaro", "endeavouros"}, "pacman", "pacman -S bash", "pacman -S git"},
62 {[]string{"opensuse", "suse", "sles"}, "zypper", "zypper install bash", "zypper install git"},
63 {[]string{"alpine"}, "apk", "apk add bash", "apk add git"},
64 } {
65 if intersectsIdentity(identities, candidate.ids) {
66 command := candidate.bash
67 if packageName == "git" {
68 command = candidate.git
69 } else if packageName != "bash" {
70 return &RepairGuidanceView{Manager: "system"}
71 }
72 return &RepairGuidanceView{Manager: candidate.manager, Command: command}
73 }
74 }
75 return &RepairGuidanceView{Manager: "system"}
76 }
77
78 func linuxOSReleaseIdentities(contents []byte) []string {
79 values := map[string]string{}
80 for line := range strings.SplitSeq(string(contents), "\n") {
81 line = strings.TrimSpace(line)
82 if line == "" || strings.HasPrefix(line, "#") {
83 continue
84 }
85 key, value, ok := strings.Cut(line, "=")
86 if !ok {
87 continue
88 }
89 key = strings.TrimSpace(key)
90 if key != "ID" && key != "ID_LIKE" {
91 continue
92 }
93 value = strings.TrimSpace(value)
94 if unquoted, err := strconv.Unquote(value); err == nil {
95 value = unquoted
96 } else {
97 value = strings.Trim(value, "'\"")
98 }
99 values[key] = strings.ToLower(value)
100 }
101 return strings.Fields(values["ID"] + " " + values["ID_LIKE"])
102 }
103
104 func intersectsIdentity(available, candidates []string) bool {
105 for _, actual := range available {
106 if slices.Contains(candidates, actual) {
107 return true
108 }
109 }
110 return false
111 }
112
112 lines GO