main bac21c6

local_admin_socket.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"net/http"
8	"os"
9	"sync"
10	"time"
11
12	"github.com/bnema/zerowrap"
13
14	"github.com/bnema/gordon/internal/adapters/in/http/middleware"
15	"github.com/bnema/gordon/internal/adapters/localadmin"
16)
17
18// localAdminServer owns the daemon's owner-only admin Unix socket: one HTTP
19// listener, one local-only authority handler, and the socket file it created.
20type localAdminServer struct {
21	srv  *http.Server
22	path string
23	info os.FileInfo
24	log  zerowrap.Logger
25	once sync.Once
26}
27
28// startLocalAdminServer binds the owner-only admin socket, composes the
29// local-only authority over the admin handler, and serves it in the
30// background. Any startup error aborts daemon startup; a later serve error is
31// reported on errChan so the daemon fails rather than silently losing local
32// administration.
33func startLocalAdminServer(svc *services, errChan chan<- error, log zerowrap.Logger) (*localAdminServer, error) {
34	if svc == nil || svc.adminHandler == nil {
35		return nil, nil
36	}
37
38	dir, err := localadmin.EnsureRuntimeDir()
39	if err != nil {
40		return nil, fmt.Errorf("local admin socket directory: %w", err)
41	}
42	ln, info, err := localadmin.Listen(dir)
43	if err != nil {
44		return nil, fmt.Errorf("local admin socket: %w", err)
45	}
46
47	handler := middleware.Chain(
48		middleware.PanicRecovery(log),
49		middleware.SecurityHeaders,
50	)(svc.adminHandler.LocalAuthority())
51
52	// No WriteTimeout: app log streaming over the socket stays open for as long
53	// as the client follows it.
54	srv := &http.Server{
55		Handler:           handler,
56		ReadHeaderTimeout: 10 * time.Second,
57		IdleTimeout:       120 * time.Second,
58		MaxHeaderBytes:    1 << 20,
59	}
60
61	s := &localAdminServer{
62		srv:  srv,
63		path: localadmin.SocketPath(dir),
64		info: info,
65		log:  log,
66	}
67
68	go func() {
69		if serveErr := srv.Serve(ln); serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
70			wrapped := fmt.Errorf("local admin socket server: %w", serveErr)
71			select {
72			case errChan <- wrapped:
73			default:
74				log.Error().Err(serveErr).Msg("local admin socket server error")
75			}
76		}
77	}()
78
79	log.Info().Str("path", s.path).Msg("local admin socket listening")
80	return s, nil
81}
82
83// Close drains and stops the socket server, then removes the socket file only
84// when it is still the file this listener bound. It is safe to call twice.
85func (s *localAdminServer) Close() {
86	if s == nil {
87		return
88	}
89	s.once.Do(func() {
90		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
91		defer cancel()
92		if err := s.srv.Shutdown(ctx); err != nil {
93			s.log.Warn().Err(err).Msg("local admin socket shutdown error")
94			if closeErr := s.srv.Close(); closeErr != nil {
95				s.log.Warn().Err(closeErr).Msg("failed to force-close local admin socket")
96			}
97		}
98		if err := localadmin.RemoveOwnedSocket(s.path, s.info); err != nil {
99			s.log.Warn().Err(err).Str("path", s.path).Msg("failed to remove local admin socket")
100			return
101		}
102		s.log.Info().Msg("local admin socket stopped")
103	})
104}

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 7

Tags 106

↑↓ moveenter openesc close