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