v0.1.0 7deb448

proxy_registry.go RawHistoryFolder

1package dgram
2
3import (
4	"crypto/sha256"
5	"encoding/base64"
6	"encoding/json"
7	"errors"
8	"os"
9	"path/filepath"
10	"syscall"
11	"time"
12
13	"github.com/bnema/vev/pkg/safedir"
14)
15
16type ProxyRecord struct {
17	Session string `json:"session"`
18	PID     int    `json:"pid"`
19	Port    int    `json:"port"`
20	// KeyFingerprint is a non-secret ownership tag (SHA-256 of the AEAD key).
21	// The raw key must never be persisted by vev.
22	KeyFingerprint string    `json:"key_fingerprint"`
23	Created        time.Time `json:"created"`
24}
25
26// KeyFingerprint derives the non-secret registry ownership tag.
27func KeyFingerprint(key []byte) string {
28	sum := sha256.Sum256(key)
29	return base64.StdEncoding.EncodeToString(sum[:])
30}
31
32type ProxyRegistry struct{ dir string }
33
34func NewProxyRegistry(dir string) *ProxyRegistry { return &ProxyRegistry{dir: dir} }
35
36func (r *ProxyRegistry) Lookup(session string) (ProxyRecord, bool) {
37	var rec ProxyRecord
38	ok := false
39	_ = r.withLock(func() error {
40		var err error
41		rec, err = r.read(session)
42		if err != nil {
43			return nil
44		}
45		if !processAlive(rec.PID) {
46			_ = r.removeLocked(session)
47			return nil
48		}
49		if rec.PID != os.Getpid() {
50			return nil
51		}
52		ok = true
53		return nil
54	})
55	return rec, ok
56}
57
58func (r *ProxyRegistry) Publish(rec ProxyRecord) error {
59	if rec.Created.IsZero() {
60		rec.Created = time.Now()
61	}
62	return r.withLock(func() error {
63		b, err := json.Marshal(rec)
64		if err != nil {
65			return err
66		}
67		tmp, err := os.CreateTemp(r.dir, ".udp-proxy-*.tmp")
68		if err != nil {
69			return err
70		}
71		tmpName := tmp.Name()
72		defer func() { _ = os.Remove(tmpName) }()
73		if _, err := tmp.Write(b); err != nil {
74			_ = tmp.Close()
75			return err
76		}
77		if err := tmp.Close(); err != nil {
78			return err
79		}
80		return os.Rename(tmpName, r.path(rec.Session))
81	})
82}
83
84func (r *ProxyRegistry) Remove(session string) error {
85	return r.withLock(func() error { return r.removeLocked(session) })
86}
87
88func (r *ProxyRegistry) RemoveOwned(rec ProxyRecord) error {
89	return r.withLock(func() error {
90		cur, err := r.read(rec.Session)
91		if errors.Is(err, os.ErrNotExist) {
92			return nil
93		}
94		if err != nil {
95			return err
96		}
97		if cur.PID != rec.PID || cur.Port != rec.Port || cur.KeyFingerprint != rec.KeyFingerprint {
98			return nil
99		}
100		return r.removeLocked(rec.Session)
101	})
102}
103
104func (r *ProxyRegistry) withLock(fn func() error) error {
105	if err := safedir.EnsurePrivate(r.dir); err != nil {
106		return err
107	}
108	lock, err := os.OpenFile(filepath.Join(r.dir, ".lock"), os.O_CREATE|os.O_RDWR, 0o600)
109	if err != nil {
110		return err
111	}
112	defer func() { _ = lock.Close() }()
113	if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
114		return err
115	}
116	defer func() { _ = syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) }()
117	return fn()
118}
119
120func (r *ProxyRegistry) removeLocked(session string) error {
121	err := os.Remove(r.path(session))
122	if errors.Is(err, os.ErrNotExist) {
123		return nil
124	}
125	return err
126}
127
128func (r *ProxyRegistry) read(session string) (ProxyRecord, error) {
129	b, err := os.ReadFile(r.path(session))
130	if err != nil {
131		return ProxyRecord{}, err
132	}
133	var rec ProxyRecord
134	if err := json.Unmarshal(b, &rec); err != nil {
135		return ProxyRecord{}, err
136	}
137	return rec, nil
138}
139
140func (r *ProxyRegistry) path(session string) string {
141	if session == "" {
142		return filepath.Join(r.dir, "udp-proxy-empty.json")
143	}
144	name := base64.RawURLEncoding.EncodeToString([]byte(session))
145	return filepath.Join(r.dir, "udp-proxy-"+name+".json")
146}
147
148func processAlive(pid int) bool {
149	if pid <= 0 {
150		return false
151	}
152	p, err := os.FindProcess(pid)
153	if err != nil {
154		return false
155	}
156	err = p.Signal(syscall.Signal(0))
157	return err == nil || errors.Is(err, syscall.EPERM)
158}

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 12

Tags 11

↑↓ moveenter openesc close