forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 11 kB view raw
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 }