oficina / memoria Public

Clone
git clone https://amadan.net/oficina/memoria

Plain git — no account needed to clone.

Download

Download this file

1// Command memoria wires the app: resolve the platform's activation argv,
2// open the database, unseal nothing until the DEK is in hand, serve.
3package main
4
5import (
6 "log/slog"
7 "os"
8
9 "amadan.net/rastrillo/rastrillo"
10 "amadan.net/rastrillo/rastrillo/db"
11 "amadan.net/rastrillo/rastrillo/mail"
12
13 memoria "amadan.net/oficina/memoria/internal/memoria"
14)
15
16// LoadConfigFromEnv is LoadConfig against the real environment.
17func LoadConfigFromEnv() (memoria.Config, error) {
18 return memoria.LoadConfig(os.Getenv)
19}
20
21func main() {
22 logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
23
24 opts, err := rastrillo.Resolve(rastrillo.Options{DBPath: "memoria.db", Logger: logger})
25 if err != nil {
26 logger.Error("resolve activation", "err", err)
27 os.Exit(1)
28 }
29
30 // Every refusal in LoadConfig is a safety property, and it lives in
31 // a tested function rather than inline here — see config.go for the
32 // false-pass guard that made the case for moving it.
33 cfg, err := LoadConfigFromEnv()
34 if err != nil {
35 logger.Error("configuration", "err", err)
36 os.Exit(1)
37 }
38 if cfg.UsedDefaultOrigin() {
39 logger.Warn("MEMORIA_ORIGIN not set; defaulting", "origin", cfg.Origin)
40 }
41
42 d, err := db.Open(opts.DBPath, logger)
43 if err != nil {
44 logger.Error("open database", "err", err)
45 os.Exit(1)
46 }
47 defer d.Close()
48
49 mux, app, err := memoria.App(d, cfg.Origin, cfg.Sealer, logger)
50 if err != nil {
51 logger.Error("build app", "err", err)
52 os.Exit(1)
53 }
54
55 // Wire the mailer. Without this rastrillo substitutes mail.Logged,
56 // which writes the magic link — a live credential — into the process
57 // log. LoadConfig refuses to boot in that state unless asked
58 // explicitly, but refusing was only half the fix: nothing was
59 // building a real sender either.
60 //
61 // "CARLOS" is the prefix the suite uses, matching Sheets.
62 //
63 // The owners list is set FIRST, because app.Admit reads it: it is the
64 // one admission predicate, used by rastrillo at verification, by the
65 // sign-in guard before the classifier dials, and by requireOwner on
66 // the connector routes. Ordering it after NewAuth would work — Admit
67 // reads the list at call time — and would hide the dependency, which
68 // is how the two-copy version got here.
69 //
70 // In slice 0 the connector routes check the same list the admission
71 // gate uses, so every signed-in person is an owner. requireOwner says
72 // that out loud rather than implying a restriction the app does not
73 // yet have.
74 app.SetOwners(cfg.Owners)
75
76 a, err := memoria.NewAuth(d.Writer(), memoria.AuthEnv{
77 Origin: cfg.Origin,
78 InstanceKey: cfg.InstanceKey,
79 Owners: cfg.Owners,
80 Mailer: mail.FromEnv("CARLOS", logger),
81 HomeEnabled: cfg.Home != nil,
82 }, logger, app)
83 if err != nil {
84 logger.Error("wire sign-in", "err", err)
85 os.Exit(1)
86 }
87 app.UseAuth(a)
88 if err := app.WireHome(mux, cfg); err != nil {
89 logger.Error("Home sign-in unavailable")
90 os.Exit(1)
91 }
92 app.MountAuth(mux, cfg.Origin)
93
94 // The apps of record memoria may ask about passages (§4.1).
95 //
96 // One call, deliberately: the roster, the custody, the log line and
97 // the wake poll all live in WireSiblings, where they have tests. This
98 // file's guard is a gate over its own text, and a gate over text can
99 // only see that a call is spelled — a review put the same wiring
100 // behind `if len(apps) > 1` and inside an uninvoked closure, and both
101 // read as present. Logic that matters does not live here.
102 //
103 // Fatal on error, like every other refusal in this file: a
104 // half-configured roster does not fail loudly at query time, it
105 // serves a permanently short page blaming an app that is working
106 // fine. An EMPTY roster is not a failure.
107 //
108 // nil dial: the module's team-bound transport does its own host check
109 // before dialling. The hook is for tests.
110 // The model layer (§8.9): off unless MEMORIA_AI=on. One call; the logic
111 // and its tests are in WireModel.
112 if err := memoria.WireModel(app, cfg); err != nil {
113 logger.Error("wire model layer", "err", err)
114 os.Exit(1)
115 }
116
117 if err := memoria.WireSiblings(app, os.Getenv, nil); err != nil {
118 logger.Error("sibling roster", "err", err)
119 os.Exit(1)
120 }
121
122 // The router, the error page, and rastrillo's default CSP
123 // (memoria.ServeOptions says why there is no policy of our own).
124 opts = app.ServeOptions(opts, mux)
125 opts.DBPath = ""
126 if err := rastrillo.Serve(opts); err != nil {
127 logger.Error("serve failed", "err", err)
128 os.Exit(1)
129 }
130}
131