v0.4.2 257b319

archives.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3package cachesvc
4
5import (
6	"crypto/hmac"
7	"errors"
8	"net/http"
9	"net/url"
10	"strconv"
11	"time"
12
13	"bnema.dev/ermite-runner/internal/actionscache"
14)
15
16// ArchivePath is the prefix of the action archive route: the host downloads the
17// archives of actions into its own cache (package actionscache) and the sandbox
18// reads them here instead of from the forge. It is not part of the toolkit's
19// protocol.
20const ArchivePath = "/_ermite/action-archives/"
21
22// ArchiveURL is the URL, as the guest sees it (its loopback, DefaultPort), from
23// which the job can read the archive of key for ttl. Like the toolkit's archive
24// URLs the signature is the credential: it is bound to the key, the job and the
25// expiry, so a URL that left its sandbox is useless on any other channel, and a
26// job can read only the archives the host resolved for it. It is "" when the
27// service holds no archive cache.
28func (s *Service) ArchiveURL(job, key string, ttl time.Duration) string {
29	if s.archives == nil || !actionscache.ValidKey(key) {
30		return ""
31	}
32	exp := s.now().Add(ttl).Unix()
33	q := url.Values{"job": {job}, "exp": {strconv.FormatInt(exp, 10)}, "sig": {b64.EncodeToString(s.sign.mac("action-archive", []byte(key), []byte(job), []byte(strconv.FormatInt(exp, 10))))}}
34	return "http://127.0.0.1:" + strconv.Itoa(DefaultPort) + ArchivePath + key + "?" + q.Encode()
35}
36
37func (s *Service) archiveOK(key, job string, exp int64, sig string) bool {
38	got, err := b64.DecodeString(sig)
39	if err != nil || !s.now().Before(time.Unix(exp, 0)) {
40		return false
41	}
42	return hmac.Equal(got, s.sign.mac("action-archive", []byte(key), []byte(job), []byte(strconv.FormatInt(exp, 10))))
43}
44
45func (s *Service) serveActionArchive(channelJob string) http.HandlerFunc {
46	return func(w http.ResponseWriter, r *http.Request) {
47		key := r.PathValue("key")
48		q := r.URL.Query()
49		exp, err := strconv.ParseInt(q.Get("exp"), 10, 64)
50		job := q.Get("job")
51		if s.archives == nil || err != nil || job == "" || (channelJob != "" && job != channelJob) || !actionscache.ValidKey(key) || !s.archiveOK(key, job, exp, q.Get("sig")) {
52			reply(w, http.StatusForbidden, "forbidden")
53			return
54		}
55		f, err := s.archives.Open(key)
56		if err != nil {
57			if !errors.Is(err, actionscache.ErrNotFound) {
58				s.log.Warn().Err(err).Msg("action archive")
59			}
60			reply(w, http.StatusNotFound, "no such archive")
61			return
62		}
63		defer func() { _ = f.Close() }()
64		rc := http.NewResponseController(w)
65		remaining := s.remaining(exp)
66		defer func() { _ = rc.SetWriteDeadline(deadline(s.progress, remaining)) }()
67		w.Header().Set("Content-Type", "application/gzip")
68		http.ServeContent(&progressWriter{ResponseWriter: w, rc: rc, idle: s.progress, remaining: remaining}, r, "", time.Time{}, f)
69	}
70}

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 4

Tags 11

↑↓ moveenter openesc close