1package bootstrap
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7)
8
9const (
10 developmentEnvironmentName = "dev"
11 developmentPrivateMode os.FileMode = 0o700
12)
13
14// ApplyDevelopmentEnvironment isolates the process from the user's XDG and
15// home directories when ENV=dev. It must run before GTK, CEF, or any adapter
16// can initialize and read process environment settings.
17func ApplyDevelopmentEnvironment() error {
18 if os.Getenv("ENV") != developmentEnvironmentName {
19 return nil
20 }
21
22 cwd, err := os.Getwd()
23 if err != nil {
24 return fmt.Errorf("resolve development environment root: %w", err)
25 }
26
27 devDir := filepath.Join(cwd, ".dev")
28 if err := ensureDevelopmentDirectory(devDir, true); err != nil {
29 return err
30 }
31
32 root := filepath.Join(devDir, "dumber")
33 paths := map[string]string{
34 "HOME": filepath.Join(root, "home"),
35 "XDG_CONFIG_HOME": filepath.Join(root, "config"),
36 "XDG_DATA_HOME": filepath.Join(root, "data"),
37 "XDG_STATE_HOME": filepath.Join(root, "state"),
38 "XDG_CACHE_HOME": filepath.Join(root, "cache"),
39 }
40 for _, path := range developmentPrivateDirectories(root, paths) {
41 if err := ensureDevelopmentDirectory(path, true); err != nil {
42 return err
43 }
44 }
45 for name, path := range paths {
46 if err := os.Setenv(name, path); err != nil {
47 return fmt.Errorf("set %s for development environment: %w", name, err)
48 }
49 }
50
51 return nil
52}
53
54// developmentPrivateDirectories returns every currently known path that Dumber
55// creates below the development sandbox. Creating and validating them before
56// GTK, CEF, logging, or the browser profile starts prevents those subsystems
57// from following a pre-existing sandbox symlink.
58func developmentPrivateDirectories(root string, paths map[string]string) []string {
59 enginesDir := filepath.Join(root, "engines")
60 cefDir := filepath.Join(enginesDir, "cef")
61 webkitDir := filepath.Join(enginesDir, "webkit")
62 runtimeDir := filepath.Join(root, "runtime")
63 return []string{
64 root,
65 paths["HOME"],
66 paths["XDG_CONFIG_HOME"],
67 paths["XDG_DATA_HOME"],
68 paths["XDG_STATE_HOME"],
69 paths["XDG_CACHE_HOME"],
70 filepath.Join(root, "logs"),
71 enginesDir,
72 cefDir,
73 filepath.Join(cefDir, "data"),
74 filepath.Join(cefDir, "runtime"),
75 filepath.Join(cefDir, "logs"),
76 webkitDir,
77 filepath.Join(webkitDir, "data"),
78 filepath.Join(webkitDir, "cache"),
79 filepath.Join(webkitDir, "runtime"),
80 filepath.Join(webkitDir, "logs"),
81 runtimeDir,
82 filepath.Join(runtimeDir, "cef"),
83 filepath.Join(runtimeDir, "webkit"),
84 }
85}
86
87func ensureDevelopmentDirectory(path string, private bool) error {
88 if err := os.MkdirAll(path, developmentPrivateMode); err != nil {
89 return fmt.Errorf("create development environment directory %q: %w", path, err)
90 }
91
92 info, err := os.Lstat(path)
93 if err != nil {
94 return fmt.Errorf("inspect development environment directory %q: %w", path, err)
95 }
96 if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
97 return fmt.Errorf("development environment path %q must be a directory, not %s", path, info.Mode().Type())
98 }
99 if !private {
100 return nil
101 }
102 if err = os.Chmod(path, developmentPrivateMode); err != nil {
103 return fmt.Errorf("restrict development environment directory %q: %w", path, err)
104 }
105 info, err = os.Stat(path)
106 if err != nil {
107 return fmt.Errorf("verify development environment directory %q: %w", path, err)
108 }
109 if info.Mode().Perm() != developmentPrivateMode.Perm() {
110 return fmt.Errorf("development environment directory %q permissions are %04o, want 0700", path, info.Mode().Perm())
111 }
112 return nil
113}