v0.30.0 eb3068f

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	Mode        Mode
72	Engine      string
73	Shared      SharedPaths
74	EnginePaths EnginePaths
75	IPC         IPCPaths
76}
77
78// Resolve builds a runtime profile from pure inputs.
79func Resolve(input ResolveInput) (Profile, error) {
80	engine := normalizeEngine(input.Engine)
81	if envValue(input.Env, "ENV") == string(ModeDev) {
82		cwd, err := cwdValue(input.CWD)
83		if err != nil {
84			return Profile{}, fmt.Errorf("resolve cwd: %w", err)
85		}
86		root := filepath.Join(cwd, ".dev", "dumber")
87		engineRoot := filepath.Join(root, "engines", engine)
88		ipcRoot, err := devIPCRoot(input.Env, root, engine)
89		if err != nil {
90			return Profile{}, err
91		}
92		return Profile{
93			Mode:   ModeDev,
94			Engine: engine,
95			Shared: SharedPaths{
96				RootDir:   root,
97				ConfigDir: filepath.Join(root, "config"),
98				DataDir:   filepath.Join(root, "data"),
99				StateDir:  filepath.Join(root, "state"),
100				CacheDir:  filepath.Join(root, "cache"),
101				LogDir:    filepath.Join(root, "logs"),
102			},
103			EnginePaths: EnginePaths{
104				Name:       engine,
105				RootDir:    engineRoot,
106				RuntimeDir: filepath.Join(engineRoot, "runtime"),
107				LogDir:     filepath.Join(engineRoot, "logs"),
108			},
109			IPC: IPCPaths{
110				RuntimeDir:          ipcRoot,
111				BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
112			},
113		}, nil
114	}
115
116	shared := SharedPaths{
117		ConfigDir: input.Base.ConfigHome,
118		DataDir:   input.Base.DataHome,
119		StateDir:  input.Base.StateHome,
120		CacheDir:  input.Base.CacheHome,
121		LogDir:    filepath.Join(input.Base.StateHome, "logs"),
122	}
123	engineRoot := filepath.Join(shared.StateDir, "engines", engine)
124	ipcRoot := filepath.Join(shared.StateDir, "runtime", engine)
125	return Profile{
126		Mode:   ModeProd,
127		Engine: engine,
128		Shared: shared,
129		EnginePaths: EnginePaths{
130			Name:       engine,
131			RootDir:    engineRoot,
132			RuntimeDir: filepath.Join(engineRoot, "runtime"),
133			LogDir:     filepath.Join(engineRoot, "logs"),
134		},
135		IPC: IPCPaths{
136			RuntimeDir:          ipcRoot,
137			BrowserLaunchSocket: filepath.Join(ipcRoot, browserLaunchSocketName),
138		},
139	}, nil
140}
141
142func devIPCRoot(env func(string) string, root, engine string) (string, error) {
143	rootHash := devIPCRootHash(root)
144	for _, base := range []string{envValue(env, "XDG_RUNTIME_DIR"), envValue(env, "TMPDIR")} {
145		if base == "" {
146			continue
147		}
148		ipcRoot := devIPCRootFromBase(base, rootHash, engine)
149		if len(filepath.Join(ipcRoot, browserLaunchSocketName)) < devIPCSocketPathLimit {
150			return ipcRoot, nil
151		}
152	}
153
154	ipcRoot := filepath.Join(root, "runtime", engine)
155	socketPath := filepath.Join(ipcRoot, browserLaunchSocketName)
156	if len(socketPath) < devIPCSocketPathLimit {
157		return ipcRoot, nil
158	}
159	return "", fmt.Errorf(
160		"browser launch socket path too long: %d bytes; set XDG_RUNTIME_DIR or TMPDIR to a shorter runtime directory",
161		len(socketPath),
162	)
163}
164
165func devIPCRootFromBase(base, rootHash, engine string) string {
166	return filepath.Join(base, "dumber", "dev-"+rootHash, engine)
167}
168
169func devIPCRootHash(root string) string {
170	sum := sha256.Sum256([]byte(root))
171	return hex.EncodeToString(sum[:])[:12]
172}
173
174// normalizeEngine trims and lowercases the runtime engine name.
175// Supported engines are "webkit" and "cef"; any other value falls back to
176// the default "webkit" runtime namespace.
177func normalizeEngine(engine string) string {
178	switch normalized := strings.ToLower(strings.TrimSpace(engine)); normalized {
179	case "", engineWebKit:
180		return engineWebKit
181	case engineCEF:
182		return engineCEF
183	default:
184		return engineWebKit
185	}
186}
187
188func envValue(get func(string) string, key string) string {
189	if get == nil {
190		return ""
191	}
192	return get(key)
193}
194
195func cwdValue(get func() (string, error)) (string, error) {
196	if get == nil {
197		return "", fmt.Errorf("cwd getter is nil")
198	}
199	return get()
200}
201
202// CEFUserDataDir returns the resolved CEF root cache/user-data directory.
203func (p Profile) CEFUserDataDir() string {
204	if p.Mode == ModeDev {
205		return filepath.Join(p.EnginePaths.RootDir, "data")
206	}
207	return filepath.Join(p.Shared.DataDir, "cef_user_data")
208}
209
210// CEFLogFile returns the resolved default CEF runtime log path.
211func (p Profile) CEFLogFile() string {
212	return filepath.Join(p.EnginePaths.LogDir, "cef_runtime.log")
213}
214
215// WebKitDataDir returns the resolved WebKit website data directory.
216func (p Profile) WebKitDataDir() string {
217	if p.Mode == ModeDev {
218		return filepath.Join(p.EnginePaths.RootDir, "data")
219	}
220	return p.Shared.DataDir
221}
222
223// WebKitCacheDir returns the resolved WebKit cache directory.
224func (p Profile) WebKitCacheDir() string {
225	if p.Mode == ModeDev {
226		return filepath.Join(p.EnginePaths.RootDir, "cache")
227	}
228	// Keep the current prod layout under state for non-user-managed technical
229	// engine cache data; this intentionally preserves existing WebKit behavior.
230	return filepath.Join(p.Shared.StateDir, "webkit-cache")
231}

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