@muhammedaksam/easiarr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,868 @@
1
+ /**
2
+ * App Registry
3
+ * Definitions for all supported *arr ecosystem applications
4
+ * Based on mediastack and TRaSH Guides configurations
5
+ */
6
+
7
+ import type { AppDefinition, AppId } from "../config/schema"
8
+
9
+ export const APPS: Record<AppId, AppDefinition> = {
10
+ // === SERVARR (Media Management) ===
11
+ radarr: {
12
+ id: "radarr",
13
+ name: "Radarr",
14
+ description: "Movie collection manager",
15
+ category: "servarr",
16
+ defaultPort: 7878,
17
+ image: "lscr.io/linuxserver/radarr:latest",
18
+ puid: 13002,
19
+ pgid: 13000,
20
+ volumes: (root) => [`${root}/config/radarr:/config`, `${root}/data:/data`],
21
+
22
+ trashGuide: "docs/Radarr/",
23
+ apiKeyMeta: {
24
+ configFile: "config.xml",
25
+ parser: "regex",
26
+ selector: "<ApiKey>(.*?)</ApiKey>",
27
+ },
28
+ rootFolder: {
29
+ path: "/data/media/movies",
30
+ apiVersion: "v3",
31
+ },
32
+ },
33
+
34
+ sonarr: {
35
+ id: "sonarr",
36
+ name: "Sonarr",
37
+ description: "TV series collection manager",
38
+ category: "servarr",
39
+ defaultPort: 8989,
40
+ image: "lscr.io/linuxserver/sonarr:latest",
41
+ puid: 13001,
42
+ pgid: 13000,
43
+ volumes: (root) => [`${root}/config/sonarr:/config`, `${root}/data:/data`],
44
+
45
+ trashGuide: "docs/Sonarr/",
46
+ apiKeyMeta: {
47
+ configFile: "config.xml",
48
+ parser: "regex",
49
+ selector: "<ApiKey>(.*?)</ApiKey>",
50
+ },
51
+ rootFolder: {
52
+ path: "/data/media/tv",
53
+ apiVersion: "v3",
54
+ },
55
+ },
56
+
57
+ lidarr: {
58
+ id: "lidarr",
59
+ name: "Lidarr",
60
+ description: "Music collection manager",
61
+ category: "servarr",
62
+ defaultPort: 8686,
63
+ image: "lscr.io/linuxserver/lidarr:latest",
64
+ puid: 13003,
65
+ pgid: 13000,
66
+ volumes: (root) => [`${root}/config/lidarr:/config`, `${root}/data:/data`],
67
+ apiKeyMeta: {
68
+ configFile: "config.xml",
69
+ parser: "regex",
70
+ selector: "<ApiKey>(.*?)</ApiKey>",
71
+ },
72
+ rootFolder: {
73
+ path: "/data/media/music",
74
+ apiVersion: "v1",
75
+ },
76
+ },
77
+
78
+ readarr: {
79
+ id: "readarr",
80
+ name: "Readarr",
81
+ description: "Book collection manager",
82
+ category: "servarr",
83
+ defaultPort: 8787,
84
+ image: "lscr.io/linuxserver/readarr:develop",
85
+ puid: 13004,
86
+ pgid: 13000,
87
+ volumes: (root) => [`${root}/config/readarr:/config`, `${root}/data:/data`],
88
+ apiKeyMeta: {
89
+ configFile: "config.xml",
90
+ parser: "regex",
91
+ selector: "<ApiKey>(.*?)</ApiKey>",
92
+ },
93
+ rootFolder: {
94
+ path: "/data/media/books",
95
+ apiVersion: "v1",
96
+ },
97
+ },
98
+
99
+ bazarr: {
100
+ id: "bazarr",
101
+ name: "Bazarr",
102
+ description: "Subtitle manager for Sonarr/Radarr",
103
+ category: "servarr",
104
+ defaultPort: 6767,
105
+ image: "lscr.io/linuxserver/bazarr:latest",
106
+ puid: 13013,
107
+ pgid: 13000,
108
+ volumes: (root) => [`${root}/config/bazarr:/config`, `${root}/data/media:/media`],
109
+ dependsOn: ["sonarr", "radarr"],
110
+ trashGuide: "docs/Bazarr/",
111
+ apiKeyMeta: {
112
+ configFile: "config/config.ini",
113
+ parser: "regex",
114
+ selector: "apikey\\s*=\\s*(.+)",
115
+ },
116
+ },
117
+
118
+ mylar3: {
119
+ id: "mylar3",
120
+ name: "Mylar3",
121
+ description: "Comic book collection manager",
122
+ category: "servarr",
123
+ defaultPort: 8090,
124
+ image: "lscr.io/linuxserver/mylar3:latest",
125
+ puid: 13005,
126
+ pgid: 13000,
127
+ volumes: (root) => [`${root}/config/mylar3:/config`, `${root}/data:/data`],
128
+ apiKeyMeta: {
129
+ configFile: "mylar/config.ini",
130
+ parser: "regex",
131
+ selector: "api_key\\s*=\\s*(.+)",
132
+ },
133
+ },
134
+
135
+ whisparr: {
136
+ id: "whisparr",
137
+ name: "Whisparr",
138
+ description: "Adult media collection manager",
139
+ category: "servarr",
140
+ defaultPort: 6969,
141
+ image: "hotio/whisparr:nightly",
142
+ puid: 13015,
143
+ pgid: 13000,
144
+ volumes: (root) => [`${root}/config/whisparr:/config`, `${root}/data:/data`],
145
+ apiKeyMeta: {
146
+ configFile: "config.xml",
147
+ parser: "regex",
148
+ selector: "<ApiKey>(.*?)</ApiKey>",
149
+ },
150
+ rootFolder: {
151
+ path: "/data/media/adult",
152
+ apiVersion: "v3",
153
+ },
154
+ },
155
+
156
+ audiobookshelf: {
157
+ id: "audiobookshelf",
158
+ name: "Audiobookshelf",
159
+ description: "Audiobook and podcast server",
160
+ category: "servarr",
161
+ defaultPort: 13378,
162
+ image: "ghcr.io/advplyr/audiobookshelf:latest",
163
+ puid: 13014,
164
+ pgid: 13000,
165
+ volumes: (root) => [
166
+ `${root}/config/audiobookshelf:/config`,
167
+ `${root}/data/media/audiobooks:/audiobooks`,
168
+ `${root}/data/media/podcasts:/podcasts`,
169
+ `${root}/data/media/audiobookshelf-metadata:/metadata`,
170
+ ],
171
+ },
172
+
173
+ // === INDEXERS ===
174
+ prowlarr: {
175
+ id: "prowlarr",
176
+ name: "Prowlarr",
177
+ description: "Indexer manager for *arr apps",
178
+ category: "indexer",
179
+ defaultPort: 9696,
180
+ image: "lscr.io/linuxserver/prowlarr:develop",
181
+ puid: 13006,
182
+ pgid: 13000,
183
+ volumes: (root) => [`${root}/config/prowlarr:/config`],
184
+
185
+ trashGuide: "docs/Prowlarr/",
186
+ apiKeyMeta: {
187
+ configFile: "config.xml",
188
+ parser: "regex",
189
+ selector: "<ApiKey>(.*?)</ApiKey>",
190
+ },
191
+ },
192
+
193
+ jackett: {
194
+ id: "jackett",
195
+ name: "Jackett",
196
+ description: "Alternative indexer manager",
197
+ category: "indexer",
198
+ defaultPort: 9117,
199
+ image: "lscr.io/linuxserver/jackett:latest",
200
+ puid: 13008,
201
+ pgid: 13000,
202
+ volumes: (root) => [`${root}/config/jackett:/config`],
203
+ apiKeyMeta: {
204
+ configFile: "Jackett/ServerConfig.json",
205
+ parser: "json",
206
+ selector: "APIKey",
207
+ },
208
+ },
209
+
210
+ flaresolverr: {
211
+ id: "flaresolverr",
212
+ name: "FlareSolverr",
213
+ description: "Cloudflare bypass proxy",
214
+ category: "indexer",
215
+ defaultPort: 8191,
216
+ image: "ghcr.io/flaresolverr/flaresolverr:latest",
217
+ puid: 0,
218
+ pgid: 0,
219
+ volumes: () => [],
220
+ environment: {
221
+ LOG_LEVEL: "info",
222
+ LOG_HTML: "false",
223
+ CAPTCHA_SOLVER: "none",
224
+ },
225
+ },
226
+
227
+ // === DOWNLOAD CLIENTS ===
228
+ qbittorrent: {
229
+ id: "qbittorrent",
230
+ name: "qBittorrent",
231
+ description: "BitTorrent client",
232
+ category: "downloader",
233
+ defaultPort: 8080,
234
+ image: "lscr.io/linuxserver/qbittorrent:latest",
235
+ puid: 13007,
236
+ pgid: 13000,
237
+ volumes: (root) => [`${root}/config/qbittorrent:/config`, `${root}/data/torrents:/data/torrents`],
238
+ environment: { WEBUI_PORT: "8080" },
239
+ trashGuide: "docs/Downloaders/qBittorrent/",
240
+ },
241
+
242
+ sabnzbd: {
243
+ id: "sabnzbd",
244
+ name: "SABnzbd",
245
+ description: "Usenet downloader",
246
+ category: "downloader",
247
+ defaultPort: 8081,
248
+ image: "lscr.io/linuxserver/sabnzbd:latest",
249
+ puid: 13011,
250
+ pgid: 13000,
251
+ volumes: (root) => [`${root}/config/sabnzbd:/config`, `${root}/data/usenet:/data/usenet`],
252
+
253
+ trashGuide: "docs/Downloaders/SABnzbd/",
254
+ apiKeyMeta: {
255
+ configFile: "sabnzbd.ini",
256
+ parser: "regex",
257
+ selector: "api_key\\s*=\\s*(.+)",
258
+ },
259
+ },
260
+
261
+ // === MEDIA SERVERS ===
262
+ plex: {
263
+ id: "plex",
264
+ name: "Plex",
265
+ description: "Media server with streaming",
266
+ category: "mediaserver",
267
+ defaultPort: 32400,
268
+ image: "lscr.io/linuxserver/plex:latest",
269
+ puid: 13010,
270
+ pgid: 13000,
271
+ volumes: (root) => [`${root}/config/plex:/config`, `${root}/data/media:/media`],
272
+ environment: { VERSION: "docker" },
273
+ trashGuide: "docs/Plex/",
274
+ apiKeyMeta: {
275
+ configFile: "Library/Application Support/Plex Media Server/Preferences.xml",
276
+ parser: "regex",
277
+ selector: 'PlexOnlineToken="([^"]+)"',
278
+ },
279
+ },
280
+
281
+ jellyfin: {
282
+ id: "jellyfin",
283
+ name: "Jellyfin",
284
+ description: "Free open-source media server",
285
+ category: "mediaserver",
286
+ defaultPort: 8096,
287
+ image: "lscr.io/linuxserver/jellyfin:latest",
288
+ puid: 0,
289
+ pgid: 13000,
290
+ volumes: (root) => [`${root}/config/jellyfin:/config`, `${root}/data/media:/data/media`],
291
+ },
292
+
293
+ tautulli: {
294
+ id: "tautulli",
295
+ name: "Tautulli",
296
+ description: "Plex monitoring and statistics",
297
+ category: "mediaserver",
298
+ defaultPort: 8181,
299
+ image: "lscr.io/linuxserver/tautulli:latest",
300
+ puid: 0,
301
+ pgid: 13000,
302
+ volumes: (root) => [`${root}/config/tautulli:/config`],
303
+ dependsOn: ["plex"],
304
+ apiKeyMeta: {
305
+ configFile: "config.ini",
306
+ parser: "regex",
307
+ selector: "api_key\\s*=\\s*(.+)",
308
+ },
309
+ },
310
+
311
+ tdarr: {
312
+ id: "tdarr",
313
+ name: "Tdarr",
314
+ description: "Audio/video transcoding automation",
315
+ category: "mediaserver",
316
+ defaultPort: 8265,
317
+ image: "ghcr.io/haveagitgat/tdarr:latest",
318
+ puid: 0,
319
+ pgid: 13000,
320
+ volumes: (root) => [
321
+ `${root}/config/tdarr/server:/app/server`,
322
+ `${root}/config/tdarr/configs:/app/configs`,
323
+ `${root}/config/tdarr/logs:/app/logs`,
324
+ `${root}/data/media:/data`,
325
+ ],
326
+ environment: { serverIP: "0.0.0.0", internalNode: "true" },
327
+ },
328
+
329
+ // === REQUEST MANAGEMENT ===
330
+ overseerr: {
331
+ id: "overseerr",
332
+ name: "Overseerr",
333
+ description: "Request management for Plex",
334
+ category: "request",
335
+ defaultPort: 5055,
336
+ image: "sctx/overseerr:latest",
337
+ puid: 13009,
338
+ pgid: 13000,
339
+ volumes: (root) => [`${root}/config/overseerr:/app/config`],
340
+ dependsOn: ["plex"],
341
+ apiKeyMeta: {
342
+ configFile: "settings.json",
343
+ parser: "json",
344
+ selector: "main.apiKey",
345
+ },
346
+ },
347
+
348
+ jellyseerr: {
349
+ id: "jellyseerr",
350
+ name: "Jellyseerr",
351
+ description: "Request management for Jellyfin",
352
+ category: "request",
353
+ defaultPort: 5056,
354
+ image: "fallenbagel/jellyseerr:latest",
355
+ puid: 13012,
356
+ pgid: 13000,
357
+ volumes: (root) => [`${root}/config/jellyseerr:/app/config`],
358
+ dependsOn: ["jellyfin"],
359
+ apiKeyMeta: {
360
+ configFile: "settings.json",
361
+ parser: "json",
362
+ selector: "main.apiKey",
363
+ },
364
+ },
365
+
366
+ // === DASHBOARDS ===
367
+ homarr: {
368
+ id: "homarr",
369
+ name: "Homarr",
370
+ description: "Modern dashboard for all services",
371
+ category: "dashboard",
372
+ defaultPort: 7575,
373
+ image: "ghcr.io/ajnart/homarr:latest",
374
+ puid: 0,
375
+ pgid: 0,
376
+ volumes: (root) => [
377
+ `${root}/config/homarr/configs:/app/data/configs`,
378
+ `${root}/config/homarr/icons:/app/public/icons`,
379
+ `${root}/config/homarr/data:/data`,
380
+ "/var/run/docker.sock:/var/run/docker.sock",
381
+ ],
382
+ },
383
+
384
+ heimdall: {
385
+ id: "heimdall",
386
+ name: "Heimdall",
387
+ description: "Application dashboard and launcher",
388
+ category: "dashboard",
389
+ defaultPort: 8082,
390
+ image: "lscr.io/linuxserver/heimdall:latest",
391
+ puid: 0,
392
+ pgid: 13000,
393
+ volumes: (root) => [`${root}/config/heimdall:/config`],
394
+ },
395
+
396
+ homepage: {
397
+ id: "homepage",
398
+ name: "Homepage",
399
+ description: "Highly customizable application dashboard",
400
+ category: "dashboard",
401
+ defaultPort: 3000,
402
+ image: "ghcr.io/gethomepage/homepage:latest",
403
+ puid: 0,
404
+ pgid: 0,
405
+ volumes: (root) => [`${root}/config/homepage:/app/config`, "/var/run/docker.sock:/var/run/docker.sock"],
406
+ },
407
+
408
+ // === UTILITIES ===
409
+ portainer: {
410
+ id: "portainer",
411
+ name: "Portainer",
412
+ description: "Docker container management UI",
413
+ category: "utility",
414
+ defaultPort: 9000,
415
+ image: "portainer/portainer-ce:latest",
416
+ puid: 0,
417
+ pgid: 0,
418
+ volumes: (root) => [`${root}/config/portainer:/data`, "/var/run/docker.sock:/var/run/docker.sock"],
419
+ },
420
+
421
+ huntarr: {
422
+ id: "huntarr",
423
+ name: "Huntarr",
424
+ description: "Missing content manager for *arr apps",
425
+ category: "utility",
426
+ defaultPort: 9705,
427
+ image: "huntarr/huntarr:latest",
428
+ puid: 0,
429
+ pgid: 13000,
430
+ volumes: (root) => [`${root}/config/huntarr:/config`],
431
+ },
432
+
433
+ unpackerr: {
434
+ id: "unpackerr",
435
+ name: "Unpackerr",
436
+ description: "Archive extraction for *arr apps",
437
+ category: "utility",
438
+ defaultPort: 5656,
439
+ image: "golift/unpackerr",
440
+ puid: 0,
441
+ pgid: 13000,
442
+ volumes: (root) => [`${root}/config/unpackerr:/config`, `${root}/data:/data`],
443
+ },
444
+
445
+ filebot: {
446
+ id: "filebot",
447
+ name: "FileBot",
448
+ description: "Media file renaming and automator",
449
+ category: "utility",
450
+ defaultPort: 5452,
451
+ image: "rednoah/filebot",
452
+ puid: 13000,
453
+ pgid: 13000,
454
+ volumes: (root) => [`${root}/config/filebot:/data`, `${root}/data:/data`],
455
+ environment: { DARK_MODE: "1" },
456
+ },
457
+
458
+ chromium: {
459
+ id: "chromium",
460
+ name: "Chromium",
461
+ description: "Web browser for secure remote browsing",
462
+ category: "utility",
463
+ defaultPort: 3000,
464
+ image: "lscr.io/linuxserver/chromium:latest",
465
+ puid: 13000,
466
+ pgid: 13000,
467
+ volumes: (root) => [`${root}/config/chromium:/config`],
468
+ environment: { TITLE: "Chromium" },
469
+ },
470
+
471
+ guacamole: {
472
+ id: "guacamole",
473
+ name: "Guacamole",
474
+ description: "Clientless remote desktop gateway",
475
+ category: "utility",
476
+ defaultPort: 8080,
477
+ image: "guacamole/guacamole",
478
+ puid: 0,
479
+ pgid: 0,
480
+ volumes: (root) => [`${root}/config/guacamole:/config`],
481
+ environment: {
482
+ WEBAPP_CONTEXT: "ROOT",
483
+ GUACD_HOSTNAME: "guacd",
484
+ POSTGRESQL_HOSTNAME: "postgresql",
485
+ POSTGRESQL_DATABASE: "guacamole",
486
+ POSTGRESQL_USER: "${POSTGRESQL_USERNAME}",
487
+ POSTGRESQL_PASSWORD: "${POSTGRESQL_PASSWORD}",
488
+ },
489
+ dependsOn: ["guacd", "postgresql"],
490
+ secrets: [
491
+ {
492
+ name: "POSTGRESQL_USERNAME",
493
+ description: "PostgreSQL Username",
494
+ required: true,
495
+ default: "postgres",
496
+ },
497
+ {
498
+ name: "POSTGRESQL_PASSWORD",
499
+ description: "PostgreSQL Password",
500
+ required: true,
501
+ mask: true,
502
+ },
503
+ ],
504
+ },
505
+
506
+ guacd: {
507
+ id: "guacd",
508
+ name: "Guacd",
509
+ description: "Guacamole proxy daemon",
510
+ category: "utility",
511
+ defaultPort: 4822,
512
+ image: "guacamole/guacd",
513
+ puid: 0, // Guacd runs as restricted user inside, or PUID? MediaStack sets user: PUID:PGID
514
+ pgid: 13000,
515
+ volumes: (root) => [`${root}/config/guacd:/config`], // Not really used but keeps structure
516
+ dependsOn: ["postgresql"],
517
+ },
518
+
519
+ "ddns-updater": {
520
+ id: "ddns-updater",
521
+ name: "DDNS-Updater",
522
+ description: "Dynamic DNS record updater",
523
+ category: "utility",
524
+ defaultPort: 8000,
525
+ image: "qmcgaw/ddns-updater",
526
+ puid: 13000,
527
+ pgid: 13000,
528
+ volumes: (root) => [`${root}/config/ddns-updater:/data`],
529
+ },
530
+
531
+ // === VPN ===
532
+ gluetun: {
533
+ id: "gluetun",
534
+ name: "Gluetun",
535
+ description: "VPN client container for routing traffic",
536
+ category: "vpn",
537
+ defaultPort: 8888,
538
+ image: "qmcgaw/gluetun:latest",
539
+ puid: 0,
540
+ pgid: 0,
541
+ cap_add: ["NET_ADMIN"],
542
+ devices: ["/dev/net/tun:/dev/net/tun"],
543
+ volumes: (root) => [`${root}/config/gluetun:/gluetun`],
544
+ environment: {
545
+ VPN_SERVICE_PROVIDER: "${VPN_SERVICE_PROVIDER}",
546
+ OPENVPN_USER: "${VPN_USERNAME}",
547
+ OPENVPN_PASSWORD: "${VPN_PASSWORD}",
548
+ WIREGUARD_PRIVATE_KEY: "${WIREGUARD_PRIVATE_KEY}",
549
+ HTTPPROXY: "on",
550
+ SHADOWSOCKS: "on",
551
+ },
552
+ secrets: [
553
+ {
554
+ name: "VPN_SERVICE_PROVIDER",
555
+ description: "VPN Provider (e.g. custom, airvpn)",
556
+ required: true,
557
+ default: "custom",
558
+ },
559
+ {
560
+ name: "VPN_USERNAME",
561
+ description: "OpenVPN Username",
562
+ required: false,
563
+ },
564
+ {
565
+ name: "VPN_PASSWORD",
566
+ description: "OpenVPN Password",
567
+ required: false,
568
+ mask: true,
569
+ },
570
+ {
571
+ name: "WIREGUARD_PRIVATE_KEY",
572
+ description: "WireGuard Private Key",
573
+ required: false,
574
+ mask: true,
575
+ },
576
+ ],
577
+ },
578
+
579
+ // === MONITORING ===
580
+ grafana: {
581
+ id: "grafana",
582
+ name: "Grafana",
583
+ description: "Visual monitoring dashboard",
584
+ category: "monitoring",
585
+ defaultPort: 3001,
586
+ image: "grafana/grafana-enterprise",
587
+ puid: 0,
588
+ pgid: 13000,
589
+ volumes: (root) => [`${root}/config/grafana:/var/lib/grafana`],
590
+ },
591
+
592
+ prometheus: {
593
+ id: "prometheus",
594
+ name: "Prometheus",
595
+ description: "Systems and service monitoring",
596
+ category: "monitoring",
597
+ defaultPort: 9090,
598
+ image: "prom/prometheus",
599
+ puid: 0,
600
+ pgid: 13000,
601
+ volumes: (root) => [`${root}/config/prometheus:/prometheus`],
602
+ },
603
+
604
+ dozzle: {
605
+ id: "dozzle",
606
+ name: "Dozzle",
607
+ description: "Real-time log viewer for Docker containers",
608
+ category: "monitoring",
609
+ defaultPort: 8888, // Often overlaps with Gluetun default 8888? Gluetun is 8888 proxy. Dozzle defaults 8080 or 8888?
610
+ // checking default: usually 8080.
611
+ // I'll set defaultPort to 9999 or something unique if possible, or 8080 and let user change.
612
+ // Actually Dozzle defaults to 8080 inside container.
613
+ image: "amir20/dozzle",
614
+ puid: 0,
615
+ pgid: 0,
616
+ volumes: () => ["/var/run/docker.sock:/var/run/docker.sock"],
617
+ },
618
+
619
+ "uptime-kuma": {
620
+ id: "uptime-kuma",
621
+ name: "Uptime Kuma",
622
+ description: "Self-hosted monitoring tool",
623
+ category: "monitoring",
624
+ defaultPort: 3001, // Commonly 3001
625
+ image: "louislam/uptime-kuma:1",
626
+ puid: 0,
627
+ pgid: 0,
628
+ volumes: (root) => [`${root}/config/uptime-kuma:/app/data`, "/var/run/docker.sock:/var/run/docker.sock"],
629
+ },
630
+
631
+ // === INFRASTRUCTURE ===
632
+ traefik: {
633
+ id: "traefik",
634
+ name: "Traefik",
635
+ description: "Reverse proxy and load balancer",
636
+ category: "infrastructure",
637
+ defaultPort: 8083,
638
+ image: "traefik:latest",
639
+ puid: 0,
640
+ pgid: 0,
641
+ volumes: (root) => [
642
+ `${root}/config/traefik:/etc/traefik`,
643
+ `${root}/config/traefik/letsencrypt:/letsencrypt`,
644
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
645
+ ],
646
+ secrets: [
647
+ {
648
+ name: "CLOUDFLARE_DNS_API_TOKEN",
649
+ description: "Cloudflare DNS API Token for Traefik",
650
+ required: false,
651
+ mask: true,
652
+ },
653
+ {
654
+ name: "CLOUDFLARE_DNS_ZONE",
655
+ description: "Root Domain (e.g. example.com)",
656
+ required: true,
657
+ },
658
+ ],
659
+ },
660
+
661
+ "traefik-certs-dumper": {
662
+ id: "traefik-certs-dumper",
663
+ name: "Traefik Certs Dumper",
664
+ description: "Extracts certificates from Traefik",
665
+ category: "infrastructure",
666
+ defaultPort: 0,
667
+ image: "ldez/traefik-certs-dumper:latest",
668
+ puid: 0,
669
+ pgid: 0,
670
+ volumes: (root) => [`${root}/config/traefik/letsencrypt:/traefik:ro`, `${root}/config/traefik/certs:/output`],
671
+ dependsOn: ["traefik"],
672
+ },
673
+
674
+ crowdsec: {
675
+ id: "crowdsec",
676
+ name: "CrowdSec",
677
+ description: "Intrusion prevention system",
678
+ category: "infrastructure",
679
+ defaultPort: 8080,
680
+ image: "crowdsecurity/crowdsec:latest",
681
+ puid: 0,
682
+ pgid: 0,
683
+ volumes: (root) => [`${root}/config/crowdsec:/etc/crowdsec`, "/var/run/docker.sock:/var/run/docker.sock:ro"],
684
+ },
685
+
686
+ headscale: {
687
+ id: "headscale",
688
+ name: "Headscale",
689
+ description: "Open-source Tailscale control server",
690
+ category: "infrastructure",
691
+ defaultPort: 8084,
692
+ image: "headscale/headscale:latest",
693
+ puid: 0,
694
+ pgid: 0,
695
+ volumes: (root) => [`${root}/config/headscale:/etc/headscale`, `${root}/config/headscale/data:/var/lib/headscale`],
696
+ },
697
+
698
+ headplane: {
699
+ id: "headplane",
700
+ name: "Headplane",
701
+ description: "Headscale web UI",
702
+ category: "infrastructure",
703
+ defaultPort: 3000,
704
+ image: "ghcr.io/tale/headplane:latest",
705
+ puid: 0,
706
+ pgid: 0,
707
+ volumes: (root) => [`${root}/config/headplane:/config`],
708
+ dependsOn: ["headscale"],
709
+ },
710
+
711
+ tailscale: {
712
+ id: "tailscale",
713
+ name: "Tailscale",
714
+ description: "VPN mesh network client",
715
+ category: "infrastructure",
716
+ defaultPort: 0,
717
+ image: "tailscale/tailscale:latest",
718
+ puid: 0,
719
+ pgid: 0,
720
+ cap_add: ["NET_ADMIN"],
721
+ devices: ["/dev/net/tun:/dev/net/tun"],
722
+ volumes: (root) => [`${root}/config/tailscale:/var/lib/tailscale`],
723
+ secrets: [
724
+ {
725
+ name: "TAILSCALE_AUTHKEY",
726
+ description: "Tailscale Auth Key",
727
+ required: true,
728
+ mask: true,
729
+ },
730
+ ],
731
+ },
732
+
733
+ authentik: {
734
+ id: "authentik",
735
+ name: "Authentik",
736
+ description: "Identity provider and SSO (Server)",
737
+ category: "infrastructure",
738
+ defaultPort: 9001,
739
+ image: "ghcr.io/goauthentik/server:latest",
740
+ puid: 0,
741
+ pgid: 13000,
742
+ volumes: (root) => [`${root}/config/authentik/media:/media`, `${root}/config/authentik/templates:/templates`],
743
+ environment: {
744
+ AUTHENTIK_REDIS__HOST: "valkey",
745
+ AUTHENTIK_POSTGRESQL__HOST: "postgresql",
746
+ AUTHENTIK_POSTGRESQL__NAME: "authentik",
747
+ AUTHENTIK_POSTGRESQL__USER: "${POSTGRESQL_USERNAME}",
748
+ AUTHENTIK_POSTGRESQL__PASSWORD: "${POSTGRESQL_PASSWORD}",
749
+ AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}",
750
+ },
751
+ dependsOn: ["postgresql", "valkey", "authentik-worker"],
752
+ secrets: [
753
+ {
754
+ name: "AUTHENTIK_SECRET_KEY",
755
+ description: "Authentik Secret Key",
756
+ required: true,
757
+ mask: true,
758
+ generate: true,
759
+ },
760
+ {
761
+ name: "POSTGRESQL_USERNAME",
762
+ description: "Postgres Username",
763
+ required: true,
764
+ default: "postgres",
765
+ },
766
+ {
767
+ name: "POSTGRESQL_PASSWORD",
768
+ description: "Postgres Password",
769
+ required: true,
770
+ mask: true,
771
+ },
772
+ ],
773
+ },
774
+
775
+ "authentik-worker": {
776
+ id: "authentik-worker",
777
+ name: "Authentik Worker",
778
+ description: "Identity provider background worker",
779
+ category: "infrastructure",
780
+ defaultPort: 0,
781
+ image: "ghcr.io/goauthentik/server:latest",
782
+ puid: 0,
783
+ pgid: 13000,
784
+ volumes: (root) => [
785
+ `${root}/config/authentik/media:/media`,
786
+ `${root}/config/authentik/templates:/templates`,
787
+ `${root}/config/authentik/certs:/certs`,
788
+ "/var/run/docker.sock:/var/run/docker.sock",
789
+ ],
790
+ environment: {
791
+ AUTHENTIK_REDIS__HOST: "valkey",
792
+ AUTHENTIK_POSTGRESQL__HOST: "postgresql",
793
+ AUTHENTIK_POSTGRESQL__NAME: "authentik",
794
+ AUTHENTIK_POSTGRESQL__USER: "${POSTGRESQL_USERNAME}",
795
+ AUTHENTIK_POSTGRESQL__PASSWORD: "${POSTGRESQL_PASSWORD}",
796
+ AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}",
797
+ },
798
+ dependsOn: ["postgresql", "valkey"],
799
+ },
800
+
801
+ postgresql: {
802
+ id: "postgresql",
803
+ name: "PostgreSQL",
804
+ description: "Database server",
805
+ category: "infrastructure",
806
+ defaultPort: 5432,
807
+ image: "docker.io/library/postgres:latest",
808
+ puid: 0,
809
+ pgid: 13000,
810
+ volumes: (root) => [`${root}/config/postgresql:/var/lib/postgresql/data`],
811
+ environment: {
812
+ POSTGRES_USER: "${POSTGRESQL_USERNAME}",
813
+ POSTGRES_PASSWORD: "${POSTGRESQL_PASSWORD}",
814
+ POSTGRES_DB: "authentik", // Default to authentik db or user needs to change?
815
+ },
816
+ secrets: [
817
+ {
818
+ name: "POSTGRESQL_USERNAME",
819
+ description: "PostgreSQL Username",
820
+ required: true,
821
+ default: "postgres",
822
+ },
823
+ {
824
+ name: "POSTGRESQL_PASSWORD",
825
+ description: "PostgreSQL Password",
826
+ required: true,
827
+ mask: true,
828
+ },
829
+ ],
830
+ },
831
+
832
+ valkey: {
833
+ id: "valkey",
834
+ name: "Valkey",
835
+ description: "Redis-compatible key-value store",
836
+ category: "infrastructure",
837
+ defaultPort: 6379,
838
+ image: "valkey/valkey:alpine",
839
+ puid: 0,
840
+ pgid: 13000,
841
+ volumes: (root) => [`${root}/config/valkey:/data`],
842
+ },
843
+ }
844
+
845
+ export function getAppsByCategory(): Record<string, AppDefinition[]> {
846
+ const result: Record<string, AppDefinition[]> = {}
847
+
848
+ for (const app of Object.values(APPS)) {
849
+ if (!result[app.category]) {
850
+ result[app.category] = []
851
+ }
852
+ result[app.category].push(app)
853
+ }
854
+
855
+ return result
856
+ }
857
+
858
+ export function getApp(id: AppId): AppDefinition | undefined {
859
+ return APPS[id]
860
+ }
861
+
862
+ export function getAllApps(): AppDefinition[] {
863
+ return Object.values(APPS)
864
+ }
865
+
866
+ export function getAppIds(): AppId[] {
867
+ return Object.keys(APPS) as AppId[]
868
+ }