forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
this repo has no description
fork
Configure Feed
Select the types of activity you want to include in your feed.
1{
2 config,
3 lib,
4 ...
5}: let
6 cfg = config.services.tangled.appview;
7in
8 with lib; {
9 options = {
10 services.tangled.appview = {
11 enable = mkOption {
12 type = types.bool;
13 default = false;
14 description = "Enable tangled appview";
15 };
16
17 package = mkOption {
18 type = types.package;
19 description = "Package to use for the appview";
20 };
21
22 # core configuration
23 port = mkOption {
24 type = types.port;
25 default = 3000;
26 description = "Port to run the appview on";
27 };
28
29 listenAddr = mkOption {
30 type = types.str;
31 default = "0.0.0.0:${toString cfg.port}";
32 description = "Listen address for the appview service";
33 };
34
35 dbPath = mkOption {
36 type = types.str;
37 default = "/var/lib/appview/appview.db";
38 description = "Path to the SQLite database file";
39 };
40
41 appviewHost = mkOption {
42 type = types.str;
43 default = "https://tangled.org";
44 example = "https://example.com";
45 description = "Public host URL for the appview instance";
46 };
47
48 appviewName = mkOption {
49 type = types.str;
50 default = "Tangled";
51 description = "Display name for the appview instance";
52 };
53
54 dev = mkOption {
55 type = types.bool;
56 default = false;
57 description = "Enable development mode";
58 };
59
60 disallowedNicknamesFile = mkOption {
61 type = types.nullOr types.path;
62 default = null;
63 description = "Path to file containing disallowed nicknames";
64 };
65
66 # redis configuration
67 redis = {
68 addr = mkOption {
69 type = types.str;
70 default = "localhost:6379";
71 description = "Redis server address";
72 };
73
74 db = mkOption {
75 type = types.int;
76 default = 0;
77 description = "Redis database number";
78 };
79 };
80
81 # jetstream configuration
82 jetstream = {
83 endpoint = mkOption {
84 type = types.str;
85 default = "wss://jetstream1.us-east.bsky.network/subscribe";
86 description = "Jetstream WebSocket endpoint";
87 };
88 };
89
90 # knotstream consumer configuration
91 knotstream = {
92 retryInterval = mkOption {
93 type = types.str;
94 default = "60s";
95 description = "Initial retry interval for knotstream consumer";
96 };
97
98 maxRetryInterval = mkOption {
99 type = types.str;
100 default = "120m";
101 description = "Maximum retry interval for knotstream consumer";
102 };
103
104 connectionTimeout = mkOption {
105 type = types.str;
106 default = "5s";
107 description = "Connection timeout for knotstream consumer";
108 };
109
110 workerCount = mkOption {
111 type = types.int;
112 default = 64;
113 description = "Number of workers for knotstream consumer";
114 };
115
116 queueSize = mkOption {
117 type = types.int;
118 default = 100;
119 description = "Queue size for knotstream consumer";
120 };
121 };
122
123 # spindlestream consumer configuration
124 spindlestream = {
125 retryInterval = mkOption {
126 type = types.str;
127 default = "60s";
128 description = "Initial retry interval for spindlestream consumer";
129 };
130
131 maxRetryInterval = mkOption {
132 type = types.str;
133 default = "120m";
134 description = "Maximum retry interval for spindlestream consumer";
135 };
136
137 connectionTimeout = mkOption {
138 type = types.str;
139 default = "5s";
140 description = "Connection timeout for spindlestream consumer";
141 };
142
143 workerCount = mkOption {
144 type = types.int;
145 default = 64;
146 description = "Number of workers for spindlestream consumer";
147 };
148
149 queueSize = mkOption {
150 type = types.int;
151 default = 100;
152 description = "Queue size for spindlestream consumer";
153 };
154 };
155
156 # resend configuration
157 resend = {
158 sentFrom = mkOption {
159 type = types.str;
160 default = "noreply@notifs.tangled.sh";
161 description = "Email address to send notifications from";
162 };
163 };
164
165 # posthog configuration
166 posthog = {
167 endpoint = mkOption {
168 type = types.str;
169 default = "https://eu.i.posthog.com";
170 description = "PostHog API endpoint";
171 };
172 };
173
174 # camo configuration
175 camo = {
176 host = mkOption {
177 type = types.str;
178 default = "https://camo.tangled.sh";
179 description = "Camo proxy host URL";
180 };
181 };
182
183 # avatar configuration
184 avatar = {
185 host = mkOption {
186 type = types.str;
187 default = "https://avatar.tangled.sh";
188 description = "Avatar service host URL";
189 };
190 };
191
192 plc = {
193 url = mkOption {
194 type = types.str;
195 default = "https://plc.directory";
196 description = "PLC directory URL";
197 };
198 };
199
200 pds = {
201 host = mkOption {
202 type = types.str;
203 default = "https://tngl.sh";
204 description = "PDS host URL";
205 };
206 };
207
208 label = {
209 defaults = mkOption {
210 type = types.listOf types.str;
211 default = [
212 "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/wontfix"
213 "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/good-first-issue"
214 "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/duplicate"
215 "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/documentation"
216 "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/assignee"
217 ];
218 description = "Default label definitions";
219 };
220
221 goodFirstIssue = mkOption {
222 type = types.str;
223 default = "at://did:plc:wshs7t2adsemcrrd4snkeqli/sh.tangled.label.definition/good-first-issue";
224 description = "Good first issue label definition";
225 };
226 };
227
228 environmentFile = mkOption {
229 type = with types; nullOr path;
230 default = null;
231 example = "/etc/appview.env";
232 description = ''
233 Additional environment file as defined in {manpage}`systemd.exec(5)`.
234
235 Sensitive secrets such as {env}`TANGLED_COOKIE_SECRET`,
236 {env}`TANGLED_OAUTH_CLIENT_SECRET`, {env}`TANGLED_RESEND_API_KEY`,
237 {env}`TANGLED_CAMO_SHARED_SECRET`, {env}`TANGLED_AVATAR_SHARED_SECRET`,
238 {env}`TANGLED_REDIS_PASS`, {env}`TANGLED_PDS_ADMIN_SECRET`,
239 {env}`TANGLED_CLOUDFLARE_API_TOKEN`, {env}`TANGLED_CLOUDFLARE_ZONE_ID`,
240 {env}`TANGLED_CLOUDFLARE_TURNSTILE_SITE_KEY`,
241 {env}`TANGLED_CLOUDFLARE_TURNSTILE_SECRET_KEY`,
242 {env}`TANGLED_POSTHOG_API_KEY`, {env}`TANGLED_APP_PASSWORD`,
243 and {env}`TANGLED_ALT_APP_PASSWORD` may be passed to the service
244 without making them world readable in the nix store.
245 '';
246 };
247 };
248 };
249
250 config = mkIf cfg.enable {
251 services.redis.servers.appview = {
252 enable = true;
253 port = 6379;
254 };
255
256 systemd.services.appview = {
257 description = "tangled appview service";
258 wantedBy = ["multi-user.target"];
259 after = ["redis-appview.service" "network-online.target"];
260 requires = ["redis-appview.service"];
261 wants = ["network-online.target"];
262
263 serviceConfig = {
264 Type = "simple";
265 ExecStart = "${cfg.package}/bin/appview";
266 Restart = "always";
267 RestartSec = "10s";
268 EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
269
270 # state directory
271 StateDirectory = "appview";
272 WorkingDirectory = "/var/lib/appview";
273
274 # security hardening
275 NoNewPrivileges = true;
276 PrivateTmp = true;
277 ProtectSystem = "strict";
278 ProtectHome = true;
279 ReadWritePaths = ["/var/lib/appview"];
280 };
281
282 environment =
283 {
284 TANGLED_DB_PATH = cfg.dbPath;
285 TANGLED_LISTEN_ADDR = cfg.listenAddr;
286 TANGLED_APPVIEW_HOST = cfg.appviewHost;
287 TANGLED_APPVIEW_NAME = cfg.appviewName;
288 TANGLED_DEV =
289 if cfg.dev
290 then "true"
291 else "false";
292 }
293 // optionalAttrs (cfg.disallowedNicknamesFile != null) {
294 TANGLED_DISALLOWED_NICKNAMES_FILE = cfg.disallowedNicknamesFile;
295 }
296 // {
297 TANGLED_REDIS_ADDR = cfg.redis.addr;
298 TANGLED_REDIS_DB = toString cfg.redis.db;
299
300 TANGLED_JETSTREAM_ENDPOINT = cfg.jetstream.endpoint;
301
302 TANGLED_KNOTSTREAM_RETRY_INTERVAL = cfg.knotstream.retryInterval;
303 TANGLED_KNOTSTREAM_MAX_RETRY_INTERVAL = cfg.knotstream.maxRetryInterval;
304 TANGLED_KNOTSTREAM_CONNECTION_TIMEOUT = cfg.knotstream.connectionTimeout;
305 TANGLED_KNOTSTREAM_WORKER_COUNT = toString cfg.knotstream.workerCount;
306 TANGLED_KNOTSTREAM_QUEUE_SIZE = toString cfg.knotstream.queueSize;
307
308 TANGLED_SPINDLESTREAM_RETRY_INTERVAL = cfg.spindlestream.retryInterval;
309 TANGLED_SPINDLESTREAM_MAX_RETRY_INTERVAL = cfg.spindlestream.maxRetryInterval;
310 TANGLED_SPINDLESTREAM_CONNECTION_TIMEOUT = cfg.spindlestream.connectionTimeout;
311 TANGLED_SPINDLESTREAM_WORKER_COUNT = toString cfg.spindlestream.workerCount;
312 TANGLED_SPINDLESTREAM_QUEUE_SIZE = toString cfg.spindlestream.queueSize;
313
314 TANGLED_RESEND_SENT_FROM = cfg.resend.sentFrom;
315
316 TANGLED_POSTHOG_ENDPOINT = cfg.posthog.endpoint;
317
318 TANGLED_CAMO_HOST = cfg.camo.host;
319
320 TANGLED_AVATAR_HOST = cfg.avatar.host;
321
322 TANGLED_PLC_URL = cfg.plc.url;
323
324 TANGLED_PDS_HOST = cfg.pds.host;
325
326 TANGLED_LABEL_DEFAULTS = concatStringsSep "," cfg.label.defaults;
327 TANGLED_LABEL_GFI = cfg.label.goodFirstIssue;
328 };
329 };
330 };
331 }