Go · 2758 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package web
4
5 import (
6 "errors"
7 "fmt"
8 "io/fs"
9 "log/slog"
10 "os"
11 "path/filepath"
12
13 "github.com/jackc/pgx/v5/pgxpool"
14
15 "github.com/tenseleyFlow/shithub/internal/auth/audit"
16 "github.com/tenseleyFlow/shithub/internal/auth/secretbox"
17 "github.com/tenseleyFlow/shithub/internal/auth/throttle"
18 "github.com/tenseleyFlow/shithub/internal/infra/config"
19 "github.com/tenseleyFlow/shithub/internal/infra/storage"
20 repoh "github.com/tenseleyFlow/shithub/internal/web/handlers/repo"
21 "github.com/tenseleyFlow/shithub/internal/web/render"
22 )
23
24 // buildRepoHandlers wires the repo-create + empty-home handlers. The
25 // bare repos live at cfg.Storage.ReposRoot (must be set; we refuse to
26 // boot the repo surface without it).
27 func buildRepoHandlers(
28 cfg config.Config,
29 pool *pgxpool.Pool,
30 objectStore storage.ObjectStore,
31 tmplFS fs.FS,
32 logger *slog.Logger,
33 ) (*repoh.Handlers, error) {
34 if cfg.Storage.ReposRoot == "" {
35 return nil, errors.New("repo: cfg.Storage.ReposRoot is empty")
36 }
37 root, err := filepath.Abs(cfg.Storage.ReposRoot)
38 if err != nil {
39 return nil, fmt.Errorf("repo: resolve repos_root: %w", err)
40 }
41 rfs, err := storage.NewRepoFS(root)
42 if err != nil {
43 return nil, fmt.Errorf("repo: NewRepoFS: %w", err)
44 }
45 rr, err := render.New(tmplFS, render.Options{Octicons: render.BuiltinOcticons()})
46 if err != nil {
47 return nil, fmt.Errorf("repo: render.New: %w", err)
48 }
49 // shithubdPath is the running binary, baked into hook shims by
50 // repos.Create. os.Executable can rarely fail (e.g. exec name
51 // stripped); when it does we fall back to "shithubd" on PATH so
52 // hook shims still resolve in unusual environments.
53 shithubdPath := "shithubd"
54 if exe, err := os.Executable(); err == nil {
55 if abs, err := filepath.Abs(exe); err == nil {
56 shithubdPath = abs
57 }
58 }
59
60 // Webhook secret box (S33). Reuses the TOTP key — they're both
61 // at-rest AEAD-wrapped secrets. nil-tolerant: if the key is
62 // missing/invalid the webhook surface renders a placeholder.
63 var hookBox *secretbox.Box
64 if cfg.Auth.TOTPKeyB64 != "" {
65 if box, err := secretbox.FromBase64(cfg.Auth.TOTPKeyB64); err == nil {
66 hookBox = box
67 } else if logger != nil {
68 logger.Warn("repo: webhook secretbox unavailable",
69 "hint", "set Auth.TOTPKeyB64 to a base64 32-byte key",
70 "error", err)
71 }
72 }
73
74 return repoh.New(repoh.Deps{
75 Logger: logger,
76 Render: rr,
77 Pool: pool,
78 RepoFS: rfs,
79 ObjectStore: objectStore,
80 Audit: audit.NewRecorder(),
81 Limiter: throttle.NewLimiter(),
82 SecretBox: hookBox,
83 ShithubdPath: shithubdPath,
84 CloneURLs: repoh.CloneURLs{
85 BaseURL: cfg.Auth.BaseURL,
86 SSHEnabled: cfg.Auth.SSH.Enabled,
87 SSHHost: cfg.Auth.SSH.Host,
88 },
89 })
90 }
91