refactor/kvconf 3421f16

config.go RawHistoryFolder

1package ports
2
3import (
4	"fmt"
5	"strings"
6	"time"
7	"unicode"
8	"unicode/utf8"
9)
10
11const MaxDuration = 24 * time.Hour
12
13// Zero After disables only that action. No deadline implies another action.
14//
15// The kv tags describe the config file keys (decoded by adapters/config). A
16// zero duration is written "off", except fade.duration where 0s is a value.
17// max:"24h" mirrors MaxDuration (kept for the line number; Validate also checks).
18type Config struct {
19	LockAfter    time.Duration `kv:"lock.after,off" min:"1ns" max:"24h"`
20	FadeAfter    time.Duration `kv:"fade.after,off" min:"1ns" max:"24h"`
21	OffAfter     time.Duration `kv:"screens.off-after,off" min:"1ns" max:"24h"`
22	SleepAfter   time.Duration `kv:"sleep.after,off" min:"1ns" max:"24h"`
23	FadeDuration time.Duration `kv:"fade.duration" min:"0s" max:"24h"`
24	Auth         AuthConfig    `kv:"auth"`
25	// LockOutput names the only output (wl_output name, e.g. DP-2) that
26	// shows the lock prompt while it is connected. Empty, or absent: every
27	// output shows it. A change applies from the next lock.
28	LockOutput string `kv:"lock.output,nonempty"`
29}
30
31// MaxOutputName bounds LockOutput: neferclient truncates longer wl_output
32// names, which could never match.
33const MaxOutputName = 64
34
35type AuthMode string
36
37const (
38	AuthModePassword AuthMode = "password"
39	AuthModePIN      AuthMode = "pin"
40)
41
42type PINSource string
43
44const (
45	PINSourceEnv  PINSource = "env"
46	PINSourcePass PINSource = "pass"
47)
48
49// Only metadata: values resolved by the visual/auth worker never reach policy.
50// PIN mode always falls back to PAM when its selected source is unavailable or
51// invalid. A wrong PIN is authentication failure, never a fallback trigger.
52type AuthConfig struct {
53	Mode      AuthMode  `kv:"mode" oneof:"password|pin"`
54	PINSource PINSource `kv:"pin-source" oneof:"env|pass"`
55	PINEnv    string    `kv:"pin-env,nonempty"`
56	PINEntry  string    `kv:"pin-entry,nonempty"`
57}
58
59func Defaults() Config {
60	return Config{LockAfter: 5 * time.Minute, FadeAfter: 6 * time.Minute, FadeDuration: 2 * time.Second, OffAfter: 7 * time.Minute, SleepAfter: 20 * time.Minute, Auth: AuthConfig{Mode: AuthModePassword}}
61}
62func (c Config) Validate() error {
63	for _, d := range []time.Duration{c.LockAfter, c.FadeAfter, c.OffAfter, c.SleepAfter, c.FadeDuration} {
64		if d < 0 || d > MaxDuration {
65			return fmt.Errorf("duration outside 0..24h")
66		}
67	}
68	if len(c.LockOutput) > MaxOutputName || !utf8.ValidString(c.LockOutput) || strings.ContainsFunc(c.LockOutput, unicode.IsControl) {
69		return fmt.Errorf("invalid lock output name")
70	}
71	a := c.Auth
72	for _, r := range []string{string(a.Mode), string(a.PINSource), a.PINEnv, a.PINEntry} {
73		if !utf8.ValidString(r) {
74			return fmt.Errorf("auth metadata is not valid UTF-8")
75		}
76	}
77	if a.Mode != AuthModePassword && a.Mode != AuthModePIN {
78		return fmt.Errorf("unknown auth mode")
79	}
80	for _, r := range []string{a.PINEnv, a.PINEntry} {
81		if len(r) > 4096 {
82			return fmt.Errorf("auth metadata too large")
83		}
84		for _, c := range r {
85			if unicode.IsControl(c) {
86				return fmt.Errorf("control character in auth metadata")
87			}
88		}
89	}
90	if a.PINEnv != "" {
91		for i, r := range a.PINEnv {
92			if !(r == '_' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || i > 0 && r >= '0' && r <= '9') {
93				return fmt.Errorf("invalid environment reference")
94			}
95		}
96	}
97	if a.PINEntry != "" {
98		if strings.HasPrefix(a.PINEntry, "-") || strings.HasPrefix(a.PINEntry, "/") || strings.Contains(a.PINEntry, "\\") {
99			return fmt.Errorf("invalid pass entry")
100		}
101		for _, part := range strings.Split(a.PINEntry, "/") {
102			if part == "" || part == "." || part == ".." {
103				return fmt.Errorf("invalid pass entry")
104			}
105		}
106	}
107	switch a.PINSource {
108	case "":
109		if a.Mode == AuthModePIN || a.PINEnv != "" || a.PINEntry != "" {
110			return fmt.Errorf("PIN metadata requires source")
111		}
112	case PINSourceEnv:
113		if a.PINEnv == "" || a.PINEntry != "" {
114			return fmt.Errorf("env source requires only pin-env")
115		}
116	case PINSourcePass:
117		if a.PINEntry == "" || a.PINEnv != "" {
118			return fmt.Errorf("pass source requires only pin-entry")
119		}
120	default:
121		return fmt.Errorf("unknown PIN source")
122	}
123	return nil
124}
125
126type ConfigChanged struct{ Config Config }

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 2

Tags 9

↑↓ moveenter openesc close