Skip to content

Commit 64cdc22

Browse files
committed
chore: backup snapshot before ACL hardening
1 parent 5be3221 commit 64cdc22

27 files changed

Lines changed: 2599 additions & 95 deletions

src/bootstrap/init.go

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
package bootstrap
2+
3+
import (
4+
prefs "common/preferences"
5+
"common/registry"
6+
"fmt"
7+
"nvm/link"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
)
12+
13+
const (
14+
bootstrapVersionV1 = uint32(1)
15+
bootstrapVersionV2 = uint32(2)
16+
currentBootstrapVersion = bootstrapVersionV2
17+
bootstrapVersionValueName = "BootstrapVersion"
18+
legacyUserProfileInitializedValueName = "UserProfileInitialized"
19+
)
20+
21+
func EnsureUserProfileInitialized() error {
22+
state, err := currentBootstrapState()
23+
if err != nil {
24+
return fmt.Errorf("failed to check bootstrap state: %w", err)
25+
}
26+
27+
shimDir, err := ShimDir()
28+
if err != nil {
29+
return err
30+
}
31+
32+
linkDir, err := LinkDir()
33+
if err != nil {
34+
return err
35+
}
36+
37+
if err := EnsureHiddenDir(shimDir); err != nil {
38+
return fmt.Errorf("failed to create shim directory: %w", err)
39+
}
40+
41+
programShimPath, err := ProgramShimPath()
42+
if err != nil {
43+
return err
44+
}
45+
46+
if err := syncShimExecutable(programShimPath, filepath.Join(shimDir, "node.exe")); err != nil {
47+
return fmt.Errorf("failed to synchronize node shim: %w", err)
48+
}
49+
50+
programProxyPath, err := ProgramProxyPath()
51+
if err != nil {
52+
return err
53+
}
54+
55+
dataProxyPath, err := DataProxyPath()
56+
if err != nil {
57+
return err
58+
}
59+
60+
if err := syncSharedExecutable(programProxyPath, dataProxyPath); err != nil {
61+
return fmt.Errorf("failed to synchronize shared proxy shim: %w", err)
62+
}
63+
64+
programSyncRoot, err := ProgramSyncRoot()
65+
if err != nil {
66+
return err
67+
}
68+
69+
dataSyncRoot, err := DataSyncRoot()
70+
if err != nil {
71+
return err
72+
}
73+
74+
if err := seedDirectoryContents(programSyncRoot, dataSyncRoot); err != nil {
75+
return fmt.Errorf("failed to seed sync assets: %w", err)
76+
}
77+
78+
if state.version < currentBootstrapVersion {
79+
dataRoot, err := DataRoot()
80+
if err != nil {
81+
return err
82+
}
83+
84+
if err := cleanupLegacyUserPayload(dataRoot); err != nil {
85+
return fmt.Errorf("failed to clean legacy per-user payload: %w", err)
86+
}
87+
}
88+
89+
if err := EnsureHiddenDir(linkDir); err != nil {
90+
return fmt.Errorf("failed to create link directory: %w", err)
91+
}
92+
93+
nodejsPath, err := NodejsPath()
94+
if err != nil {
95+
return err
96+
}
97+
98+
mode, err := settingString("mode")
99+
if err != nil {
100+
return fmt.Errorf("failed to read operating mode: %w", err)
101+
}
102+
103+
if mode == "" {
104+
mode = "shim"
105+
}
106+
107+
activeVersion := ""
108+
if strings.EqualFold(mode, "link") {
109+
activeVersion, err = settingString("active_version")
110+
if err != nil {
111+
return fmt.Errorf("failed to read active version: %w", err)
112+
}
113+
}
114+
115+
needsRepair, err := profileNeedsRepair(state.version, strings.ToLower(mode), shimDir, linkDir, nodejsPath, dataProxyPath, activeVersion)
116+
if err != nil {
117+
return err
118+
}
119+
if !needsRepair {
120+
if state.needsMarkerUpgrade() {
121+
if err := writeBootstrapVersion(currentBootstrapVersion); err != nil {
122+
return fmt.Errorf("failed to update bootstrap version marker: %w", err)
123+
}
124+
}
125+
return nil
126+
}
127+
128+
switch strings.ToLower(mode) {
129+
case "shim":
130+
if err := link.Link(shimDir, nodejsPath); err != nil {
131+
return fmt.Errorf("failed to initialize .nodejs junction: %w", err)
132+
}
133+
case "link":
134+
linkNodePath, err := LinkNodePath()
135+
if err != nil {
136+
return err
137+
}
138+
139+
if activeVersion != "" {
140+
installRoot, err := InstallRoot()
141+
if err != nil {
142+
return err
143+
}
144+
145+
if err := link.Link(filepath.Join(installRoot, "v"+activeVersion), linkNodePath); err != nil {
146+
return fmt.Errorf("failed to initialize link-mode target: %w", err)
147+
}
148+
}
149+
150+
if err := link.Link(linkNodePath, nodejsPath); err != nil {
151+
return fmt.Errorf("failed to initialize .nodejs junction: %w", err)
152+
}
153+
default:
154+
return fmt.Errorf("unsupported operating mode %q during profile initialization", mode)
155+
}
156+
157+
if err := writeBootstrapVersion(currentBootstrapVersion); err != nil {
158+
return fmt.Errorf("failed to write bootstrap version marker: %w", err)
159+
}
160+
161+
return nil
162+
}
163+
164+
func profileNeedsRepair(version uint32, mode, shimDir, linkDir, nodejsPath, dataProxyPath, activeVersion string) (bool, error) {
165+
if version < currentBootstrapVersion {
166+
return true, nil
167+
}
168+
169+
requiredPaths := []string{shimDir, linkDir, nodejsPath, dataProxyPath}
170+
if mode == "link" && strings.TrimSpace(activeVersion) != "" {
171+
linkNodePath, err := LinkNodePath()
172+
if err != nil {
173+
return false, err
174+
}
175+
requiredPaths = append(requiredPaths, linkNodePath)
176+
}
177+
178+
for _, candidate := range requiredPaths {
179+
if _, err := os.Lstat(candidate); err != nil {
180+
if os.IsNotExist(err) {
181+
return true, nil
182+
}
183+
return false, fmt.Errorf("failed to inspect runtime path %s: %w", candidate, err)
184+
}
185+
}
186+
187+
return false, nil
188+
}
189+
190+
type bootstrapState struct {
191+
version uint32
192+
hasVersionMarker bool
193+
hasLegacyMarker bool
194+
}
195+
196+
func (s bootstrapState) needsMarkerUpgrade() bool {
197+
return s.version == currentBootstrapVersion && (!s.hasVersionMarker || s.hasLegacyMarker)
198+
}
199+
200+
func currentBootstrapState() (bootstrapState, error) {
201+
value, exists, err := registry.Get(bootstrapVersionPath())
202+
if err != nil {
203+
return bootstrapState{}, err
204+
}
205+
if exists {
206+
version, err := normalizeBootstrapVersion(value)
207+
if err != nil {
208+
return bootstrapState{}, err
209+
}
210+
return bootstrapState{version: version, hasVersionMarker: true}, nil
211+
}
212+
213+
initialized, legacyExists, err := registry.GetBool(legacyInitializationMarkerPath())
214+
if err != nil {
215+
return bootstrapState{}, err
216+
}
217+
if legacyExists && initialized {
218+
return bootstrapState{version: bootstrapVersionV1, hasLegacyMarker: true}, nil
219+
}
220+
221+
return bootstrapState{}, nil
222+
}
223+
224+
func normalizeBootstrapVersion(value interface{}) (uint32, error) {
225+
switch v := value.(type) {
226+
case uint32:
227+
return v, nil
228+
case uint64:
229+
return uint32(v), nil
230+
case int:
231+
return uint32(v), nil
232+
case int32:
233+
return uint32(v), nil
234+
case int64:
235+
return uint32(v), nil
236+
case string:
237+
trimmed := strings.TrimSpace(v)
238+
if trimmed == "" {
239+
return 0, nil
240+
}
241+
var parsed uint32
242+
if _, err := fmt.Sscanf(trimmed, "%d", &parsed); err != nil {
243+
return 0, fmt.Errorf("invalid bootstrap version %q", v)
244+
}
245+
return parsed, nil
246+
default:
247+
return 0, fmt.Errorf("unsupported bootstrap version type %T", value)
248+
}
249+
}
250+
251+
func writeBootstrapVersion(version uint32) error {
252+
if err := registry.Put(version, bootstrapVersionPath()); err != nil {
253+
return err
254+
}
255+
256+
if err := registry.Del(legacyInitializationMarkerPath()); err != nil {
257+
return err
258+
}
259+
260+
return nil
261+
}
262+
263+
func bootstrapVersionPath() string {
264+
return prefs.ROOT + "/" + bootstrapVersionValueName
265+
}
266+
267+
func legacyInitializationMarkerPath() string {
268+
return prefs.ROOT + "/" + legacyUserProfileInitializedValueName
269+
}

0 commit comments

Comments
 (0)