profile.go RawHistoryFolder

1package runtimeprofile
2
3import (
4	"crypto/sha256"
5	"encoding/hex"
6	"fmt"
7	"path/filepath"
8	"strings"
9)
10
11const (
12	browserLaunchSocketName = "browser-launch.sock"
13	devIPCSocketPathLimit   = 104
14	engineWebKit            = "webkit"
15	engineCEF               = "cef"
16)
17
18// Mode identifies the execution sandbox mode.
19type Mode string
20
21const (
22	ModeProd Mode = "prod"
23	ModeDev  Mode = "dev"
24)
25
26// BasePaths holds the already-resolved dumber XDG homes used in prod.
27type BasePaths struct {
28	ConfigHome string
29	DataHome   string
30	StateHome  string
31	CacheHome  string
32}
33
34// ResolveInput contains the external inputs needed to resolve a runtime profile.
35type ResolveInput struct {
36	Env    func(string) string
37	CWD    func() (string, error)
38	Engine string
39	Base   BasePaths
40}
41
42// SharedPaths are shared by the whole runtime sandbox.
43type SharedPaths struct {
44	// RootDir is only populated when the shared sandbox has a single root
45	// directory (for example ENV=dev under .dev/dumber). It is intentionally
46	// empty in prod, where shared paths come from distinct XDG homes.
47	RootDir   string
48	ConfigDir string
49	DataDir   string
50	StateDir  string
51	CacheDir  string
52	LogDir    string
53}
54
55// EnginePaths are engine-specific technical paths.
56type EnginePaths struct {
57	Name       string
58	RootDir    string
59	RuntimeDir string
60	LogDir     string
61}
62
63// IPCPaths are runtime coordination paths.
64type IPCPaths struct {
65	RuntimeDir          string
66	BrowserLaunchSocket string
67}
68
69// Profile is the fully resolved runtime profile for one mode+engine namespace.
70type Profile struct {
71	InstanceRoot        string
72	InstanceAppIDSuffix string
73	Mode                Mode
74	Engine              string
75	Shared              SharedPaths
76	EnginePaths         EnginePaths
77	IPC                 IPCPaths
78}
79
80// Resolve builds a runtime profile from pure inputs.
81func Resolve(input ResolveInput) (Profile, error) {
82	engine := normalizeEngine(input.Engine)
83	if envValue(input.Env, "ENV") == string(ModeDev) {
84		cwd, err := cwdValue(input.CWD)
85		if err != nil {
86			return Profile{}, fmt.Errorf("resolve cwd: %w", err)
87		}
88		root := filepath.Join(cwd, ".dev", "dumber")
89		engineRoot := filepath.Join(root, "engines", engine)
90		ipcRoot, err := devIPCRoot(input.Env, root, engine)
91		if err != nil {
92			return Profile{}, err
93		}
94		return Profile{
95			Mode:   ModeDev,
96			Engine: engine,
97			Shared: SharedPaths{
98				RootDir:   root,
99				ConfigDir: filepath.Join(root, "config"),
100				DataDir:   filepath.Join(root, "data"),
101				StateDir:  filepath.Join(root, "state"),
102				CacheDir:  filepath.Join(root, "cache"),
103				LogDir:    filepath.Join(root, "logs"),
104			},
105			EnginePaths: EnginePaths{
106				Name:       engine,
107				RootDir:    engineRoot,
108				RuntimeDir: filepath.Join(engineRoot, "runtime"),
109				LogDir:     filepath.Join(engineRoot, "logs"),
110			},
111			IPC: IPCPaths{
112				RuntimeDir:          ipcRoot,
113				BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
114			},
115		}, nil
116	}
117
118	shared := SharedPaths{
119		ConfigDir: input.Base.ConfigHome,
120		DataDir:   input.Base.DataHome,
121		StateDir:  input.Base.StateHome,
122		CacheDir:  input.Base.CacheHome,
123		LogDir:    filepath.Join(input.Base.StateHome, "logs"),
124	}
125	engineRoot := filepath.Join(shared.StateDir, "engines", engine)
126	ipcRoot := filepath.Join(shared.StateDir, "runtime", engine)
127	return Profile{
128		Mode:   ModeProd,
129		Engine: engine,
130		Shared: shared,
131		EnginePaths: EnginePaths{
132			Name:       engine,
133			RootDir:    engineRoot,
134			RuntimeDir: filepath.Join(engineRoot, "runtime"),
135			LogDir:     filepath.Join(engineRoot, "logs"),
136		},
137		IPC: IPCPaths{
138			RuntimeDir:          ipcRoot,
139			BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
140		},
141	}, nil
142}
143
144func devIPCRoot(env func(string) string, root, engine string) (string, error) {
145	rootHash := devIPCRootHash(root)
146	for _, base := range []string{envValue(env, "XDG_RUNTIME_DIR"), envValue(env, "TMPDIR")} {
147		if base == "" {
148			continue
149		}
150		ipcRoot := devIPCRootFromBase(base, rootHash, engine)
151		if len(filepath.Join(ipcRoot, browserLaunchSocketName)) < devIPCSocketPathLimit {
152			return ipcRoot, nil
153		}
154	}
155
156	ipcRoot := filepath.Join(root, "runtime", engine)
157	socketPath := filepath.Join(ipcRoot, browserLaunchSocketName)
158	if len(socketPath) < devIPCSocketPathLimit {
159		return ipcRoot, nil
160	}
161	return "", fmt.Errorf(
162		"browser launch socket path too long: %d bytes; set XDG_RUNTIME_DIR or TMPDIR to a shorter runtime directory",
163		len(socketPath),
164	)
165}
166
167func devIPCRootFromBase(base, rootHash, engine string) string {
168	return filepath.Join(base, "dumber", "dev-"+rootHash, engine)
169}
170
171func devIPCRootHash(root string) string {
172	sum := sha256.Sum256([]byte(root))
173	return hex.EncodeToString(sum[:])[:12]
174}
175
176// normalizeEngine trims and lowercases the runtime engine name.
177// Supported engines are "webkit" and "cef"; any other value falls back to
178// the default "webkit" runtime namespace.
179func normalizeEngine(engine string) string {
180	switch normalized := strings.ToLower(strings.TrimSpace(engine)); normalized {
181	case "", engineWebKit:
182		return engineWebKit
183	case engineCEF:
184		return engineCEF
185	default:
186		return engineWebKit
187	}
188}
189
190func envValue(get func(string) string, key string) string {
191	if get == nil {
192		return ""
193	}
194	return get(key)
195}
196
197func cwdValue(get func() (string, error)) (string, error) {
198	if get == nil {
199		return "", fmt.Errorf("cwd getter is nil")
200	}
201	return get()
202}
203
204// CEFUserDataDir returns the resolved CEF root cache/user-data directory.
205func (p Profile) CEFUserDataDir() string {
206	if p.InstanceRoot != "" {
207		return filepath.Join(p.InstanceRoot, "cef")
208	}
209	if p.Mode == ModeDev {
210		return filepath.Join(p.EnginePaths.RootDir, "data")
211	}
212	return filepath.Join(p.Shared.DataDir, "cef_user_data")
213}
214
215// CEFLogFile returns the resolved default CEF runtime log path.
216func (p Profile) CEFLogFile() string {
217	return filepath.Join(p.EnginePaths.LogDir, "cef_runtime.log")
218}
219
220// WebKitDataDir returns the resolved WebKit website data directory.
221func (p Profile) WebKitDataDir() string {
222	if p.Mode == ModeDev {
223		return filepath.Join(p.EnginePaths.RootDir, "data")
224	}
225	return p.Shared.DataDir
226}
227
228// WebKitCacheDir returns the resolved WebKit cache directory.
229func (p Profile) WebKitCacheDir() string {
230	if p.Mode == ModeDev {
231		return filepath.Join(p.EnginePaths.RootDir, "cache")
232	}
233	// Keep the current prod layout under state for non-user-managed technical
234	// engine cache data; this intentionally preserves existing WebKit behavior.
235	return filepath.Join(p.Shared.StateDir, "webkit-cache")
236}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close