This repository has no description
1//! weir: a minimal prefect server whose database is an atproto repo.
2//!
3//! boot: load (or mint) the workspace signing key, replay the repo CAR from
4//! disk, hydrate the appview, serve. env:
5//! WEIR_PORT default 4200
6//! WEIR_DATA_DIR default ./data
7
8const std = @import("std");
9const httpz = @import("httpz");
10
11const repo_mod = @import("repo.zig");
12const cache_mod = @import("cache.zig");
13const api_mod = @import("api.zig");
14
15const log = std.log.scoped(.main);
16
17pub fn main() !void {
18 const allocator = std.heap.smp_allocator;
19
20 var threaded = std.Io.Threaded.init(allocator, .{});
21 defer threaded.deinit();
22 const io = threaded.io();
23
24 const port: u16 = if (std.c.getenv("WEIR_PORT")) |p|
25 std.fmt.parseInt(u16, std.mem.span(p), 10) catch 4200
26 else
27 4200;
28 const data_dir: []const u8 = if (std.c.getenv("WEIR_DATA_DIR")) |d|
29 std.mem.span(d)
30 else
31 "data";
32
33 const repo = try repo_mod.Repo.boot(allocator, io, data_dir);
34 defer repo.deinit(allocator);
35
36 var cache: cache_mod.Cache = blk: {
37 const backend = if (std.c.getenv("WEIR_CACHE")) |c| std.mem.span(c) else "burner";
38 if (std.mem.eql(u8, backend, "redis")) {
39 const host = if (std.c.getenv("WEIR_REDIS_HOST")) |h| std.mem.span(h) else "127.0.0.1";
40 const redis_port: u16 = if (std.c.getenv("WEIR_REDIS_PORT")) |p|
41 std.fmt.parseInt(u16, std.mem.span(p), 10) catch 6379
42 else
43 6379;
44 log.info("appview backend: redis at {s}:{d}", .{ host, redis_port });
45 break :blk try cache_mod.Cache.initRedis(allocator, io, host, redis_port);
46 }
47 log.info("appview backend: burner-redis (embedded)", .{});
48 break :blk cache_mod.Cache.initBurner(allocator, io);
49 };
50 defer cache.deinit();
51
52 const commit_every: usize = if (std.c.getenv("WEIR_COMMIT_EVERY")) |c|
53 std.fmt.parseInt(usize, std.mem.span(c), 10) catch 1
54 else
55 1;
56
57 const ui_dir: ?[]const u8 = blk: {
58 const dir = if (std.c.getenv("WEIR_UI_DIR")) |d| std.mem.span(d) else "ui";
59 var check_buf: [512]u8 = undefined;
60 const index_path = std.fmt.bufPrint(&check_buf, "{s}/index.html", .{dir}) catch break :blk null;
61 var probe: [16]u8 = undefined;
62 _ = std.Io.Dir.readFile(.cwd(), io, index_path, &probe) catch break :blk null;
63 break :blk dir;
64 };
65
66 var api = api_mod.Api{
67 .allocator = allocator,
68 .io = io,
69 .repo = repo,
70 .cache = &cache,
71 .commit_every = commit_every,
72 .ui_dir = ui_dir,
73 };
74 if (ui_dir) |d| {
75 log.info("ui bundle: {s}/ (open http://127.0.0.1:{d})", .{ d, port });
76 } else {
77 log.info("no ui bundle (run `just fetch-ui` to serve the prefect ui)", .{});
78 }
79 if (commit_every > 1) log.info("commit cadence: every {d} writes", .{commit_every});
80
81 log.info("workspace did: {s}", .{repo.did});
82 log.info("repo: {d} records, data dir: {s}", .{ repo.record_count, data_dir });
83 if (repo.record_count > 0) {
84 var hydrate_arena = std.heap.ArenaAllocator.init(allocator);
85 defer hydrate_arena.deinit();
86 const applied = try api.hydrate(hydrate_arena.allocator());
87 log.info("appview hydrated: {d} records replayed from the repo", .{applied});
88 }
89
90 const ip = try std.Io.net.IpAddress.parse("127.0.0.1", port);
91 var server = try httpz.Server(*api_mod.Api).init(io, allocator, .{
92 .address = .{ .ip = ip },
93 .request = .{ .max_body_size = 16 * 1024 * 1024 },
94 }, &api);
95 defer server.deinit();
96
97 log.info("listening on http://127.0.0.1:{d} (api: /api, atproto: /xrpc)", .{port});
98 try server.listen();
99}
100
101test {
102 _ = @import("repo.zig");
103 _ = @import("cache.zig");
104 _ = @import("json_cbor.zig");
105 _ = @import("util.zig");
106 _ = @import("api.zig");
107}