@muhammedaksam/easiarr 1.4.1 → 1.6.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.
Files changed (2) hide show
  1. package/dist/index.js +1255 -1342
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@muhammedaksam/easiarr",
55
- version: "1.4.1",
55
+ version: "1.6.0",
56
56
  description: "TUI tool for generating docker-compose files for the *arr media ecosystem with 41 apps, TRaSH Guides best practices, VPN routing, and Traefik reverse proxy support",
57
57
  module: "dist/index.js",
58
58
  type: "module",
@@ -108,20 +108,20 @@ var init_package = __esm(() => {
108
108
  "@types/bcrypt": "^6.0.0",
109
109
  "@types/bun": "latest",
110
110
  "@types/jest": "^30.0.0",
111
- "@types/node": "^26.0.1",
112
- eslint: "^10.6.0",
111
+ "@types/node": "^26.1.1",
112
+ eslint: "^10.8.0",
113
113
  "eslint-plugin-import": "^2.32.0",
114
114
  jest: "^30.4.2",
115
115
  jiti: "^2.7.0",
116
- prettier: "^3.9.1",
117
- "ts-jest": "^29.4.11",
118
- "typescript-eslint": "^8.62.0"
116
+ prettier: "^3.9.6",
117
+ "ts-jest": "^29.4.12",
118
+ "typescript-eslint": "^8.65.0"
119
119
  },
120
120
  peerDependencies: {
121
121
  typescript: "^5.9.3"
122
122
  },
123
123
  dependencies: {
124
- "@opentui/core": "^0.4.2",
124
+ "@opentui/core": "^0.4.5",
125
125
  bcrypt: "^6.0.0",
126
126
  "socket.io-client": "^4.8.3",
127
127
  yaml: "^2.9.0"
@@ -352,1258 +352,7 @@ var init_manager = __esm(() => {
352
352
  init_schema();
353
353
  });
354
354
 
355
- // src/utils/arch.ts
356
- function getSystemArch() {
357
- const arch = process.arch;
358
- switch (arch) {
359
- case "x64":
360
- case "ia32":
361
- return "x64";
362
- case "arm64":
363
- return "arm64";
364
- case "arm":
365
- return "arm32";
366
- default:
367
- return "x64";
368
- }
369
- }
370
- function isAppCompatible(app, arch) {
371
- const systemArch = arch ?? getSystemArch();
372
- if (!app.arch) {
373
- return true;
374
- }
375
- if (app.arch.deprecated?.includes(systemArch)) {
376
- return false;
377
- }
378
- if (app.arch.supported && !app.arch.supported.includes(systemArch)) {
379
- return false;
380
- }
381
- return true;
382
- }
383
- function getArchWarning(app, arch) {
384
- const systemArch = arch ?? getSystemArch();
385
- if (!app.arch) {
386
- return null;
387
- }
388
- if (app.arch.deprecated?.includes(systemArch)) {
389
- return app.arch.warning || `${app.name} has deprecated support for ${systemArch}`;
390
- }
391
- if (app.arch.supported && !app.arch.supported.includes(systemArch)) {
392
- return `${app.name} does not support ${systemArch} architecture`;
393
- }
394
- return null;
395
- }
396
- function isAppDeprecated(app, arch) {
397
- const systemArch = arch ?? getSystemArch();
398
- return app.arch?.deprecated?.includes(systemArch) ?? false;
399
- }
400
-
401
- // src/apps/registry.ts
402
- var exports_registry = {};
403
- __export(exports_registry, {
404
- isAppDeprecated: () => isAppDeprecated,
405
- isAppCompatible: () => isAppCompatible,
406
- getSystemArch: () => getSystemArch,
407
- getCompatibleApps: () => getCompatibleApps,
408
- getArchWarning: () => getArchWarning,
409
- getAppsWithArchWarnings: () => getAppsWithArchWarnings,
410
- getAppsByCategory: () => getAppsByCategory,
411
- getAppIds: () => getAppIds,
412
- getApp: () => getApp,
413
- getAllApps: () => getAllApps,
414
- APPS: () => APPS
415
- });
416
- function getAppsByCategory() {
417
- const result = {};
418
- for (const app of Object.values(APPS)) {
419
- if (!result[app.category]) {
420
- result[app.category] = [];
421
- }
422
- result[app.category].push(app);
423
- }
424
- return result;
425
- }
426
- function getApp(id) {
427
- return APPS[id];
428
- }
429
- function getAllApps() {
430
- return Object.values(APPS);
431
- }
432
- function getAppIds() {
433
- return Object.keys(APPS);
434
- }
435
- function getCompatibleApps() {
436
- const arch = getSystemArch();
437
- return Object.values(APPS).filter((app) => isAppCompatible(app, arch));
438
- }
439
- function getAppsWithArchWarnings() {
440
- const arch = getSystemArch();
441
- const result = [];
442
- for (const app of Object.values(APPS)) {
443
- const warning = getArchWarning(app, arch);
444
- if (warning) {
445
- result.push({ app, warning });
446
- }
447
- }
448
- return result;
449
- }
450
- var APPS;
451
- var init_registry = __esm(() => {
452
- APPS = {
453
- radarr: {
454
- id: "radarr",
455
- name: "Radarr",
456
- description: "Movie collection manager",
457
- category: "servarr",
458
- defaultPort: 7878,
459
- image: "lscr.io/linuxserver/radarr:latest",
460
- puid: 13002,
461
- pgid: 13000,
462
- volumes: (root) => [`${root}/config/radarr:/config`, `${root}/data:/data`],
463
- trashGuide: "docs/Radarr/",
464
- apiKeyMeta: {
465
- configFile: "config.xml",
466
- parser: "regex",
467
- selector: "<ApiKey>(.*?)</ApiKey>"
468
- },
469
- rootFolder: {
470
- path: "/data/media/movies",
471
- apiVersion: "v3"
472
- },
473
- prowlarrCategoryIds: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090],
474
- homepage: { icon: "radarr.png", widget: "radarr" },
475
- logVolume: "/config/logs"
476
- },
477
- sonarr: {
478
- id: "sonarr",
479
- name: "Sonarr",
480
- description: "TV series collection manager",
481
- category: "servarr",
482
- defaultPort: 8989,
483
- image: "lscr.io/linuxserver/sonarr:latest",
484
- puid: 13001,
485
- pgid: 13000,
486
- volumes: (root) => [`${root}/config/sonarr:/config`, `${root}/data:/data`],
487
- trashGuide: "docs/Sonarr/",
488
- apiKeyMeta: {
489
- configFile: "config.xml",
490
- parser: "regex",
491
- selector: "<ApiKey>(.*?)</ApiKey>"
492
- },
493
- rootFolder: {
494
- path: "/data/media/tv",
495
- apiVersion: "v3"
496
- },
497
- prowlarrCategoryIds: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080, 5090],
498
- homepage: { icon: "sonarr.png", widget: "sonarr" },
499
- logVolume: "/config/logs"
500
- },
501
- lidarr: {
502
- id: "lidarr",
503
- name: "Lidarr",
504
- description: "Music collection manager",
505
- category: "servarr",
506
- defaultPort: 8686,
507
- image: "lscr.io/linuxserver/lidarr:latest",
508
- puid: 13003,
509
- pgid: 13000,
510
- volumes: (root) => [`${root}/config/lidarr:/config`, `${root}/data:/data`],
511
- apiKeyMeta: {
512
- configFile: "config.xml",
513
- parser: "regex",
514
- selector: "<ApiKey>(.*?)</ApiKey>"
515
- },
516
- rootFolder: {
517
- path: "/data/media/music",
518
- apiVersion: "v1"
519
- },
520
- prowlarrCategoryIds: [3000, 3010, 3020, 3030, 3040, 3050, 3060],
521
- homepage: { icon: "lidarr.png", widget: "lidarr" },
522
- logVolume: "/config/logs"
523
- },
524
- readarr: {
525
- id: "readarr",
526
- name: "Readarr",
527
- description: "Book collection manager",
528
- category: "servarr",
529
- defaultPort: 8787,
530
- image: "lscr.io/linuxserver/readarr:develop",
531
- puid: 13004,
532
- pgid: 13000,
533
- volumes: (root) => [`${root}/config/readarr:/config`, `${root}/data:/data`],
534
- apiKeyMeta: {
535
- configFile: "config.xml",
536
- parser: "regex",
537
- selector: "<ApiKey>(.*?)</ApiKey>"
538
- },
539
- rootFolder: {
540
- path: "/data/media/books",
541
- apiVersion: "v1"
542
- },
543
- prowlarrCategoryIds: [7000, 7010, 7020, 7030, 7040, 7050, 7060],
544
- arch: {
545
- deprecated: ["arm64", "arm32"],
546
- warning: "Readarr is deprecated - no ARM64 support (project abandoned by upstream)"
547
- },
548
- homepage: { icon: "readarr.png", widget: "readarr" },
549
- logVolume: "/config/logs"
550
- },
551
- bazarr: {
552
- id: "bazarr",
553
- name: "Bazarr",
554
- description: "Subtitle manager for Sonarr/Radarr",
555
- category: "servarr",
556
- defaultPort: 6767,
557
- image: "lscr.io/linuxserver/bazarr:latest",
558
- puid: 13013,
559
- pgid: 13000,
560
- volumes: (root) => [`${root}/config/bazarr:/config`, `${root}/data/media:/data/media`],
561
- dependsOn: ["sonarr", "radarr"],
562
- trashGuide: "docs/Bazarr/",
563
- apiKeyMeta: {
564
- configFile: "config/config.yaml",
565
- parser: "yaml",
566
- selector: "auth.apikey"
567
- },
568
- homepage: { icon: "bazarr.png", widget: "bazarr" },
569
- logVolume: "/config/log"
570
- },
571
- mylar3: {
572
- id: "mylar3",
573
- name: "Mylar3",
574
- description: "Comic book collection manager",
575
- category: "servarr",
576
- defaultPort: 8090,
577
- image: "lscr.io/linuxserver/mylar3:latest",
578
- puid: 13005,
579
- pgid: 13000,
580
- volumes: (root) => [`${root}/config/mylar3:/config`, `${root}/data:/data`],
581
- apiKeyMeta: {
582
- configFile: "mylar/config.ini",
583
- parser: "ini",
584
- section: "API",
585
- selector: "api_key",
586
- enabledKey: "api_enabled",
587
- generateIfMissing: true
588
- },
589
- prowlarrCategoryIds: [7030],
590
- homepage: { icon: "mylar.png", widget: "mylar" },
591
- logVolume: "/app/mylar/logs"
592
- },
593
- whisparr: {
594
- id: "whisparr",
595
- name: "Whisparr",
596
- description: "Adult media collection manager",
597
- category: "servarr",
598
- defaultPort: 6969,
599
- image: "ghcr.io/hotio/whisparr:nightly",
600
- puid: 13015,
601
- pgid: 13000,
602
- volumes: (root) => [`${root}/config/whisparr:/config`, `${root}/data:/data`],
603
- apiKeyMeta: {
604
- configFile: "config.xml",
605
- parser: "regex",
606
- selector: "<ApiKey>(.*?)</ApiKey>"
607
- },
608
- rootFolder: {
609
- path: "/data/media/adult",
610
- apiVersion: "v3"
611
- },
612
- prowlarrCategoryIds: [6000, 6010, 6020, 6030, 6040, 6045, 6050, 6060, 6070, 6080, 6090],
613
- homepage: { icon: "whisparr.png", widget: "sonarr" },
614
- logVolume: "/config/logs"
615
- },
616
- audiobookshelf: {
617
- id: "audiobookshelf",
618
- name: "Audiobookshelf",
619
- description: "Audiobook and podcast server",
620
- category: "servarr",
621
- defaultPort: 13378,
622
- image: "ghcr.io/advplyr/audiobookshelf:latest",
623
- puid: 13014,
624
- pgid: 13000,
625
- volumes: (root) => [
626
- `${root}/config/audiobookshelf:/config`,
627
- `${root}/data/media/audiobooks:/audiobooks`,
628
- `${root}/data/media/podcasts:/podcasts`,
629
- `${root}/data/media/audiobookshelf-metadata:/metadata`
630
- ],
631
- homepage: { icon: "audiobookshelf.png", widget: "audiobookshelf" },
632
- logVolume: "/config/metadata/logs"
633
- },
634
- prowlarr: {
635
- id: "prowlarr",
636
- name: "Prowlarr",
637
- description: "Indexer manager for *arr apps",
638
- category: "indexer",
639
- defaultPort: 9696,
640
- image: "lscr.io/linuxserver/prowlarr:develop",
641
- puid: 13006,
642
- pgid: 13000,
643
- volumes: (root) => [`${root}/config/prowlarr:/config`],
644
- trashGuide: "docs/Prowlarr/",
645
- apiKeyMeta: {
646
- configFile: "config.xml",
647
- parser: "regex",
648
- selector: "<ApiKey>(.*?)</ApiKey>"
649
- },
650
- homepage: { icon: "prowlarr.png", widget: "prowlarr" },
651
- logVolume: "/config/logs"
652
- },
653
- jackett: {
654
- id: "jackett",
655
- name: "Jackett",
656
- description: "Alternative indexer manager",
657
- category: "indexer",
658
- defaultPort: 9117,
659
- image: "lscr.io/linuxserver/jackett:latest",
660
- puid: 13008,
661
- pgid: 13000,
662
- volumes: (root) => [`${root}/config/jackett:/config`],
663
- apiKeyMeta: {
664
- configFile: "Jackett/ServerConfig.json",
665
- parser: "json",
666
- selector: "APIKey"
667
- },
668
- homepage: { icon: "jackett.png", widget: "jackett" },
669
- logVolume: "/config/logs"
670
- },
671
- flaresolverr: {
672
- id: "flaresolverr",
673
- name: "FlareSolverr",
674
- description: "Cloudflare bypass proxy",
675
- category: "indexer",
676
- defaultPort: 8191,
677
- image: "ghcr.io/flaresolverr/flaresolverr:latest",
678
- puid: 0,
679
- pgid: 0,
680
- volumes: () => [],
681
- environment: {
682
- LOG_LEVEL: "info",
683
- LOG_HTML: "false",
684
- CAPTCHA_SOLVER: "none"
685
- },
686
- homepage: {
687
- icon: "flaresolverr.png",
688
- widget: "customapi",
689
- widgetFields: {
690
- url: "http://flaresolverr:8191",
691
- mappings: JSON.stringify([
692
- { field: "msg", label: "Status" },
693
- { field: "version", label: "Version" }
694
- ])
695
- }
696
- }
697
- },
698
- qbittorrent: {
699
- id: "qbittorrent",
700
- name: "qBittorrent",
701
- description: "BitTorrent client",
702
- category: "downloader",
703
- defaultPort: 8080,
704
- image: "lscr.io/linuxserver/qbittorrent:latest",
705
- puid: 13007,
706
- pgid: 13000,
707
- volumes: (root) => [`${root}/config/qbittorrent:/config`, `${root}/data:/data`],
708
- environment: { WEBUI_PORT: "8080" },
709
- secrets: [
710
- {
711
- name: "USERNAME_QBITTORRENT",
712
- description: "Username for qBittorrent WebUI",
713
- required: false,
714
- default: "admin"
715
- },
716
- {
717
- name: "PASSWORD_QBITTORRENT",
718
- description: "Password for qBittorrent WebUI",
719
- required: false,
720
- mask: true
721
- }
722
- ],
723
- trashGuide: "docs/Downloaders/qBittorrent/",
724
- homepage: { icon: "qbittorrent.png", widget: "qbittorrent" },
725
- logVolume: "/config/qBittorrent/logs"
726
- },
727
- sabnzbd: {
728
- id: "sabnzbd",
729
- name: "SABnzbd",
730
- description: "Usenet downloader",
731
- category: "downloader",
732
- defaultPort: 8081,
733
- image: "lscr.io/linuxserver/sabnzbd:latest",
734
- puid: 13011,
735
- pgid: 13000,
736
- volumes: (root) => [`${root}/config/sabnzbd:/config`, `${root}/data:/data`],
737
- trashGuide: "docs/Downloaders/SABnzbd/",
738
- apiKeyMeta: {
739
- configFile: "sabnzbd.ini",
740
- parser: "regex",
741
- selector: "api_key\\s*=\\s*(.+)"
742
- },
743
- homepage: { icon: "sabnzbd.png", widget: "sabnzbd" },
744
- logVolume: "/config/logs"
745
- },
746
- slskd: {
747
- id: "slskd",
748
- name: "Slskd",
749
- description: "Soulseek client for music downloads",
750
- category: "downloader",
751
- defaultPort: 5030,
752
- image: "slskd/slskd",
753
- puid: 0,
754
- pgid: 0,
755
- useDockerUser: true,
756
- volumes: (root) => [`${root}/config/slskd:/app`, `${root}/data:/data`],
757
- secondaryPorts: ["5031:5031", "50300:50300"],
758
- environment: {
759
- SLSKD_REMOTE_CONFIGURATION: "true",
760
- SLSKD_USERNAME: "${USERNAME_GLOBAL}",
761
- SLSKD_PASSWORD: "${PASSWORD_GLOBAL}",
762
- SLSKD_SLSK_USERNAME: "${USERNAME_SOULSEEK}",
763
- SLSKD_SLSK_PASSWORD: "${PASSWORD_SOULSEEK}"
764
- },
765
- secrets: [
766
- {
767
- name: "USERNAME_SOULSEEK",
768
- description: "Soulseek network username (your Soulseek account)",
769
- required: true
770
- },
771
- {
772
- name: "PASSWORD_SOULSEEK",
773
- description: "Soulseek network password",
774
- required: true,
775
- mask: true
776
- }
777
- ],
778
- homepage: { icon: "slskd.png", widget: "slskd" },
779
- autoSetup: {
780
- type: "partial",
781
- description: "Generates slskd.yml with API key for Homepage widget and Soularr integration"
782
- },
783
- logVolume: "/app/logs"
784
- },
785
- soularr: {
786
- id: "soularr",
787
- name: "Soularr",
788
- description: "Connects Lidarr with Soulseek via Slskd",
789
- category: "downloader",
790
- defaultPort: 0,
791
- image: "mrusse08/soularr:latest",
792
- puid: 13017,
793
- pgid: 13000,
794
- volumes: (root) => [`${root}/data/slskd_downloads:/downloads`, `${root}/config/soularr:/data`],
795
- environment: {
796
- SCRIPT_INTERVAL: "300"
797
- },
798
- dependsOn: ["lidarr", "slskd"]
799
- },
800
- plex: {
801
- id: "plex",
802
- name: "Plex",
803
- description: "Media server with streaming",
804
- category: "mediaserver",
805
- defaultPort: 32400,
806
- image: "lscr.io/linuxserver/plex:latest",
807
- puid: 13010,
808
- pgid: 13000,
809
- volumes: (root) => [`${root}/config/plex:/config`, `${root}/data/media:/data/media`],
810
- environment: { VERSION: "docker", PLEX_CLAIM: "${PLEX_CLAIM}" },
811
- trashGuide: "docs/Plex/",
812
- apiKeyMeta: {
813
- configFile: "Library/Application Support/Plex Media Server/Preferences.xml",
814
- parser: "regex",
815
- selector: 'PlexOnlineToken="([^"]+)"'
816
- },
817
- homepage: { icon: "plex.png", widget: "plex" },
818
- logVolume: "/config/Library/Application Support/Plex Media Server/Logs",
819
- autoSetup: {
820
- type: "full",
821
- description: "Claim server with token, create media libraries",
822
- envVars: ["PLEX_CLAIM"]
823
- }
824
- },
825
- jellyfin: {
826
- id: "jellyfin",
827
- name: "Jellyfin",
828
- description: "Free open-source media server",
829
- category: "mediaserver",
830
- defaultPort: 8096,
831
- image: "lscr.io/linuxserver/jellyfin:latest",
832
- puid: 0,
833
- pgid: 13000,
834
- volumes: (root) => [`${root}/config/jellyfin:/config`, `${root}/data/media:/data/media`],
835
- homepage: { icon: "jellyfin.png", widget: "jellyfin" },
836
- logVolume: "/config/log"
837
- },
838
- tautulli: {
839
- id: "tautulli",
840
- name: "Tautulli",
841
- description: "Plex monitoring and statistics",
842
- category: "mediaserver",
843
- defaultPort: 8181,
844
- image: "lscr.io/linuxserver/tautulli:latest",
845
- puid: 0,
846
- pgid: 13000,
847
- volumes: (root) => [`${root}/config/tautulli:/config`],
848
- dependsOn: ["plex"],
849
- apiKeyMeta: {
850
- configFile: "config.ini",
851
- parser: "regex",
852
- selector: "api_key\\s*=\\s*(.+)"
853
- },
854
- homepage: { icon: "tautulli.png", widget: "tautulli" },
855
- autoSetup: {
856
- type: "partial",
857
- description: "Connect to Plex, enable API",
858
- requires: ["plex"]
859
- },
860
- logVolume: "/config/logs"
861
- },
862
- tdarr: {
863
- id: "tdarr",
864
- name: "Tdarr",
865
- description: "Audio/video transcoding automation",
866
- category: "mediaserver",
867
- defaultPort: 8265,
868
- image: "ghcr.io/haveagitgat/tdarr:latest",
869
- puid: 0,
870
- pgid: 13000,
871
- volumes: (root) => [
872
- `${root}/config/tdarr/server:/app/server`,
873
- `${root}/config/tdarr/configs:/app/configs`,
874
- `${root}/config/tdarr/logs:/app/logs`,
875
- `${root}/data/media:/data`
876
- ],
877
- environment: { serverIP: "0.0.0.0", internalNode: "true" },
878
- homepage: { icon: "tdarr.png", widget: "tdarr" },
879
- logVolume: "/app/logs"
880
- },
881
- overseerr: {
882
- id: "overseerr",
883
- name: "Overseerr",
884
- description: "Request management for Plex",
885
- category: "request",
886
- defaultPort: 5055,
887
- image: "sctx/overseerr:latest",
888
- puid: 13009,
889
- pgid: 13000,
890
- volumes: (root) => [`${root}/config/overseerr:/app/config`],
891
- dependsOn: ["plex"],
892
- apiKeyMeta: {
893
- configFile: "settings.json",
894
- parser: "json",
895
- selector: "main.apiKey"
896
- },
897
- homepage: { icon: "overseerr.png", widget: "overseerr" },
898
- autoSetup: {
899
- type: "full",
900
- description: "Connect to Plex, configure Radarr/Sonarr",
901
- requires: ["plex"]
902
- },
903
- logVolume: "/app/config/logs"
904
- },
905
- jellyseerr: {
906
- id: "jellyseerr",
907
- name: "Jellyseerr",
908
- description: "Request management for Jellyfin",
909
- category: "request",
910
- defaultPort: 5055,
911
- image: "fallenbagel/jellyseerr:latest",
912
- puid: 13012,
913
- pgid: 13000,
914
- volumes: (root) => [`${root}/config/jellyseerr:/app/config`],
915
- dependsOn: ["jellyfin"],
916
- apiKeyMeta: {
917
- configFile: "settings.json",
918
- parser: "json",
919
- selector: "main.apiKey"
920
- },
921
- homepage: { icon: "jellyseerr.png", widget: "jellyseerr" },
922
- logVolume: "/app/config/logs"
923
- },
924
- homarr: {
925
- id: "homarr",
926
- name: "Homarr",
927
- description: "Modern dashboard for all services",
928
- category: "dashboard",
929
- defaultPort: 7575,
930
- image: "ghcr.io/ajnart/homarr:latest",
931
- puid: 0,
932
- pgid: 0,
933
- volumes: (root) => [
934
- `${root}/config/homarr/configs:/app/data/configs`,
935
- `${root}/config/homarr/icons:/app/public/icons`,
936
- `${root}/config/homarr/data:/data`,
937
- "/var/run/docker.sock:/var/run/docker.sock"
938
- ],
939
- homepage: { icon: "homarr.png" },
940
- logVolume: "/app/data/logs"
941
- },
942
- heimdall: {
943
- id: "heimdall",
944
- name: "Heimdall",
945
- description: "Application dashboard and launcher",
946
- category: "dashboard",
947
- defaultPort: 8082,
948
- image: "lscr.io/linuxserver/heimdall:latest",
949
- puid: 0,
950
- pgid: 13000,
951
- volumes: (root) => [`${root}/config/heimdall:/config`],
952
- homepage: { icon: "heimdall.png" },
953
- logVolume: "/config/log"
954
- },
955
- homepage: {
956
- id: "homepage",
957
- name: "Homepage",
958
- description: "Highly customizable application dashboard",
959
- category: "dashboard",
960
- defaultPort: 3009,
961
- internalPort: 3000,
962
- image: "ghcr.io/gethomepage/homepage:latest",
963
- puid: 0,
964
- pgid: 0,
965
- volumes: (root) => [
966
- `${root}/config/homepage:/app/config`,
967
- "/var/run/docker.sock:/var/run/docker.sock"
968
- ],
969
- environment: {
970
- HOMEPAGE_ALLOWED_HOSTS: "homepage,homepage.${CLOUDFLARE_DNS_ZONE},${CLOUDFLARE_DNS_ZONE},localhost,${LOCAL_DOCKER_IP},${LOCAL_DOCKER_IP}:3009"
971
- },
972
- logVolume: "/app/config/logs"
973
- },
974
- portainer: {
975
- id: "portainer",
976
- name: "Portainer",
977
- description: "Docker container management UI",
978
- category: "utility",
979
- defaultPort: 9000,
980
- image: "portainer/portainer-ce:latest",
981
- puid: 0,
982
- pgid: 0,
983
- volumes: (root) => [
984
- `${root}/config/portainer:/data`,
985
- "/var/run/docker.sock:/var/run/docker.sock"
986
- ],
987
- minPasswordLength: 12,
988
- homepage: { icon: "portainer.png", widget: "portainer" },
989
- logVolume: "/data/logs"
990
- },
991
- huntarr: {
992
- id: "huntarr",
993
- name: "Huntarr",
994
- description: "Missing content manager for *arr apps",
995
- category: "utility",
996
- defaultPort: 9705,
997
- image: "huntarr/huntarr:latest",
998
- puid: 0,
999
- pgid: 13000,
1000
- volumes: (root) => [`${root}/config/huntarr:/config`],
1001
- dependsOn: ["sonarr", "radarr", "lidarr", "readarr"],
1002
- homepage: {
1003
- icon: "huntarr.png",
1004
- widget: "customapi",
1005
- widgetFields: {
1006
- url: "http://huntarr:9705/api/cycle/status",
1007
- mappings: JSON.stringify([
1008
- { field: "sonarr.next_cycle", label: "Sonarr", format: "relativeDate" },
1009
- { field: "radarr.next_cycle", label: "Radarr", format: "relativeDate" },
1010
- { field: "lidarr.next_cycle", label: "Lidarr", format: "relativeDate" },
1011
- { field: "readarr.next_cycle", label: "Readarr", format: "relativeDate" },
1012
- { field: "whisparr.next_cycle", label: "Whisparr", format: "relativeDate" }
1013
- ])
1014
- }
1015
- },
1016
- autoSetup: {
1017
- type: "full",
1018
- description: "Test connections to Sonarr, Radarr, Lidarr, Readarr, Whisparr",
1019
- requires: ["sonarr", "radarr"]
1020
- },
1021
- logVolume: "/config/logs"
1022
- },
1023
- maintainerr: {
1024
- id: "maintainerr",
1025
- name: "Maintainerr",
1026
- description: "Automated media management and cleanup for Plex",
1027
- category: "utility",
1028
- defaultPort: 6246,
1029
- image: "ghcr.io/maintainerr/maintainerr:latest",
1030
- puid: 0,
1031
- pgid: 13000,
1032
- volumes: (root) => [`${root}/config/maintainerr:/opt/data`],
1033
- dependsOn: ["plex"],
1034
- homepage: {
1035
- icon: "maintainerr.png",
1036
- widget: "customapi",
1037
- widgetFields: {
1038
- url: "http://maintainerr:6246/api/settings/version",
1039
- mappings: JSON.stringify([{ field: "version", label: "Version" }])
1040
- }
1041
- },
1042
- autoSetup: {
1043
- type: "full",
1044
- description: "Generate API key, configure Plex/Radarr/Sonarr/Overseerr/Tautulli connections",
1045
- requires: ["plex"]
1046
- },
1047
- logVolume: "/opt/data/logs"
1048
- },
1049
- unpackerr: {
1050
- id: "unpackerr",
1051
- name: "Unpackerr",
1052
- description: "Archive extraction for *arr apps",
1053
- category: "utility",
1054
- defaultPort: 5656,
1055
- image: "golift/unpackerr",
1056
- puid: 0,
1057
- pgid: 13000,
1058
- volumes: (root) => [`${root}/config/unpackerr:/config`, `${root}/data:/data`],
1059
- logVolume: "/config/logs"
1060
- },
1061
- filebot: {
1062
- id: "filebot",
1063
- name: "FileBot",
1064
- description: "Media file renaming and automator",
1065
- category: "utility",
1066
- defaultPort: 5452,
1067
- image: "rednoah/filebot",
1068
- puid: 13000,
1069
- pgid: 13000,
1070
- volumes: (root) => [`${root}/config/filebot:/data`, `${root}/data:/data`],
1071
- environment: { DARK_MODE: "1" },
1072
- logVolume: "/data/logs"
1073
- },
1074
- chromium: {
1075
- id: "chromium",
1076
- name: "Chromium",
1077
- description: "Web browser for secure remote browsing",
1078
- category: "utility",
1079
- defaultPort: 3000,
1080
- image: "lscr.io/linuxserver/chromium:latest",
1081
- puid: 13000,
1082
- pgid: 13000,
1083
- volumes: (root) => [`${root}/config/chromium:/config`],
1084
- environment: { TITLE: "Chromium" },
1085
- logVolume: "/config/logs"
1086
- },
1087
- guacamole: {
1088
- id: "guacamole",
1089
- name: "Guacamole",
1090
- description: "Clientless remote desktop gateway",
1091
- category: "utility",
1092
- defaultPort: 8080,
1093
- image: "guacamole/guacamole",
1094
- puid: 0,
1095
- pgid: 0,
1096
- volumes: (root) => [`${root}/config/guacamole:/config`],
1097
- environment: {
1098
- WEBAPP_CONTEXT: "ROOT",
1099
- GUACD_HOSTNAME: "guacd",
1100
- POSTGRESQL_HOSTNAME: "postgresql",
1101
- POSTGRESQL_DATABASE: "guacamole",
1102
- POSTGRESQL_USER: "${USERNAME_POSTGRESQL}",
1103
- POSTGRESQL_PASSWORD: "${PASSWORD_POSTGRESQL}"
1104
- },
1105
- dependsOn: ["guacd", "postgresql"],
1106
- secrets: [
1107
- {
1108
- name: "USERNAME_POSTGRESQL",
1109
- description: "PostgreSQL Username",
1110
- required: true,
1111
- default: "postgres"
1112
- },
1113
- {
1114
- name: "PASSWORD_POSTGRESQL",
1115
- description: "PostgreSQL Password",
1116
- required: true,
1117
- mask: true
1118
- }
1119
- ],
1120
- logVolume: "/guacamole/logs"
1121
- },
1122
- guacd: {
1123
- id: "guacd",
1124
- name: "Guacd",
1125
- description: "Guacamole proxy daemon",
1126
- category: "utility",
1127
- defaultPort: 4822,
1128
- image: "guacamole/guacd",
1129
- puid: 0,
1130
- pgid: 13000,
1131
- volumes: (root) => [`${root}/config/guacd:/config`],
1132
- dependsOn: ["postgresql"],
1133
- logVolume: "/guacd/logs"
1134
- },
1135
- "ddns-updater": {
1136
- id: "ddns-updater",
1137
- name: "DDNS-Updater",
1138
- description: "Dynamic DNS record updater",
1139
- category: "utility",
1140
- defaultPort: 8000,
1141
- image: "qmcgaw/ddns-updater",
1142
- puid: 13000,
1143
- pgid: 13000,
1144
- volumes: (root) => [`${root}/config/ddns-updater:/data`],
1145
- logVolume: "/data/logs"
1146
- },
1147
- easiarr: {
1148
- id: "easiarr",
1149
- name: "easiarr",
1150
- description: "Exposes easiarr config and bookmarks for Homepage dashboard",
1151
- category: "utility",
1152
- defaultPort: 3010,
1153
- internalPort: 8080,
1154
- image: "halverneus/static-file-server:latest",
1155
- puid: 0,
1156
- pgid: 0,
1157
- volumes: () => [
1158
- "${XDG_CONFIG_HOME}/easiarr/config.json:/web/config.json:ro",
1159
- "${XDG_CONFIG_HOME}/easiarr/bookmarks-local.html:/web/bookmarks-local.html:ro",
1160
- "${XDG_CONFIG_HOME}/easiarr/bookmarks-remote.html:/web/bookmarks-remote.html:ro"
1161
- ],
1162
- environment: {
1163
- FOLDER: "/web",
1164
- CORS: "true"
1165
- },
1166
- homepage: {
1167
- icon: "mdi-docker",
1168
- widget: "customapi",
1169
- widgetFields: {
1170
- url: "http://easiarr:8080/config.json",
1171
- mappings: JSON.stringify([
1172
- { field: "version", label: "Version" },
1173
- { field: "apps.length", label: "Apps" }
1174
- ])
1175
- }
1176
- }
1177
- },
1178
- recyclarr: {
1179
- id: "recyclarr",
1180
- name: "Recyclarr",
1181
- description: "Automatic TRaSH Guides profile sync for *arr apps",
1182
- category: "utility",
1183
- defaultPort: 0,
1184
- image: "ghcr.io/recyclarr/recyclarr:latest",
1185
- puid: 0,
1186
- pgid: 0,
1187
- useDockerUser: true,
1188
- volumes: (root) => [`${root}/config/recyclarr:/config`],
1189
- environment: {
1190
- RECYCLARR_CREATE_CONFIG: "false",
1191
- CRON_SCHEDULE: "@daily"
1192
- },
1193
- dependsOn: ["radarr", "sonarr"],
1194
- autoSetup: {
1195
- type: "full",
1196
- description: "Generate recyclarr.yml config for enabled *arr apps with TRaSH profiles",
1197
- requires: ["radarr", "sonarr"]
1198
- }
1199
- },
1200
- profilarr: {
1201
- id: "profilarr",
1202
- name: "Profilarr",
1203
- description: "Web UI for managing TRaSH Guides profiles (alternative to Recyclarr)",
1204
- category: "utility",
1205
- defaultPort: 6868,
1206
- image: "santiagosayshey/profilarr:latest",
1207
- puid: 1000,
1208
- pgid: 1000,
1209
- volumes: (root) => [`${root}/config/profilarr:/config`],
1210
- dependsOn: ["radarr", "sonarr"],
1211
- homepage: {
1212
- icon: "profilarr"
1213
- }
1214
- },
1215
- gluetun: {
1216
- id: "gluetun",
1217
- name: "Gluetun",
1218
- description: "VPN client container for routing traffic",
1219
- category: "vpn",
1220
- defaultPort: 8888,
1221
- image: "qmcgaw/gluetun:latest",
1222
- puid: 0,
1223
- pgid: 0,
1224
- cap_add: ["NET_ADMIN"],
1225
- devices: ["/dev/net/tun:/dev/net/tun"],
1226
- volumes: (root) => [`${root}/config/gluetun:/gluetun`],
1227
- environment: {
1228
- VPN_SERVICE_PROVIDER: "${VPN_SERVICE_PROVIDER}",
1229
- OPENVPN_USER: "${USERNAME_VPN}",
1230
- OPENVPN_PASSWORD: "${PASSWORD_VPN}",
1231
- WIREGUARD_PRIVATE_KEY: "${WIREGUARD_PRIVATE_KEY}",
1232
- HTTPPROXY: "on",
1233
- SHADOWSOCKS: "on"
1234
- },
1235
- secrets: [
1236
- {
1237
- name: "VPN_SERVICE_PROVIDER",
1238
- description: "VPN Provider (e.g. custom, airvpn)",
1239
- required: true,
1240
- default: "custom"
1241
- },
1242
- {
1243
- name: "USERNAME_VPN",
1244
- description: "OpenVPN Username",
1245
- required: false
1246
- },
1247
- {
1248
- name: "PASSWORD_VPN",
1249
- description: "OpenVPN Password",
1250
- required: false,
1251
- mask: true
1252
- },
1253
- {
1254
- name: "WIREGUARD_PRIVATE_KEY",
1255
- description: "WireGuard Private Key",
1256
- required: false,
1257
- mask: true
1258
- }
1259
- ],
1260
- homepage: { icon: "gluetun.png", widget: "gluetun" },
1261
- logVolume: "/gluetun"
1262
- },
1263
- grafana: {
1264
- id: "grafana",
1265
- name: "Grafana",
1266
- description: "Visual monitoring dashboard",
1267
- category: "monitoring",
1268
- defaultPort: 3001,
1269
- image: "grafana/grafana-enterprise",
1270
- puid: 0,
1271
- pgid: 13000,
1272
- volumes: (root) => [`${root}/config/grafana:/var/lib/grafana`],
1273
- homepage: { icon: "grafana.png", widget: "grafana" },
1274
- autoSetup: {
1275
- type: "full",
1276
- description: "Setup admin user, configure Prometheus datasource",
1277
- requires: ["prometheus"]
1278
- },
1279
- logVolume: "/var/log/grafana"
1280
- },
1281
- prometheus: {
1282
- id: "prometheus",
1283
- name: "Prometheus",
1284
- description: "Systems and service monitoring",
1285
- category: "monitoring",
1286
- defaultPort: 9090,
1287
- image: "prom/prometheus",
1288
- puid: 0,
1289
- pgid: 13000,
1290
- volumes: (root) => [`${root}/config/prometheus:/prometheus`],
1291
- homepage: { icon: "prometheus.png", widget: "prometheus" },
1292
- logVolume: "/prometheus/logs"
1293
- },
1294
- dozzle: {
1295
- id: "dozzle",
1296
- name: "Dozzle",
1297
- description: "Real-time log viewer for Docker containers",
1298
- category: "monitoring",
1299
- defaultPort: 8080,
1300
- image: "amir20/dozzle",
1301
- puid: 0,
1302
- pgid: 0,
1303
- volumes: () => ["/var/run/docker.sock:/var/run/docker.sock"]
1304
- },
1305
- "uptime-kuma": {
1306
- id: "uptime-kuma",
1307
- name: "Uptime Kuma",
1308
- description: "Self-hosted monitoring tool",
1309
- category: "monitoring",
1310
- defaultPort: 3001,
1311
- image: "louislam/uptime-kuma:1",
1312
- puid: 0,
1313
- pgid: 0,
1314
- volumes: (root) => [
1315
- `${root}/config/uptime-kuma:/app/data`,
1316
- "/var/run/docker.sock:/var/run/docker.sock"
1317
- ],
1318
- homepage: { icon: "uptime-kuma.png", widget: "uptimekuma" },
1319
- autoSetup: {
1320
- type: "full",
1321
- description: "Create admin user, add monitors for enabled apps"
1322
- },
1323
- logVolume: "/app/data/logs"
1324
- },
1325
- traefik: {
1326
- id: "traefik",
1327
- name: "Traefik",
1328
- description: "Reverse proxy and load balancer",
1329
- category: "infrastructure",
1330
- defaultPort: 80,
1331
- internalPort: 80,
1332
- image: "traefik:latest",
1333
- puid: 0,
1334
- pgid: 0,
1335
- volumes: (root) => [
1336
- `${root}/config/traefik:/etc/traefik`,
1337
- `${root}/config/traefik/letsencrypt:/letsencrypt`,
1338
- "/var/run/docker.sock:/var/run/docker.sock:ro"
1339
- ],
1340
- secondaryPorts: ["8083:8080"],
1341
- secrets: [
1342
- {
1343
- name: "CLOUDFLARE_DNS_ZONE",
1344
- description: "Root Domain (e.g. example.com)",
1345
- required: true
1346
- }
1347
- ],
1348
- homepage: { icon: "traefik.png", widget: "traefik" },
1349
- logVolume: "/etc/traefik/logs"
1350
- },
1351
- caddy: {
1352
- id: "caddy",
1353
- name: "Caddy",
1354
- description: "Web server with automatic HTTPS and reverse proxy",
1355
- category: "infrastructure",
1356
- defaultPort: 80,
1357
- internalPort: 80,
1358
- secondaryPorts: ["443:443"],
1359
- image: "caddy:latest",
1360
- puid: 0,
1361
- pgid: 0,
1362
- volumes: (root) => [
1363
- `${root}/config/caddy/Caddyfile:/etc/caddy/Caddyfile`,
1364
- `${root}/config/caddy/data:/data`,
1365
- `${root}/config/caddy/config:/config`
1366
- ],
1367
- homepage: { icon: "caddy.png" },
1368
- logVolume: "/data/logs"
1369
- },
1370
- cloudflared: {
1371
- id: "cloudflared",
1372
- name: "Cloudflared",
1373
- description: "Cloudflare Tunnel for secure external access without port forwarding",
1374
- category: "infrastructure",
1375
- defaultPort: 0,
1376
- image: "cloudflare/cloudflared:latest",
1377
- puid: 0,
1378
- pgid: 0,
1379
- volumes: () => [],
1380
- environment: {
1381
- TUNNEL_TOKEN: "${CLOUDFLARE_TUNNEL_TOKEN}"
1382
- },
1383
- command: "tunnel run",
1384
- dependsOn: ["traefik"],
1385
- secrets: [
1386
- {
1387
- name: "CLOUDFLARE_API_TOKEN",
1388
- description: "Cloudflare API Token (for automated tunnel setup via Menu)",
1389
- required: false,
1390
- mask: true
1391
- },
1392
- {
1393
- name: "CLOUDFLARE_TUNNEL_TOKEN",
1394
- description: "Cloudflare Tunnel Token (auto-generated or from Zero Trust)",
1395
- required: true,
1396
- mask: true
1397
- }
1398
- ],
1399
- homepage: { icon: "cloudflare-zero-trust.png", widget: "cloudflared" }
1400
- },
1401
- "traefik-certs-dumper": {
1402
- id: "traefik-certs-dumper",
1403
- name: "Traefik Certs Dumper",
1404
- description: "Extracts certificates from Traefik",
1405
- category: "infrastructure",
1406
- defaultPort: 0,
1407
- image: "ldez/traefik-certs-dumper:latest",
1408
- puid: 0,
1409
- pgid: 0,
1410
- volumes: (root) => [
1411
- `${root}/config/traefik/letsencrypt:/traefik:ro`,
1412
- `${root}/config/traefik/certs:/output`
1413
- ],
1414
- dependsOn: ["traefik"]
1415
- },
1416
- crowdsec: {
1417
- id: "crowdsec",
1418
- name: "CrowdSec",
1419
- description: "Intrusion prevention system",
1420
- category: "infrastructure",
1421
- defaultPort: 8080,
1422
- image: "crowdsecurity/crowdsec:latest",
1423
- puid: 0,
1424
- pgid: 0,
1425
- volumes: (root) => [
1426
- `${root}/config/crowdsec:/etc/crowdsec`,
1427
- "/var/run/docker.sock:/var/run/docker.sock:ro"
1428
- ],
1429
- homepage: { icon: "crowdsec.png", widget: "crowdsec" }
1430
- },
1431
- headscale: {
1432
- id: "headscale",
1433
- name: "Headscale",
1434
- description: "Open-source Tailscale control server",
1435
- category: "infrastructure",
1436
- defaultPort: 8084,
1437
- image: "headscale/headscale:latest",
1438
- puid: 0,
1439
- pgid: 0,
1440
- volumes: (root) => [
1441
- `${root}/config/headscale:/etc/headscale`,
1442
- `${root}/config/headscale/data:/var/lib/headscale`
1443
- ],
1444
- homepage: { icon: "headscale.png", widget: "headscale" }
1445
- },
1446
- headplane: {
1447
- id: "headplane",
1448
- name: "Headplane",
1449
- description: "Headscale web UI",
1450
- category: "infrastructure",
1451
- defaultPort: 3000,
1452
- image: "ghcr.io/tale/headplane:latest",
1453
- puid: 0,
1454
- pgid: 0,
1455
- volumes: (root) => [`${root}/config/headplane:/config`],
1456
- dependsOn: ["headscale"]
1457
- },
1458
- tailscale: {
1459
- id: "tailscale",
1460
- name: "Tailscale",
1461
- description: "VPN mesh network client",
1462
- category: "infrastructure",
1463
- defaultPort: 0,
1464
- image: "tailscale/tailscale:latest",
1465
- puid: 0,
1466
- pgid: 0,
1467
- cap_add: ["NET_ADMIN"],
1468
- devices: ["/dev/net/tun:/dev/net/tun"],
1469
- volumes: (root) => [`${root}/config/tailscale:/var/lib/tailscale`],
1470
- secrets: [
1471
- {
1472
- name: "TAILSCALE_AUTHKEY",
1473
- description: "Tailscale Auth Key",
1474
- required: true,
1475
- mask: true
1476
- }
1477
- ],
1478
- homepage: { icon: "tailscale.png", widget: "tailscale" }
1479
- },
1480
- authentik: {
1481
- id: "authentik",
1482
- name: "Authentik",
1483
- description: "Identity provider and SSO (Server)",
1484
- category: "infrastructure",
1485
- defaultPort: 9001,
1486
- image: "ghcr.io/goauthentik/server:latest",
1487
- puid: 0,
1488
- pgid: 13000,
1489
- volumes: (root) => [
1490
- `${root}/config/authentik/media:/media`,
1491
- `${root}/config/authentik/templates:/templates`
1492
- ],
1493
- environment: {
1494
- AUTHENTIK_REDIS__HOST: "valkey",
1495
- AUTHENTIK_POSTGRESQL__HOST: "postgresql",
1496
- AUTHENTIK_POSTGRESQL__NAME: "authentik",
1497
- AUTHENTIK_POSTGRESQL__USER: "${USERNAME_POSTGRESQL}",
1498
- AUTHENTIK_POSTGRESQL__PASSWORD: "${PASSWORD_POSTGRESQL}",
1499
- AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
1500
- },
1501
- dependsOn: ["postgresql", "valkey", "authentik-worker"],
1502
- secrets: [
1503
- {
1504
- name: "AUTHENTIK_SECRET_KEY",
1505
- description: "Authentik Secret Key",
1506
- required: true,
1507
- mask: true,
1508
- generate: true
1509
- },
1510
- {
1511
- name: "USERNAME_POSTGRESQL",
1512
- description: "Postgres Username",
1513
- required: true,
1514
- default: "postgres"
1515
- },
1516
- {
1517
- name: "PASSWORD_POSTGRESQL",
1518
- description: "Postgres Password",
1519
- required: true,
1520
- mask: true
1521
- }
1522
- ],
1523
- homepage: { icon: "authentik.png", widget: "authentik" }
1524
- },
1525
- "authentik-worker": {
1526
- id: "authentik-worker",
1527
- name: "Authentik Worker",
1528
- description: "Identity provider background worker",
1529
- category: "infrastructure",
1530
- defaultPort: 0,
1531
- image: "ghcr.io/goauthentik/server:latest",
1532
- puid: 0,
1533
- pgid: 13000,
1534
- volumes: (root) => [
1535
- `${root}/config/authentik/media:/media`,
1536
- `${root}/config/authentik/templates:/templates`,
1537
- `${root}/config/authentik/certs:/certs`,
1538
- "/var/run/docker.sock:/var/run/docker.sock"
1539
- ],
1540
- environment: {
1541
- AUTHENTIK_REDIS__HOST: "valkey",
1542
- AUTHENTIK_POSTGRESQL__HOST: "postgresql",
1543
- AUTHENTIK_POSTGRESQL__NAME: "authentik",
1544
- AUTHENTIK_POSTGRESQL__USER: "${USERNAME_POSTGRESQL}",
1545
- AUTHENTIK_POSTGRESQL__PASSWORD: "${PASSWORD_POSTGRESQL}",
1546
- AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
1547
- },
1548
- dependsOn: ["postgresql", "valkey"]
1549
- },
1550
- postgresql: {
1551
- id: "postgresql",
1552
- name: "PostgreSQL",
1553
- description: "Database server",
1554
- category: "infrastructure",
1555
- defaultPort: 5432,
1556
- image: "docker.io/library/postgres:latest",
1557
- puid: 0,
1558
- pgid: 13000,
1559
- volumes: (root) => [`${root}/config/postgresql:/var/lib/postgresql/data`],
1560
- environment: {
1561
- POSTGRES_USER: "${USERNAME_POSTGRESQL}",
1562
- POSTGRES_PASSWORD: "${PASSWORD_POSTGRESQL}",
1563
- POSTGRES_DB: "authentik"
1564
- },
1565
- secrets: [
1566
- {
1567
- name: "USERNAME_POSTGRESQL",
1568
- description: "PostgreSQL Username",
1569
- required: true,
1570
- default: "postgres"
1571
- },
1572
- {
1573
- name: "PASSWORD_POSTGRESQL",
1574
- description: "PostgreSQL Password",
1575
- required: true,
1576
- mask: true
1577
- }
1578
- ]
1579
- },
1580
- valkey: {
1581
- id: "valkey",
1582
- name: "Valkey",
1583
- description: "Redis-compatible key-value store",
1584
- category: "infrastructure",
1585
- defaultPort: 6379,
1586
- image: "valkey/valkey:alpine",
1587
- puid: 0,
1588
- pgid: 13000,
1589
- volumes: (root) => [`${root}/config/valkey:/data`]
1590
- }
1591
- };
1592
- });
1593
-
1594
355
  // src/utils/env.ts
1595
- var exports_env = {};
1596
- __export(exports_env, {
1597
- writeEnvSync: () => writeEnvSync,
1598
- updateEnv: () => updateEnv,
1599
- serializeEnv: () => serializeEnv,
1600
- readEnvSync: () => readEnvSync,
1601
- readEnv: () => readEnv,
1602
- parseEnvFile: () => parseEnvFile,
1603
- getLocalIp: () => getLocalIp,
1604
- getEnvValue: () => getEnvValue,
1605
- getEnvPath: () => getEnvPath
1606
- });
1607
356
  import { existsSync as existsSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
1608
357
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1609
358
  import { networkInterfaces } from "os";
@@ -1678,9 +427,6 @@ async function updateEnv(updates) {
1678
427
  const content = serializeEnv(merged);
1679
428
  await writeFile2(envPath, content, "utf-8");
1680
429
  }
1681
- function getEnvValue(key) {
1682
- return readEnvSync()[key];
1683
- }
1684
430
  var init_env = __esm(() => {
1685
431
  init_manager();
1686
432
  });
@@ -11894,8 +10640,1191 @@ var CATEGORY_ORDER = [
11894
10640
  { id: "infrastructure", short: "Infra" }
11895
10641
  ];
11896
10642
 
10643
+ // src/utils/arch.ts
10644
+ function getSystemArch() {
10645
+ const arch = process.arch;
10646
+ switch (arch) {
10647
+ case "x64":
10648
+ case "ia32":
10649
+ return "x64";
10650
+ case "arm64":
10651
+ return "arm64";
10652
+ case "arm":
10653
+ return "arm32";
10654
+ default:
10655
+ return "x64";
10656
+ }
10657
+ }
10658
+ function getArchWarning(app, arch) {
10659
+ const systemArch = arch ?? getSystemArch();
10660
+ if (!app.arch) {
10661
+ return null;
10662
+ }
10663
+ if (app.arch.deprecated?.includes(systemArch)) {
10664
+ return app.arch.warning || `${app.name} has deprecated support for ${systemArch}`;
10665
+ }
10666
+ if (app.arch.supported && !app.arch.supported.includes(systemArch)) {
10667
+ return `${app.name} does not support ${systemArch} architecture`;
10668
+ }
10669
+ return null;
10670
+ }
10671
+
10672
+ // src/apps/registry.ts
10673
+ var APPS = {
10674
+ radarr: {
10675
+ id: "radarr",
10676
+ name: "Radarr",
10677
+ description: "Movie collection manager",
10678
+ category: "servarr",
10679
+ defaultPort: 7878,
10680
+ image: "lscr.io/linuxserver/radarr:latest",
10681
+ puid: 13002,
10682
+ pgid: 13000,
10683
+ volumes: (root) => [`${root}/config/radarr:/config`, `${root}/data:/data`],
10684
+ trashGuide: "docs/Radarr/",
10685
+ apiKeyMeta: {
10686
+ configFile: "config.xml",
10687
+ parser: "regex",
10688
+ selector: "<ApiKey>(.*?)</ApiKey>"
10689
+ },
10690
+ rootFolder: {
10691
+ path: "/data/media/movies",
10692
+ apiVersion: "v3"
10693
+ },
10694
+ prowlarrCategoryIds: [2000, 2010, 2020, 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090],
10695
+ homepage: { icon: "radarr.png", widget: "radarr" },
10696
+ logVolume: "/config/logs"
10697
+ },
10698
+ sonarr: {
10699
+ id: "sonarr",
10700
+ name: "Sonarr",
10701
+ description: "TV series collection manager",
10702
+ category: "servarr",
10703
+ defaultPort: 8989,
10704
+ image: "lscr.io/linuxserver/sonarr:latest",
10705
+ puid: 13001,
10706
+ pgid: 13000,
10707
+ volumes: (root) => [`${root}/config/sonarr:/config`, `${root}/data:/data`],
10708
+ trashGuide: "docs/Sonarr/",
10709
+ apiKeyMeta: {
10710
+ configFile: "config.xml",
10711
+ parser: "regex",
10712
+ selector: "<ApiKey>(.*?)</ApiKey>"
10713
+ },
10714
+ rootFolder: {
10715
+ path: "/data/media/tv",
10716
+ apiVersion: "v3"
10717
+ },
10718
+ prowlarrCategoryIds: [5000, 5010, 5020, 5030, 5040, 5045, 5050, 5060, 5070, 5080, 5090],
10719
+ homepage: { icon: "sonarr.png", widget: "sonarr" },
10720
+ logVolume: "/config/logs"
10721
+ },
10722
+ lidarr: {
10723
+ id: "lidarr",
10724
+ name: "Lidarr",
10725
+ description: "Music collection manager",
10726
+ category: "servarr",
10727
+ defaultPort: 8686,
10728
+ image: "lscr.io/linuxserver/lidarr:latest",
10729
+ puid: 13003,
10730
+ pgid: 13000,
10731
+ volumes: (root) => [`${root}/config/lidarr:/config`, `${root}/data:/data`],
10732
+ apiKeyMeta: {
10733
+ configFile: "config.xml",
10734
+ parser: "regex",
10735
+ selector: "<ApiKey>(.*?)</ApiKey>"
10736
+ },
10737
+ rootFolder: {
10738
+ path: "/data/media/music",
10739
+ apiVersion: "v1"
10740
+ },
10741
+ prowlarrCategoryIds: [3000, 3010, 3020, 3030, 3040, 3050, 3060],
10742
+ homepage: { icon: "lidarr.png", widget: "lidarr" },
10743
+ logVolume: "/config/logs"
10744
+ },
10745
+ readarr: {
10746
+ id: "readarr",
10747
+ name: "Readarr",
10748
+ description: "Book collection manager",
10749
+ category: "servarr",
10750
+ defaultPort: 8787,
10751
+ image: "lscr.io/linuxserver/readarr:develop",
10752
+ puid: 13004,
10753
+ pgid: 13000,
10754
+ volumes: (root) => [`${root}/config/readarr:/config`, `${root}/data:/data`],
10755
+ apiKeyMeta: {
10756
+ configFile: "config.xml",
10757
+ parser: "regex",
10758
+ selector: "<ApiKey>(.*?)</ApiKey>"
10759
+ },
10760
+ rootFolder: {
10761
+ path: "/data/media/books",
10762
+ apiVersion: "v1"
10763
+ },
10764
+ prowlarrCategoryIds: [7000, 7010, 7020, 7030, 7040, 7050, 7060],
10765
+ arch: {
10766
+ deprecated: ["arm64", "arm32"],
10767
+ warning: "Readarr is deprecated - no ARM64 support (project abandoned by upstream)"
10768
+ },
10769
+ homepage: { icon: "readarr.png", widget: "readarr" },
10770
+ logVolume: "/config/logs"
10771
+ },
10772
+ bazarr: {
10773
+ id: "bazarr",
10774
+ name: "Bazarr",
10775
+ description: "Subtitle manager for Sonarr/Radarr",
10776
+ category: "servarr",
10777
+ defaultPort: 6767,
10778
+ image: "lscr.io/linuxserver/bazarr:latest",
10779
+ puid: 13013,
10780
+ pgid: 13000,
10781
+ volumes: (root) => [`${root}/config/bazarr:/config`, `${root}/data/media:/data/media`],
10782
+ dependsOn: ["sonarr", "radarr"],
10783
+ trashGuide: "docs/Bazarr/",
10784
+ apiKeyMeta: {
10785
+ configFile: "config/config.yaml",
10786
+ parser: "yaml",
10787
+ selector: "auth.apikey"
10788
+ },
10789
+ homepage: { icon: "bazarr.png", widget: "bazarr" },
10790
+ logVolume: "/config/log"
10791
+ },
10792
+ mylar3: {
10793
+ id: "mylar3",
10794
+ name: "Mylar3",
10795
+ description: "Comic book collection manager",
10796
+ category: "servarr",
10797
+ defaultPort: 8090,
10798
+ image: "lscr.io/linuxserver/mylar3:latest",
10799
+ puid: 13005,
10800
+ pgid: 13000,
10801
+ volumes: (root) => [`${root}/config/mylar3:/config`, `${root}/data:/data`],
10802
+ apiKeyMeta: {
10803
+ configFile: "mylar/config.ini",
10804
+ parser: "ini",
10805
+ section: "API",
10806
+ selector: "api_key",
10807
+ enabledKey: "api_enabled",
10808
+ generateIfMissing: true
10809
+ },
10810
+ prowlarrCategoryIds: [7030],
10811
+ homepage: { icon: "mylar.png", widget: "mylar" },
10812
+ logVolume: "/app/mylar/logs"
10813
+ },
10814
+ whisparr: {
10815
+ id: "whisparr",
10816
+ name: "Whisparr",
10817
+ description: "Adult media collection manager",
10818
+ category: "servarr",
10819
+ defaultPort: 6969,
10820
+ image: "ghcr.io/hotio/whisparr:nightly",
10821
+ puid: 13015,
10822
+ pgid: 13000,
10823
+ volumes: (root) => [`${root}/config/whisparr:/config`, `${root}/data:/data`],
10824
+ apiKeyMeta: {
10825
+ configFile: "config.xml",
10826
+ parser: "regex",
10827
+ selector: "<ApiKey>(.*?)</ApiKey>"
10828
+ },
10829
+ rootFolder: {
10830
+ path: "/data/media/adult",
10831
+ apiVersion: "v3"
10832
+ },
10833
+ prowlarrCategoryIds: [6000, 6010, 6020, 6030, 6040, 6045, 6050, 6060, 6070, 6080, 6090],
10834
+ homepage: { icon: "whisparr.png", widget: "sonarr" },
10835
+ logVolume: "/config/logs"
10836
+ },
10837
+ audiobookshelf: {
10838
+ id: "audiobookshelf",
10839
+ name: "Audiobookshelf",
10840
+ description: "Audiobook and podcast server",
10841
+ category: "servarr",
10842
+ defaultPort: 13378,
10843
+ image: "ghcr.io/advplyr/audiobookshelf:latest",
10844
+ puid: 13014,
10845
+ pgid: 13000,
10846
+ volumes: (root) => [
10847
+ `${root}/config/audiobookshelf:/config`,
10848
+ `${root}/data/media/audiobooks:/audiobooks`,
10849
+ `${root}/data/media/podcasts:/podcasts`,
10850
+ `${root}/data/media/audiobookshelf-metadata:/metadata`
10851
+ ],
10852
+ homepage: { icon: "audiobookshelf.png", widget: "audiobookshelf" },
10853
+ logVolume: "/config/metadata/logs"
10854
+ },
10855
+ prowlarr: {
10856
+ id: "prowlarr",
10857
+ name: "Prowlarr",
10858
+ description: "Indexer manager for *arr apps",
10859
+ category: "indexer",
10860
+ defaultPort: 9696,
10861
+ image: "lscr.io/linuxserver/prowlarr:develop",
10862
+ puid: 13006,
10863
+ pgid: 13000,
10864
+ volumes: (root) => [`${root}/config/prowlarr:/config`],
10865
+ trashGuide: "docs/Prowlarr/",
10866
+ apiKeyMeta: {
10867
+ configFile: "config.xml",
10868
+ parser: "regex",
10869
+ selector: "<ApiKey>(.*?)</ApiKey>"
10870
+ },
10871
+ homepage: { icon: "prowlarr.png", widget: "prowlarr" },
10872
+ logVolume: "/config/logs"
10873
+ },
10874
+ jackett: {
10875
+ id: "jackett",
10876
+ name: "Jackett",
10877
+ description: "Alternative indexer manager",
10878
+ category: "indexer",
10879
+ defaultPort: 9117,
10880
+ image: "lscr.io/linuxserver/jackett:latest",
10881
+ puid: 13008,
10882
+ pgid: 13000,
10883
+ volumes: (root) => [`${root}/config/jackett:/config`],
10884
+ apiKeyMeta: {
10885
+ configFile: "Jackett/ServerConfig.json",
10886
+ parser: "json",
10887
+ selector: "APIKey"
10888
+ },
10889
+ homepage: { icon: "jackett.png", widget: "jackett" },
10890
+ logVolume: "/config/logs"
10891
+ },
10892
+ flaresolverr: {
10893
+ id: "flaresolverr",
10894
+ name: "FlareSolverr",
10895
+ description: "Cloudflare bypass proxy",
10896
+ category: "indexer",
10897
+ defaultPort: 8191,
10898
+ image: "ghcr.io/flaresolverr/flaresolverr:latest",
10899
+ puid: 0,
10900
+ pgid: 0,
10901
+ volumes: () => [],
10902
+ environment: {
10903
+ LOG_LEVEL: "info",
10904
+ LOG_HTML: "false",
10905
+ CAPTCHA_SOLVER: "none"
10906
+ },
10907
+ homepage: {
10908
+ icon: "flaresolverr.png",
10909
+ widget: "customapi",
10910
+ widgetFields: {
10911
+ url: "http://flaresolverr:8191",
10912
+ mappings: JSON.stringify([
10913
+ { field: "msg", label: "Status" },
10914
+ { field: "version", label: "Version" }
10915
+ ])
10916
+ }
10917
+ }
10918
+ },
10919
+ qbittorrent: {
10920
+ id: "qbittorrent",
10921
+ name: "qBittorrent",
10922
+ description: "BitTorrent client",
10923
+ category: "downloader",
10924
+ defaultPort: 8080,
10925
+ image: "lscr.io/linuxserver/qbittorrent:latest",
10926
+ puid: 13007,
10927
+ pgid: 13000,
10928
+ volumes: (root) => [`${root}/config/qbittorrent:/config`, `${root}/data:/data`],
10929
+ environment: { WEBUI_PORT: "8080" },
10930
+ secrets: [
10931
+ {
10932
+ name: "USERNAME_QBITTORRENT",
10933
+ description: "Username for qBittorrent WebUI",
10934
+ required: false,
10935
+ default: "admin"
10936
+ },
10937
+ {
10938
+ name: "PASSWORD_QBITTORRENT",
10939
+ description: "Password for qBittorrent WebUI",
10940
+ required: false,
10941
+ mask: true
10942
+ }
10943
+ ],
10944
+ trashGuide: "docs/Downloaders/qBittorrent/",
10945
+ homepage: { icon: "qbittorrent.png", widget: "qbittorrent" },
10946
+ logVolume: "/config/qBittorrent/logs"
10947
+ },
10948
+ sabnzbd: {
10949
+ id: "sabnzbd",
10950
+ name: "SABnzbd",
10951
+ description: "Usenet downloader",
10952
+ category: "downloader",
10953
+ defaultPort: 8081,
10954
+ image: "lscr.io/linuxserver/sabnzbd:latest",
10955
+ puid: 13011,
10956
+ pgid: 13000,
10957
+ volumes: (root) => [`${root}/config/sabnzbd:/config`, `${root}/data:/data`],
10958
+ trashGuide: "docs/Downloaders/SABnzbd/",
10959
+ apiKeyMeta: {
10960
+ configFile: "sabnzbd.ini",
10961
+ parser: "regex",
10962
+ selector: "api_key\\s*=\\s*(.+)"
10963
+ },
10964
+ homepage: { icon: "sabnzbd.png", widget: "sabnzbd" },
10965
+ logVolume: "/config/logs"
10966
+ },
10967
+ slskd: {
10968
+ id: "slskd",
10969
+ name: "Slskd",
10970
+ description: "Soulseek client for music downloads",
10971
+ category: "downloader",
10972
+ defaultPort: 5030,
10973
+ image: "slskd/slskd",
10974
+ puid: 0,
10975
+ pgid: 0,
10976
+ useDockerUser: true,
10977
+ volumes: (root) => [`${root}/config/slskd:/app`, `${root}/data:/data`],
10978
+ secondaryPorts: ["5031:5031", "50300:50300"],
10979
+ environment: {
10980
+ SLSKD_REMOTE_CONFIGURATION: "true",
10981
+ SLSKD_USERNAME: "${USERNAME_GLOBAL}",
10982
+ SLSKD_PASSWORD: "${PASSWORD_GLOBAL}",
10983
+ SLSKD_SLSK_USERNAME: "${USERNAME_SOULSEEK}",
10984
+ SLSKD_SLSK_PASSWORD: "${PASSWORD_SOULSEEK}"
10985
+ },
10986
+ secrets: [
10987
+ {
10988
+ name: "USERNAME_SOULSEEK",
10989
+ description: "Soulseek network username (your Soulseek account)",
10990
+ required: true
10991
+ },
10992
+ {
10993
+ name: "PASSWORD_SOULSEEK",
10994
+ description: "Soulseek network password",
10995
+ required: true,
10996
+ mask: true
10997
+ }
10998
+ ],
10999
+ homepage: { icon: "slskd.png", widget: "slskd" },
11000
+ autoSetup: {
11001
+ type: "partial",
11002
+ description: "Generates slskd.yml with API key for Homepage widget and Soularr integration"
11003
+ },
11004
+ logVolume: "/app/logs"
11005
+ },
11006
+ soularr: {
11007
+ id: "soularr",
11008
+ name: "Soularr",
11009
+ description: "Connects Lidarr with Soulseek via Slskd",
11010
+ category: "downloader",
11011
+ defaultPort: 0,
11012
+ image: "mrusse08/soularr:latest",
11013
+ puid: 13017,
11014
+ pgid: 13000,
11015
+ volumes: (root) => [`${root}/data/slskd_downloads:/downloads`, `${root}/config/soularr:/data`],
11016
+ environment: {
11017
+ SCRIPT_INTERVAL: "300"
11018
+ },
11019
+ dependsOn: ["lidarr", "slskd"]
11020
+ },
11021
+ plex: {
11022
+ id: "plex",
11023
+ name: "Plex",
11024
+ description: "Media server with streaming",
11025
+ category: "mediaserver",
11026
+ defaultPort: 32400,
11027
+ image: "lscr.io/linuxserver/plex:latest",
11028
+ puid: 13010,
11029
+ pgid: 13000,
11030
+ volumes: (root) => [`${root}/config/plex:/config`, `${root}/data/media:/data/media`],
11031
+ environment: { VERSION: "docker", PLEX_CLAIM: "${PLEX_CLAIM}" },
11032
+ trashGuide: "docs/Plex/",
11033
+ apiKeyMeta: {
11034
+ configFile: "Library/Application Support/Plex Media Server/Preferences.xml",
11035
+ parser: "regex",
11036
+ selector: 'PlexOnlineToken="([^"]+)"'
11037
+ },
11038
+ homepage: { icon: "plex.png", widget: "plex" },
11039
+ logVolume: "/config/Library/Application Support/Plex Media Server/Logs",
11040
+ autoSetup: {
11041
+ type: "full",
11042
+ description: "Claim server with token, create media libraries",
11043
+ envVars: ["PLEX_CLAIM"]
11044
+ }
11045
+ },
11046
+ jellyfin: {
11047
+ id: "jellyfin",
11048
+ name: "Jellyfin",
11049
+ description: "Free open-source media server",
11050
+ category: "mediaserver",
11051
+ defaultPort: 8096,
11052
+ image: "lscr.io/linuxserver/jellyfin:latest",
11053
+ puid: 0,
11054
+ pgid: 13000,
11055
+ volumes: (root) => [`${root}/config/jellyfin:/config`, `${root}/data/media:/data/media`],
11056
+ homepage: { icon: "jellyfin.png", widget: "jellyfin" },
11057
+ logVolume: "/config/log"
11058
+ },
11059
+ tautulli: {
11060
+ id: "tautulli",
11061
+ name: "Tautulli",
11062
+ description: "Plex monitoring and statistics",
11063
+ category: "mediaserver",
11064
+ defaultPort: 8181,
11065
+ image: "lscr.io/linuxserver/tautulli:latest",
11066
+ puid: 0,
11067
+ pgid: 13000,
11068
+ volumes: (root) => [`${root}/config/tautulli:/config`],
11069
+ dependsOn: ["plex"],
11070
+ apiKeyMeta: {
11071
+ configFile: "config.ini",
11072
+ parser: "regex",
11073
+ selector: "api_key\\s*=\\s*(.+)"
11074
+ },
11075
+ homepage: { icon: "tautulli.png", widget: "tautulli" },
11076
+ autoSetup: {
11077
+ type: "partial",
11078
+ description: "Connect to Plex, enable API",
11079
+ requires: ["plex"]
11080
+ },
11081
+ logVolume: "/config/logs"
11082
+ },
11083
+ tdarr: {
11084
+ id: "tdarr",
11085
+ name: "Tdarr",
11086
+ description: "Audio/video transcoding automation",
11087
+ category: "mediaserver",
11088
+ defaultPort: 8265,
11089
+ image: "ghcr.io/haveagitgat/tdarr:latest",
11090
+ puid: 0,
11091
+ pgid: 13000,
11092
+ volumes: (root) => [
11093
+ `${root}/config/tdarr/server:/app/server`,
11094
+ `${root}/config/tdarr/configs:/app/configs`,
11095
+ `${root}/config/tdarr/logs:/app/logs`,
11096
+ `${root}/data/media:/data`
11097
+ ],
11098
+ environment: { serverIP: "0.0.0.0", internalNode: "true" },
11099
+ homepage: { icon: "tdarr.png", widget: "tdarr" },
11100
+ logVolume: "/app/logs"
11101
+ },
11102
+ overseerr: {
11103
+ id: "overseerr",
11104
+ name: "Overseerr",
11105
+ description: "Request management for Plex",
11106
+ category: "request",
11107
+ defaultPort: 5055,
11108
+ image: "sctx/overseerr:latest",
11109
+ puid: 13009,
11110
+ pgid: 13000,
11111
+ volumes: (root) => [`${root}/config/overseerr:/app/config`],
11112
+ dependsOn: ["plex"],
11113
+ apiKeyMeta: {
11114
+ configFile: "settings.json",
11115
+ parser: "json",
11116
+ selector: "main.apiKey"
11117
+ },
11118
+ homepage: { icon: "overseerr.png", widget: "overseerr" },
11119
+ autoSetup: {
11120
+ type: "full",
11121
+ description: "Connect to Plex, configure Radarr/Sonarr",
11122
+ requires: ["plex"]
11123
+ },
11124
+ logVolume: "/app/config/logs"
11125
+ },
11126
+ jellyseerr: {
11127
+ id: "jellyseerr",
11128
+ name: "Jellyseerr",
11129
+ description: "Request management for Jellyfin",
11130
+ category: "request",
11131
+ defaultPort: 5055,
11132
+ image: "fallenbagel/jellyseerr:latest",
11133
+ puid: 13012,
11134
+ pgid: 13000,
11135
+ volumes: (root) => [`${root}/config/jellyseerr:/app/config`],
11136
+ dependsOn: ["jellyfin"],
11137
+ apiKeyMeta: {
11138
+ configFile: "settings.json",
11139
+ parser: "json",
11140
+ selector: "main.apiKey"
11141
+ },
11142
+ homepage: { icon: "jellyseerr.png", widget: "jellyseerr" },
11143
+ logVolume: "/app/config/logs"
11144
+ },
11145
+ homarr: {
11146
+ id: "homarr",
11147
+ name: "Homarr",
11148
+ description: "Modern dashboard for all services",
11149
+ category: "dashboard",
11150
+ defaultPort: 7575,
11151
+ image: "ghcr.io/ajnart/homarr:latest",
11152
+ puid: 0,
11153
+ pgid: 0,
11154
+ volumes: (root) => [
11155
+ `${root}/config/homarr/configs:/app/data/configs`,
11156
+ `${root}/config/homarr/icons:/app/public/icons`,
11157
+ `${root}/config/homarr/data:/data`,
11158
+ "/var/run/docker.sock:/var/run/docker.sock"
11159
+ ],
11160
+ homepage: { icon: "homarr.png" },
11161
+ logVolume: "/app/data/logs"
11162
+ },
11163
+ heimdall: {
11164
+ id: "heimdall",
11165
+ name: "Heimdall",
11166
+ description: "Application dashboard and launcher",
11167
+ category: "dashboard",
11168
+ defaultPort: 8082,
11169
+ image: "lscr.io/linuxserver/heimdall:latest",
11170
+ puid: 0,
11171
+ pgid: 13000,
11172
+ volumes: (root) => [`${root}/config/heimdall:/config`],
11173
+ homepage: { icon: "heimdall.png" },
11174
+ logVolume: "/config/log"
11175
+ },
11176
+ homepage: {
11177
+ id: "homepage",
11178
+ name: "Homepage",
11179
+ description: "Highly customizable application dashboard",
11180
+ category: "dashboard",
11181
+ defaultPort: 3009,
11182
+ internalPort: 3000,
11183
+ image: "ghcr.io/gethomepage/homepage:latest",
11184
+ puid: 0,
11185
+ pgid: 0,
11186
+ volumes: (root) => [
11187
+ `${root}/config/homepage:/app/config`,
11188
+ "/var/run/docker.sock:/var/run/docker.sock"
11189
+ ],
11190
+ environment: {
11191
+ HOMEPAGE_ALLOWED_HOSTS: "homepage,homepage.${CLOUDFLARE_DNS_ZONE},${CLOUDFLARE_DNS_ZONE},localhost,${LOCAL_DOCKER_IP},${LOCAL_DOCKER_IP}:3009"
11192
+ },
11193
+ logVolume: "/app/config/logs"
11194
+ },
11195
+ portainer: {
11196
+ id: "portainer",
11197
+ name: "Portainer",
11198
+ description: "Docker container management UI",
11199
+ category: "utility",
11200
+ defaultPort: 9000,
11201
+ image: "portainer/portainer-ce:latest",
11202
+ puid: 0,
11203
+ pgid: 0,
11204
+ volumes: (root) => [
11205
+ `${root}/config/portainer:/data`,
11206
+ "/var/run/docker.sock:/var/run/docker.sock"
11207
+ ],
11208
+ minPasswordLength: 12,
11209
+ homepage: { icon: "portainer.png", widget: "portainer" },
11210
+ logVolume: "/data/logs"
11211
+ },
11212
+ huntarr: {
11213
+ id: "huntarr",
11214
+ name: "Huntarr",
11215
+ description: "Missing content manager for *arr apps",
11216
+ category: "utility",
11217
+ defaultPort: 9705,
11218
+ image: "huntarr/huntarr:latest",
11219
+ puid: 0,
11220
+ pgid: 13000,
11221
+ volumes: (root) => [`${root}/config/huntarr:/config`],
11222
+ dependsOn: ["sonarr", "radarr", "lidarr", "readarr"],
11223
+ homepage: {
11224
+ icon: "huntarr.png",
11225
+ widget: "customapi",
11226
+ widgetFields: {
11227
+ url: "http://huntarr:9705/api/cycle/status",
11228
+ mappings: JSON.stringify([
11229
+ { field: "sonarr.next_cycle", label: "Sonarr", format: "relativeDate" },
11230
+ { field: "radarr.next_cycle", label: "Radarr", format: "relativeDate" },
11231
+ { field: "lidarr.next_cycle", label: "Lidarr", format: "relativeDate" },
11232
+ { field: "readarr.next_cycle", label: "Readarr", format: "relativeDate" },
11233
+ { field: "whisparr.next_cycle", label: "Whisparr", format: "relativeDate" }
11234
+ ])
11235
+ }
11236
+ },
11237
+ autoSetup: {
11238
+ type: "full",
11239
+ description: "Test connections to Sonarr, Radarr, Lidarr, Readarr, Whisparr",
11240
+ requires: ["sonarr", "radarr"]
11241
+ },
11242
+ logVolume: "/config/logs"
11243
+ },
11244
+ maintainerr: {
11245
+ id: "maintainerr",
11246
+ name: "Maintainerr",
11247
+ description: "Automated media management and cleanup for Plex",
11248
+ category: "utility",
11249
+ defaultPort: 6246,
11250
+ image: "ghcr.io/maintainerr/maintainerr:latest",
11251
+ puid: 0,
11252
+ pgid: 13000,
11253
+ volumes: (root) => [`${root}/config/maintainerr:/opt/data`],
11254
+ dependsOn: ["plex"],
11255
+ homepage: {
11256
+ icon: "maintainerr.png",
11257
+ widget: "customapi",
11258
+ widgetFields: {
11259
+ url: "http://maintainerr:6246/api/settings/version",
11260
+ mappings: JSON.stringify([{ field: "version", label: "Version" }])
11261
+ }
11262
+ },
11263
+ autoSetup: {
11264
+ type: "full",
11265
+ description: "Generate API key, configure Plex/Radarr/Sonarr/Overseerr/Tautulli connections",
11266
+ requires: ["plex"]
11267
+ },
11268
+ logVolume: "/opt/data/logs"
11269
+ },
11270
+ unpackerr: {
11271
+ id: "unpackerr",
11272
+ name: "Unpackerr",
11273
+ description: "Archive extraction for *arr apps",
11274
+ category: "utility",
11275
+ defaultPort: 5656,
11276
+ image: "golift/unpackerr",
11277
+ puid: 0,
11278
+ pgid: 13000,
11279
+ volumes: (root) => [`${root}/config/unpackerr:/config`, `${root}/data:/data`],
11280
+ logVolume: "/config/logs"
11281
+ },
11282
+ filebot: {
11283
+ id: "filebot",
11284
+ name: "FileBot",
11285
+ description: "Media file renaming and automator",
11286
+ category: "utility",
11287
+ defaultPort: 5452,
11288
+ image: "rednoah/filebot",
11289
+ puid: 13000,
11290
+ pgid: 13000,
11291
+ volumes: (root) => [`${root}/config/filebot:/data`, `${root}/data:/data`],
11292
+ environment: { DARK_MODE: "1" },
11293
+ logVolume: "/data/logs"
11294
+ },
11295
+ chromium: {
11296
+ id: "chromium",
11297
+ name: "Chromium",
11298
+ description: "Web browser for secure remote browsing",
11299
+ category: "utility",
11300
+ defaultPort: 3000,
11301
+ image: "lscr.io/linuxserver/chromium:latest",
11302
+ puid: 13000,
11303
+ pgid: 13000,
11304
+ volumes: (root) => [`${root}/config/chromium:/config`],
11305
+ environment: { TITLE: "Chromium" },
11306
+ logVolume: "/config/logs"
11307
+ },
11308
+ guacamole: {
11309
+ id: "guacamole",
11310
+ name: "Guacamole",
11311
+ description: "Clientless remote desktop gateway",
11312
+ category: "utility",
11313
+ defaultPort: 8080,
11314
+ image: "guacamole/guacamole",
11315
+ puid: 0,
11316
+ pgid: 0,
11317
+ volumes: (root) => [`${root}/config/guacamole:/config`],
11318
+ environment: {
11319
+ WEBAPP_CONTEXT: "ROOT",
11320
+ GUACD_HOSTNAME: "guacd",
11321
+ POSTGRESQL_HOSTNAME: "postgresql",
11322
+ POSTGRESQL_DATABASE: "guacamole",
11323
+ POSTGRESQL_USER: "${USERNAME_POSTGRESQL}",
11324
+ POSTGRESQL_PASSWORD: "${PASSWORD_POSTGRESQL}"
11325
+ },
11326
+ dependsOn: ["guacd", "postgresql"],
11327
+ secrets: [
11328
+ {
11329
+ name: "USERNAME_POSTGRESQL",
11330
+ description: "PostgreSQL Username",
11331
+ required: true,
11332
+ default: "postgres"
11333
+ },
11334
+ {
11335
+ name: "PASSWORD_POSTGRESQL",
11336
+ description: "PostgreSQL Password",
11337
+ required: true,
11338
+ mask: true
11339
+ }
11340
+ ],
11341
+ logVolume: "/guacamole/logs"
11342
+ },
11343
+ guacd: {
11344
+ id: "guacd",
11345
+ name: "Guacd",
11346
+ description: "Guacamole proxy daemon",
11347
+ category: "utility",
11348
+ defaultPort: 4822,
11349
+ image: "guacamole/guacd",
11350
+ puid: 0,
11351
+ pgid: 13000,
11352
+ volumes: (root) => [`${root}/config/guacd:/config`],
11353
+ dependsOn: ["postgresql"],
11354
+ logVolume: "/guacd/logs"
11355
+ },
11356
+ "ddns-updater": {
11357
+ id: "ddns-updater",
11358
+ name: "DDNS-Updater",
11359
+ description: "Dynamic DNS record updater",
11360
+ category: "utility",
11361
+ defaultPort: 8000,
11362
+ image: "qmcgaw/ddns-updater",
11363
+ puid: 13000,
11364
+ pgid: 13000,
11365
+ volumes: (root) => [`${root}/config/ddns-updater:/data`],
11366
+ logVolume: "/data/logs"
11367
+ },
11368
+ easiarr: {
11369
+ id: "easiarr",
11370
+ name: "easiarr",
11371
+ description: "Exposes easiarr config and bookmarks for Homepage dashboard",
11372
+ category: "utility",
11373
+ defaultPort: 3010,
11374
+ internalPort: 8080,
11375
+ image: "halverneus/static-file-server:latest",
11376
+ puid: 0,
11377
+ pgid: 0,
11378
+ volumes: () => [
11379
+ "${XDG_CONFIG_HOME}/easiarr/config.json:/web/config.json:ro",
11380
+ "${XDG_CONFIG_HOME}/easiarr/bookmarks-local.html:/web/bookmarks-local.html:ro",
11381
+ "${XDG_CONFIG_HOME}/easiarr/bookmarks-remote.html:/web/bookmarks-remote.html:ro"
11382
+ ],
11383
+ environment: {
11384
+ FOLDER: "/web",
11385
+ CORS: "true"
11386
+ },
11387
+ homepage: {
11388
+ icon: "mdi-docker",
11389
+ widget: "customapi",
11390
+ widgetFields: {
11391
+ url: "http://easiarr:8080/config.json",
11392
+ mappings: JSON.stringify([
11393
+ { field: "version", label: "Version" },
11394
+ { field: "apps.length", label: "Apps" }
11395
+ ])
11396
+ }
11397
+ }
11398
+ },
11399
+ recyclarr: {
11400
+ id: "recyclarr",
11401
+ name: "Recyclarr",
11402
+ description: "Automatic TRaSH Guides profile sync for *arr apps",
11403
+ category: "utility",
11404
+ defaultPort: 0,
11405
+ image: "ghcr.io/recyclarr/recyclarr:latest",
11406
+ puid: 0,
11407
+ pgid: 0,
11408
+ useDockerUser: true,
11409
+ volumes: (root) => [`${root}/config/recyclarr:/config`],
11410
+ environment: {
11411
+ RECYCLARR_CREATE_CONFIG: "false",
11412
+ CRON_SCHEDULE: "@daily"
11413
+ },
11414
+ dependsOn: ["radarr", "sonarr"],
11415
+ autoSetup: {
11416
+ type: "full",
11417
+ description: "Generate recyclarr.yml config for enabled *arr apps with TRaSH profiles",
11418
+ requires: ["radarr", "sonarr"]
11419
+ }
11420
+ },
11421
+ profilarr: {
11422
+ id: "profilarr",
11423
+ name: "Profilarr",
11424
+ description: "Web UI for managing TRaSH Guides profiles (alternative to Recyclarr)",
11425
+ category: "utility",
11426
+ defaultPort: 6868,
11427
+ image: "santiagosayshey/profilarr:latest",
11428
+ puid: 1000,
11429
+ pgid: 1000,
11430
+ volumes: (root) => [`${root}/config/profilarr:/config`],
11431
+ dependsOn: ["radarr", "sonarr"],
11432
+ homepage: {
11433
+ icon: "profilarr"
11434
+ }
11435
+ },
11436
+ gluetun: {
11437
+ id: "gluetun",
11438
+ name: "Gluetun",
11439
+ description: "VPN client container for routing traffic",
11440
+ category: "vpn",
11441
+ defaultPort: 8888,
11442
+ image: "qmcgaw/gluetun:latest",
11443
+ puid: 0,
11444
+ pgid: 0,
11445
+ cap_add: ["NET_ADMIN"],
11446
+ devices: ["/dev/net/tun:/dev/net/tun"],
11447
+ volumes: (root) => [`${root}/config/gluetun:/gluetun`],
11448
+ environment: {
11449
+ VPN_SERVICE_PROVIDER: "${VPN_SERVICE_PROVIDER}",
11450
+ OPENVPN_USER: "${USERNAME_VPN}",
11451
+ OPENVPN_PASSWORD: "${PASSWORD_VPN}",
11452
+ WIREGUARD_PRIVATE_KEY: "${WIREGUARD_PRIVATE_KEY}",
11453
+ HTTPPROXY: "on",
11454
+ SHADOWSOCKS: "on"
11455
+ },
11456
+ secrets: [
11457
+ {
11458
+ name: "VPN_SERVICE_PROVIDER",
11459
+ description: "VPN Provider (e.g. custom, airvpn)",
11460
+ required: true,
11461
+ default: "custom"
11462
+ },
11463
+ {
11464
+ name: "USERNAME_VPN",
11465
+ description: "OpenVPN Username",
11466
+ required: false
11467
+ },
11468
+ {
11469
+ name: "PASSWORD_VPN",
11470
+ description: "OpenVPN Password",
11471
+ required: false,
11472
+ mask: true
11473
+ },
11474
+ {
11475
+ name: "WIREGUARD_PRIVATE_KEY",
11476
+ description: "WireGuard Private Key",
11477
+ required: false,
11478
+ mask: true
11479
+ }
11480
+ ],
11481
+ homepage: { icon: "gluetun.png", widget: "gluetun" },
11482
+ logVolume: "/gluetun"
11483
+ },
11484
+ grafana: {
11485
+ id: "grafana",
11486
+ name: "Grafana",
11487
+ description: "Visual monitoring dashboard",
11488
+ category: "monitoring",
11489
+ defaultPort: 3001,
11490
+ image: "grafana/grafana-enterprise",
11491
+ puid: 0,
11492
+ pgid: 13000,
11493
+ volumes: (root) => [`${root}/config/grafana:/var/lib/grafana`],
11494
+ homepage: { icon: "grafana.png", widget: "grafana" },
11495
+ autoSetup: {
11496
+ type: "full",
11497
+ description: "Setup admin user, configure Prometheus datasource",
11498
+ requires: ["prometheus"]
11499
+ },
11500
+ logVolume: "/var/log/grafana"
11501
+ },
11502
+ prometheus: {
11503
+ id: "prometheus",
11504
+ name: "Prometheus",
11505
+ description: "Systems and service monitoring",
11506
+ category: "monitoring",
11507
+ defaultPort: 9090,
11508
+ image: "prom/prometheus",
11509
+ puid: 0,
11510
+ pgid: 13000,
11511
+ volumes: (root) => [`${root}/config/prometheus:/prometheus`],
11512
+ homepage: { icon: "prometheus.png", widget: "prometheus" },
11513
+ logVolume: "/prometheus/logs"
11514
+ },
11515
+ dozzle: {
11516
+ id: "dozzle",
11517
+ name: "Dozzle",
11518
+ description: "Real-time log viewer for Docker containers",
11519
+ category: "monitoring",
11520
+ defaultPort: 8080,
11521
+ image: "amir20/dozzle",
11522
+ puid: 0,
11523
+ pgid: 0,
11524
+ volumes: () => ["/var/run/docker.sock:/var/run/docker.sock"]
11525
+ },
11526
+ "uptime-kuma": {
11527
+ id: "uptime-kuma",
11528
+ name: "Uptime Kuma",
11529
+ description: "Self-hosted monitoring tool",
11530
+ category: "monitoring",
11531
+ defaultPort: 3001,
11532
+ image: "louislam/uptime-kuma:1",
11533
+ puid: 0,
11534
+ pgid: 0,
11535
+ volumes: (root) => [
11536
+ `${root}/config/uptime-kuma:/app/data`,
11537
+ "/var/run/docker.sock:/var/run/docker.sock"
11538
+ ],
11539
+ homepage: { icon: "uptime-kuma.png", widget: "uptimekuma" },
11540
+ autoSetup: {
11541
+ type: "full",
11542
+ description: "Create admin user, add monitors for enabled apps"
11543
+ },
11544
+ logVolume: "/app/data/logs"
11545
+ },
11546
+ traefik: {
11547
+ id: "traefik",
11548
+ name: "Traefik",
11549
+ description: "Reverse proxy and load balancer",
11550
+ category: "infrastructure",
11551
+ defaultPort: 80,
11552
+ internalPort: 80,
11553
+ image: "traefik:latest",
11554
+ puid: 0,
11555
+ pgid: 0,
11556
+ volumes: (root) => [
11557
+ `${root}/config/traefik:/etc/traefik`,
11558
+ `${root}/config/traefik/letsencrypt:/letsencrypt`,
11559
+ "/var/run/docker.sock:/var/run/docker.sock:ro"
11560
+ ],
11561
+ secondaryPorts: ["8083:8080"],
11562
+ secrets: [
11563
+ {
11564
+ name: "CLOUDFLARE_DNS_ZONE",
11565
+ description: "Root Domain (e.g. example.com)",
11566
+ required: true
11567
+ }
11568
+ ],
11569
+ homepage: { icon: "traefik.png", widget: "traefik" },
11570
+ logVolume: "/etc/traefik/logs"
11571
+ },
11572
+ caddy: {
11573
+ id: "caddy",
11574
+ name: "Caddy",
11575
+ description: "Web server with automatic HTTPS and reverse proxy",
11576
+ category: "infrastructure",
11577
+ defaultPort: 80,
11578
+ internalPort: 80,
11579
+ secondaryPorts: ["443:443"],
11580
+ image: "caddy:latest",
11581
+ puid: 0,
11582
+ pgid: 0,
11583
+ volumes: (root) => [
11584
+ `${root}/config/caddy/Caddyfile:/etc/caddy/Caddyfile`,
11585
+ `${root}/config/caddy/data:/data`,
11586
+ `${root}/config/caddy/config:/config`
11587
+ ],
11588
+ homepage: { icon: "caddy.png" },
11589
+ logVolume: "/data/logs"
11590
+ },
11591
+ cloudflared: {
11592
+ id: "cloudflared",
11593
+ name: "Cloudflared",
11594
+ description: "Cloudflare Tunnel for secure external access without port forwarding",
11595
+ category: "infrastructure",
11596
+ defaultPort: 0,
11597
+ image: "cloudflare/cloudflared:latest",
11598
+ puid: 0,
11599
+ pgid: 0,
11600
+ volumes: () => [],
11601
+ environment: {
11602
+ TUNNEL_TOKEN: "${CLOUDFLARE_TUNNEL_TOKEN}"
11603
+ },
11604
+ command: "tunnel run",
11605
+ dependsOn: ["traefik"],
11606
+ secrets: [
11607
+ {
11608
+ name: "CLOUDFLARE_API_TOKEN",
11609
+ description: "Cloudflare API Token (for automated tunnel setup via Menu)",
11610
+ required: false,
11611
+ mask: true
11612
+ },
11613
+ {
11614
+ name: "CLOUDFLARE_TUNNEL_TOKEN",
11615
+ description: "Cloudflare Tunnel Token (auto-generated or from Zero Trust)",
11616
+ required: true,
11617
+ mask: true
11618
+ }
11619
+ ],
11620
+ homepage: { icon: "cloudflare-zero-trust.png", widget: "cloudflared" }
11621
+ },
11622
+ "traefik-certs-dumper": {
11623
+ id: "traefik-certs-dumper",
11624
+ name: "Traefik Certs Dumper",
11625
+ description: "Extracts certificates from Traefik",
11626
+ category: "infrastructure",
11627
+ defaultPort: 0,
11628
+ image: "ldez/traefik-certs-dumper:latest",
11629
+ puid: 0,
11630
+ pgid: 0,
11631
+ volumes: (root) => [
11632
+ `${root}/config/traefik/letsencrypt:/traefik:ro`,
11633
+ `${root}/config/traefik/certs:/output`
11634
+ ],
11635
+ dependsOn: ["traefik"]
11636
+ },
11637
+ crowdsec: {
11638
+ id: "crowdsec",
11639
+ name: "CrowdSec",
11640
+ description: "Intrusion prevention system",
11641
+ category: "infrastructure",
11642
+ defaultPort: 8080,
11643
+ image: "crowdsecurity/crowdsec:latest",
11644
+ puid: 0,
11645
+ pgid: 0,
11646
+ volumes: (root) => [
11647
+ `${root}/config/crowdsec:/etc/crowdsec`,
11648
+ "/var/run/docker.sock:/var/run/docker.sock:ro"
11649
+ ],
11650
+ homepage: { icon: "crowdsec.png", widget: "crowdsec" }
11651
+ },
11652
+ headscale: {
11653
+ id: "headscale",
11654
+ name: "Headscale",
11655
+ description: "Open-source Tailscale control server",
11656
+ category: "infrastructure",
11657
+ defaultPort: 8084,
11658
+ image: "headscale/headscale:latest",
11659
+ puid: 0,
11660
+ pgid: 0,
11661
+ volumes: (root) => [
11662
+ `${root}/config/headscale:/etc/headscale`,
11663
+ `${root}/config/headscale/data:/var/lib/headscale`
11664
+ ],
11665
+ homepage: { icon: "headscale.png", widget: "headscale" }
11666
+ },
11667
+ headplane: {
11668
+ id: "headplane",
11669
+ name: "Headplane",
11670
+ description: "Headscale web UI",
11671
+ category: "infrastructure",
11672
+ defaultPort: 3000,
11673
+ image: "ghcr.io/tale/headplane:latest",
11674
+ puid: 0,
11675
+ pgid: 0,
11676
+ volumes: (root) => [`${root}/config/headplane:/config`],
11677
+ dependsOn: ["headscale"]
11678
+ },
11679
+ tailscale: {
11680
+ id: "tailscale",
11681
+ name: "Tailscale",
11682
+ description: "VPN mesh network client",
11683
+ category: "infrastructure",
11684
+ defaultPort: 0,
11685
+ image: "tailscale/tailscale:latest",
11686
+ puid: 0,
11687
+ pgid: 0,
11688
+ cap_add: ["NET_ADMIN"],
11689
+ devices: ["/dev/net/tun:/dev/net/tun"],
11690
+ volumes: (root) => [`${root}/config/tailscale:/var/lib/tailscale`],
11691
+ secrets: [
11692
+ {
11693
+ name: "TAILSCALE_AUTHKEY",
11694
+ description: "Tailscale Auth Key",
11695
+ required: true,
11696
+ mask: true
11697
+ }
11698
+ ],
11699
+ homepage: { icon: "tailscale.png", widget: "tailscale" }
11700
+ },
11701
+ authentik: {
11702
+ id: "authentik",
11703
+ name: "Authentik",
11704
+ description: "Identity provider and SSO (Server)",
11705
+ category: "infrastructure",
11706
+ defaultPort: 9001,
11707
+ image: "ghcr.io/goauthentik/server:latest",
11708
+ puid: 0,
11709
+ pgid: 13000,
11710
+ volumes: (root) => [
11711
+ `${root}/config/authentik/media:/media`,
11712
+ `${root}/config/authentik/templates:/templates`
11713
+ ],
11714
+ environment: {
11715
+ AUTHENTIK_REDIS__HOST: "valkey",
11716
+ AUTHENTIK_POSTGRESQL__HOST: "postgresql",
11717
+ AUTHENTIK_POSTGRESQL__NAME: "authentik",
11718
+ AUTHENTIK_POSTGRESQL__USER: "${USERNAME_POSTGRESQL}",
11719
+ AUTHENTIK_POSTGRESQL__PASSWORD: "${PASSWORD_POSTGRESQL}",
11720
+ AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
11721
+ },
11722
+ dependsOn: ["postgresql", "valkey", "authentik-worker"],
11723
+ secrets: [
11724
+ {
11725
+ name: "AUTHENTIK_SECRET_KEY",
11726
+ description: "Authentik Secret Key",
11727
+ required: true,
11728
+ mask: true,
11729
+ generate: true
11730
+ },
11731
+ {
11732
+ name: "USERNAME_POSTGRESQL",
11733
+ description: "Postgres Username",
11734
+ required: true,
11735
+ default: "postgres"
11736
+ },
11737
+ {
11738
+ name: "PASSWORD_POSTGRESQL",
11739
+ description: "Postgres Password",
11740
+ required: true,
11741
+ mask: true
11742
+ }
11743
+ ],
11744
+ homepage: { icon: "authentik.png", widget: "authentik" }
11745
+ },
11746
+ "authentik-worker": {
11747
+ id: "authentik-worker",
11748
+ name: "Authentik Worker",
11749
+ description: "Identity provider background worker",
11750
+ category: "infrastructure",
11751
+ defaultPort: 0,
11752
+ image: "ghcr.io/goauthentik/server:latest",
11753
+ puid: 0,
11754
+ pgid: 13000,
11755
+ volumes: (root) => [
11756
+ `${root}/config/authentik/media:/media`,
11757
+ `${root}/config/authentik/templates:/templates`,
11758
+ `${root}/config/authentik/certs:/certs`,
11759
+ "/var/run/docker.sock:/var/run/docker.sock"
11760
+ ],
11761
+ environment: {
11762
+ AUTHENTIK_REDIS__HOST: "valkey",
11763
+ AUTHENTIK_POSTGRESQL__HOST: "postgresql",
11764
+ AUTHENTIK_POSTGRESQL__NAME: "authentik",
11765
+ AUTHENTIK_POSTGRESQL__USER: "${USERNAME_POSTGRESQL}",
11766
+ AUTHENTIK_POSTGRESQL__PASSWORD: "${PASSWORD_POSTGRESQL}",
11767
+ AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
11768
+ },
11769
+ dependsOn: ["postgresql", "valkey"]
11770
+ },
11771
+ postgresql: {
11772
+ id: "postgresql",
11773
+ name: "PostgreSQL",
11774
+ description: "Database server",
11775
+ category: "infrastructure",
11776
+ defaultPort: 5432,
11777
+ image: "docker.io/library/postgres:latest",
11778
+ puid: 0,
11779
+ pgid: 13000,
11780
+ volumes: (root) => [`${root}/config/postgresql:/var/lib/postgresql/data`],
11781
+ environment: {
11782
+ POSTGRES_USER: "${USERNAME_POSTGRESQL}",
11783
+ POSTGRES_PASSWORD: "${PASSWORD_POSTGRESQL}",
11784
+ POSTGRES_DB: "authentik"
11785
+ },
11786
+ secrets: [
11787
+ {
11788
+ name: "USERNAME_POSTGRESQL",
11789
+ description: "PostgreSQL Username",
11790
+ required: true,
11791
+ default: "postgres"
11792
+ },
11793
+ {
11794
+ name: "PASSWORD_POSTGRESQL",
11795
+ description: "PostgreSQL Password",
11796
+ required: true,
11797
+ mask: true
11798
+ }
11799
+ ]
11800
+ },
11801
+ valkey: {
11802
+ id: "valkey",
11803
+ name: "Valkey",
11804
+ description: "Redis-compatible key-value store",
11805
+ category: "infrastructure",
11806
+ defaultPort: 6379,
11807
+ image: "valkey/valkey:alpine",
11808
+ puid: 0,
11809
+ pgid: 13000,
11810
+ volumes: (root) => [`${root}/config/valkey:/data`]
11811
+ }
11812
+ };
11813
+ function getAppsByCategory() {
11814
+ const result = {};
11815
+ for (const app of Object.values(APPS)) {
11816
+ if (!result[app.category]) {
11817
+ result[app.category] = [];
11818
+ }
11819
+ result[app.category].push(app);
11820
+ }
11821
+ return result;
11822
+ }
11823
+ function getApp(id) {
11824
+ return APPS[id];
11825
+ }
11826
+
11897
11827
  // src/config/homepage-config.ts
11898
- init_registry();
11899
11828
  init_env();
11900
11829
  init_schema();
11901
11830
  function getHomepageConfigPath(config) {
@@ -12317,7 +12246,6 @@ async function saveRecyclarrConfig(config) {
12317
12246
  }
12318
12247
  // src/config/bookmarks-generator.ts
12319
12248
  import { writeFile as writeFile5 } from "fs/promises";
12320
- init_registry();
12321
12249
  init_env();
12322
12250
  init_paths();
12323
12251
  init_schema();
@@ -12674,7 +12602,7 @@ class AdvancedSettings {
12674
12602
  }
12675
12603
  const children = this.container.getChildren();
12676
12604
  for (const child of children) {
12677
- this.container.remove(child.id);
12605
+ this.container.remove(child);
12678
12606
  }
12679
12607
  }
12680
12608
  renderMenu() {
@@ -12783,7 +12711,6 @@ class AdvancedSettings {
12783
12711
  }
12784
12712
 
12785
12713
  // src/ui/screens/AppManager.ts
12786
- init_registry();
12787
12714
  import {
12788
12715
  BoxRenderable as BoxRenderable7,
12789
12716
  RGBA as RGBA3,
@@ -12793,18 +12720,16 @@ import {
12793
12720
  } from "@opentui/core";
12794
12721
 
12795
12722
  // src/compose/generator.ts
12796
- init_registry();
12723
+ import { writeFile as writeFile9 } from "fs/promises";
12797
12724
  init_manager();
12798
12725
  init_debug();
12799
12726
  init_env();
12800
- import { writeFile as writeFile9 } from "fs/promises";
12801
12727
 
12802
12728
  // src/compose/caddy-config.ts
12803
- init_registry();
12804
- init_debug();
12805
12729
  import { existsSync as existsSync6 } from "fs";
12806
12730
  import { mkdir as mkdir4, writeFile as writeFile7 } from "fs/promises";
12807
12731
  import { join as join5 } from "path";
12732
+ init_debug();
12808
12733
  function generateCaddyfile(config) {
12809
12734
  const domain = config.caddy?.domain || "localhost";
12810
12735
  const email = config.caddy?.email;
@@ -13219,7 +13144,6 @@ async function updateEnvFile(config) {
13219
13144
  });
13220
13145
  }
13221
13146
  // src/structure/manager.ts
13222
- init_registry();
13223
13147
  import { mkdir as mkdir6 } from "fs/promises";
13224
13148
  import { join as join7 } from "path";
13225
13149
  var BASE_DIRS = ["torrents", "usenet", "media"];
@@ -13317,11 +13241,6 @@ import {
13317
13241
  TabSelectRenderable,
13318
13242
  TextRenderable as TextRenderable4
13319
13243
  } from "@opentui/core";
13320
-
13321
- // src/apps/index.ts
13322
- init_registry();
13323
-
13324
- // src/ui/components/ApplicationSelector.ts
13325
13244
  class ApplicationSelector extends BoxRenderable5 {
13326
13245
  selectedApps;
13327
13246
  onToggle;
@@ -13417,7 +13336,7 @@ class ApplicationSelector extends BoxRenderable5 {
13417
13336
  }
13418
13337
  updateWarnings() {
13419
13338
  const children = this.warningBox.getChildren();
13420
- children.forEach((c) => this.warningBox.remove(c.id));
13339
+ children.forEach((c) => this.warningBox.remove(c));
13421
13340
  const warnings = this.getConflictWarnings();
13422
13341
  warnings.forEach((w, i) => {
13423
13342
  this.warningBox.add(new TextRenderable4(this._renderer, {
@@ -13513,8 +13432,6 @@ class ApplicationSelector extends BoxRenderable5 {
13513
13432
  }
13514
13433
 
13515
13434
  // src/ui/screens/SecretsEditor.ts
13516
- init_registry();
13517
- init_env();
13518
13435
  import { mkdir as mkdir7 } from "fs/promises";
13519
13436
  import { dirname } from "path";
13520
13437
  import {
@@ -13523,6 +13440,7 @@ import {
13523
13440
  RGBA as RGBA2,
13524
13441
  TextRenderable as TextRenderable5
13525
13442
  } from "@opentui/core";
13443
+ init_env();
13526
13444
 
13527
13445
  class SecretsEditor extends BoxRenderable6 {
13528
13446
  inputs = new Map;
@@ -13695,7 +13613,7 @@ class AppManager {
13695
13613
  }
13696
13614
  const children = this.container.getChildren();
13697
13615
  for (const child of children) {
13698
- this.container.remove(child.id);
13616
+ this.container.remove(child);
13699
13617
  }
13700
13618
  const { container: page, content } = createPageLayout(this.renderer, {
13701
13619
  title: "Manage Apps",
@@ -13850,7 +13768,7 @@ class AppManager {
13850
13768
  ]
13851
13769
  });
13852
13770
  menu.on(SelectRenderableEvents3.ITEM_SELECTED, (index) => {
13853
- this.container.remove("secrets-prompt-overlay");
13771
+ this.container.remove(overlay);
13854
13772
  if (index === 0) {
13855
13773
  const editor = new SecretsEditor(this.renderer, {
13856
13774
  id: "secrets-editor-overlay",
@@ -13858,11 +13776,11 @@ class AppManager {
13858
13776
  height: "100%",
13859
13777
  config: this.config,
13860
13778
  onSave: () => {
13861
- this.container.remove("secrets-editor-overlay");
13779
+ this.container.remove(editor);
13862
13780
  this.app.navigateTo("main");
13863
13781
  },
13864
13782
  onCancel: () => {
13865
- this.container.remove("secrets-editor-overlay");
13783
+ this.container.remove(editor);
13866
13784
  this.app.navigateTo("main");
13867
13785
  }
13868
13786
  });
@@ -14144,8 +14062,7 @@ class ContainerControl {
14144
14062
  clearPanel(panel) {
14145
14063
  const children = [...panel.getChildren()];
14146
14064
  for (const child of children) {
14147
- if (child.id)
14148
- panel.remove(child.id);
14065
+ panel.remove(child);
14149
14066
  }
14150
14067
  }
14151
14068
  updatePanels() {
@@ -14483,7 +14400,6 @@ import { randomBytes } from "crypto";
14483
14400
  import { existsSync as existsSync8, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
14484
14401
  import { join as join8 } from "path";
14485
14402
  import { BoxRenderable as BoxRenderable9, TextRenderable as TextRenderable8 } from "@opentui/core";
14486
- init_registry();
14487
14403
  init_env();
14488
14404
  function generateApiKey() {
14489
14405
  return randomBytes(16).toString("hex");
@@ -14778,11 +14694,9 @@ class ApiKeyViewer extends BoxRenderable9 {
14778
14694
  destroy() {
14779
14695
  this.cliRenderer.keyInput.off("keypress", this.keyHandler);
14780
14696
  if (this.parent) {
14781
- if (this.id) {
14782
- try {
14783
- this.parent.remove(this.id);
14784
- } catch {}
14785
- }
14697
+ try {
14698
+ this.parent.remove(this);
14699
+ } catch {}
14786
14700
  }
14787
14701
  }
14788
14702
  async saveToEnv() {
@@ -15024,10 +14938,26 @@ class ArrApiClient {
15024
14938
  return this.request("/rootfolder");
15025
14939
  }
15026
14940
  async addRootFolder(pathOrOptions) {
15027
- const body = typeof pathOrOptions === "string" ? { path: pathOrOptions } : pathOrOptions;
14941
+ const options = typeof pathOrOptions === "string" ? { path: pathOrOptions } : { ...pathOrOptions };
14942
+ if (!options.name) {
14943
+ const folderName = options.path.split("/").filter(Boolean).pop() || "Media";
14944
+ options.name = folderName.charAt(0).toUpperCase() + folderName.slice(1);
14945
+ }
14946
+ if (!options.defaultQualityProfileId) {
14947
+ const qProfiles = await this.getQualityProfiles();
14948
+ if (qProfiles.length > 0) {
14949
+ options.defaultQualityProfileId = qProfiles[0].id;
14950
+ }
14951
+ }
14952
+ if (!options.defaultMetadataProfileId) {
14953
+ const mProfiles = await this.getMetadataProfiles();
14954
+ if (mProfiles.length > 0) {
14955
+ options.defaultMetadataProfileId = mProfiles[0].id;
14956
+ }
14957
+ }
15028
14958
  return this.request("/rootfolder", {
15029
14959
  method: "POST",
15030
- body: JSON.stringify(body)
14960
+ body: JSON.stringify(options)
15031
14961
  });
15032
14962
  }
15033
14963
  async getMetadataProfiles() {
@@ -15341,9 +15271,6 @@ class QBittorrentClient extends BaseApiClient {
15341
15271
  }
15342
15272
  }
15343
15273
 
15344
- // src/ui/screens/AppConfigurator.ts
15345
- init_registry();
15346
-
15347
15274
  // src/ui/components/CredentialsForm.ts
15348
15275
  import { BoxRenderable as BoxRenderable10, InputRenderable as InputRenderable2, TextRenderable as TextRenderable9 } from "@opentui/core";
15349
15276
 
@@ -15699,7 +15626,8 @@ class AppConfigurator extends BoxRenderable12 {
15699
15626
  try {
15700
15627
  const appConfig = this.config.apps.find((a) => a.id === appId);
15701
15628
  const port = appConfig?.port ?? appDef.defaultPort;
15702
- const client = new ArrApiClient(appId, port, apiKey);
15629
+ const apiVersion = appDef.rootFolder?.apiVersion || "v3";
15630
+ const client = new ArrApiClient("localhost", port, apiKey, apiVersion);
15703
15631
  const rootFolders = await client.getRootFolders();
15704
15632
  const targetPath = appDef.rootFolder?.path;
15705
15633
  const hasCorrectRoot = rootFolders.some((rf) => rf.path === targetPath);
@@ -15709,7 +15637,12 @@ class AppConfigurator extends BoxRenderable12 {
15709
15637
  return;
15710
15638
  }
15711
15639
  if (!hasCorrectRoot && targetPath) {
15712
- await client.addRootFolder(targetPath);
15640
+ const options = { path: targetPath };
15641
+ if (appId === "lidarr")
15642
+ options.name = "Music";
15643
+ if (appId === "readarr")
15644
+ options.name = "Books";
15645
+ await client.addRootFolder(options);
15713
15646
  }
15714
15647
  result.status = "success";
15715
15648
  result.message = "Root folder set";
@@ -15741,11 +15674,9 @@ class AppConfigurator extends BoxRenderable12 {
15741
15674
  updateDisplay() {
15742
15675
  const contentChildren = [...this.contentBox.getChildren()];
15743
15676
  for (const child of contentChildren) {
15744
- if (child.id) {
15745
- try {
15746
- this.contentBox.remove(child.id);
15747
- } catch {}
15748
- }
15677
+ try {
15678
+ this.contentBox.remove(child);
15679
+ } catch {}
15749
15680
  }
15750
15681
  this.contentBox.add(new TextRenderable11(this.cliRenderer, {
15751
15682
  content: `Configuring *arr applications...
@@ -15922,20 +15853,18 @@ class AppConfigurator extends BoxRenderable12 {
15922
15853
  clear() {
15923
15854
  const children = [...this.getChildren()];
15924
15855
  for (const child of children) {
15925
- if (child.id) {
15926
- try {
15927
- this.remove(child.id);
15928
- } catch {}
15929
- }
15856
+ try {
15857
+ this.remove(child);
15858
+ } catch {}
15930
15859
  }
15931
15860
  }
15932
15861
  destroy() {
15933
15862
  if (this.keyHandler) {
15934
15863
  this.cliRenderer.keyInput.off("keypress", this.keyHandler);
15935
15864
  }
15936
- if (this.parent && this.id) {
15865
+ if (this.parent) {
15937
15866
  try {
15938
- this.parent.remove(this.id);
15867
+ this.parent.remove(this);
15939
15868
  } catch {}
15940
15869
  }
15941
15870
  }
@@ -16098,8 +16027,7 @@ class LogsViewer extends BoxRenderable13 {
16098
16027
  clearContent() {
16099
16028
  const children = [...this.content.getChildren()];
16100
16029
  for (const child of children) {
16101
- if (child.id)
16102
- this.content.remove(child.id);
16030
+ this.content.remove(child);
16103
16031
  }
16104
16032
  }
16105
16033
  renderContent() {
@@ -16297,7 +16225,7 @@ Start containers first to view logs.`,
16297
16225
  }
16298
16226
  rebuildUI() {
16299
16227
  if (this.page) {
16300
- this.remove(this.page.id);
16228
+ this.remove(this.page);
16301
16229
  }
16302
16230
  this.buildUI();
16303
16231
  }
@@ -16414,9 +16342,9 @@ import {
16414
16342
  TabSelectRenderableEvents as TabSelectRenderableEvents2,
16415
16343
  TextRenderable as TextRenderable13
16416
16344
  } from "@opentui/core";
16417
- init_registry();
16418
16345
  init_manager();
16419
16346
  init_schema();
16347
+ init_env();
16420
16348
  var DEFAULT_CHECKS = {
16421
16349
  health: true,
16422
16350
  diskspace: true,
@@ -16604,7 +16532,7 @@ class MonitorDashboard extends BoxRenderable14 {
16604
16532
  }
16605
16533
  clearPanel(panel) {
16606
16534
  const children = panel.getChildren();
16607
- children.forEach((c) => panel.remove(c.id));
16535
+ children.forEach((c) => panel.remove(c));
16608
16536
  }
16609
16537
  renderCategoriesPanel() {
16610
16538
  this.clearPanel(this.categoriesPanel);
@@ -16863,8 +16791,7 @@ Press 'r' to refresh`,
16863
16791
  const selectedApp = apps[this.appIndex];
16864
16792
  if (!selectedApp)
16865
16793
  return;
16866
- const { readEnv: readEnv2 } = await Promise.resolve().then(() => (init_env(), exports_env));
16867
- const env = await readEnv2();
16794
+ const env = await readEnv();
16868
16795
  const apiKey = env[`API_KEY_${selectedApp.id.toUpperCase()}`];
16869
16796
  if (!apiKey) {
16870
16797
  this.statusData.set(selectedApp.id, {
@@ -17046,9 +16973,9 @@ Press 'r' to refresh`,
17046
16973
  }
17047
16974
  cleanup() {
17048
16975
  this._renderer.keyInput.off("keypress", this.keyHandler);
17049
- if (this.parent && this.id) {
16976
+ if (this.parent) {
17050
16977
  try {
17051
- this.parent.remove(this.id);
16978
+ this.parent.remove(this);
17052
16979
  } catch {}
17053
16980
  }
17054
16981
  }
@@ -17109,7 +17036,7 @@ class SettingsScreen extends BoxRenderable15 {
17109
17036
  }
17110
17037
  const children = this.getChildren();
17111
17038
  for (const child of children) {
17112
- this.remove(child.id);
17039
+ this.remove(child);
17113
17040
  }
17114
17041
  const { container: page, content } = createPageLayout(this.cliRenderer, {
17115
17042
  title: "Settings",
@@ -17542,7 +17469,7 @@ class SettingsScreen extends BoxRenderable15 {
17542
17469
  }
17543
17470
  const parent = this.parent;
17544
17471
  if (parent) {
17545
- parent.remove(this.id);
17472
+ parent.remove(this);
17546
17473
  }
17547
17474
  }
17548
17475
  }
@@ -18997,7 +18924,6 @@ class QualityProfileClient {
18997
18924
  }
18998
18925
 
18999
18926
  // src/setup/actions/arr-common.ts
19000
- init_registry();
19001
18927
  init_debug();
19002
18928
 
19003
18929
  // src/utils/url-utils.ts
@@ -19054,6 +18980,8 @@ async function setupArrRootFolders(ctx) {
19054
18980
  const options = { path: def.rootFolder.path };
19055
18981
  if (app.id === "lidarr")
19056
18982
  options.name = "Music";
18983
+ if (app.id === "readarr")
18984
+ options.name = "Books";
19057
18985
  await client.addRootFolder(options);
19058
18986
  configured++;
19059
18987
  }
@@ -19499,7 +19427,6 @@ class JellyseerrClient extends BaseApiClient {
19499
19427
  }
19500
19428
 
19501
19429
  // src/setup/actions/jellyseerr.ts
19502
- init_registry();
19503
19430
  init_debug();
19504
19431
  async function checkJellyseerrPrerequisites(ctx) {
19505
19432
  const jellyseerrConfig = getEnabledAppConfig(ctx, "jellyseerr");
@@ -19996,7 +19923,6 @@ var PROWLARR_CATEGORIES = [
19996
19923
  ];
19997
19924
 
19998
19925
  // src/setup/actions/prowlarr.ts
19999
- init_registry();
20000
19926
  var ARR_APP_TYPES = {
20001
19927
  radarr: "Radarr",
20002
19928
  sonarr: "Sonarr",
@@ -23568,7 +23494,6 @@ Object.assign(lookup, {
23568
23494
  });
23569
23495
 
23570
23496
  // src/api/uptime-kuma-api.ts
23571
- init_registry();
23572
23497
  init_debug();
23573
23498
 
23574
23499
  class UptimeKumaClient {
@@ -23874,7 +23799,6 @@ async function setupTautulli(ctx) {
23874
23799
  }
23875
23800
  }
23876
23801
  // src/api/heimdall-api.ts
23877
- init_registry();
23878
23802
  class HeimdallClient extends BaseApiClient {
23879
23803
  logPrefix = "HeimdallApi";
23880
23804
  constructor(host, port = 80) {
@@ -23960,7 +23884,6 @@ class HeimdallClient extends BaseApiClient {
23960
23884
  }
23961
23885
 
23962
23886
  // src/api/homarr-api.ts
23963
- init_registry();
23964
23887
  init_debug();
23965
23888
  class HomarrClient extends BaseApiClient {
23966
23889
  logPrefix = "HomarrApi";
@@ -24385,13 +24308,12 @@ class HuntarrClient extends BaseApiClient {
24385
24308
  return { success: false, message: "Huntarr not reachable" };
24386
24309
  }
24387
24310
  const version = await this.getVersion();
24388
- const { getApp: getApp2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
24389
24311
  for (const appType of HUNTARR_APP_TYPES) {
24390
24312
  const apiKeyEnvName = `API_KEY_${appType.toUpperCase()}`;
24391
24313
  const apiKey = env[apiKeyEnvName];
24392
24314
  if (!apiKey)
24393
24315
  continue;
24394
- const appDef = getApp2(appType);
24316
+ const appDef = getApp(appType);
24395
24317
  const port = appDef?.defaultPort ?? 8989;
24396
24318
  const apiUrl = `http://${appType}:${port}`;
24397
24319
  const result = await this.testConnection(appType, apiUrl, apiKey);
@@ -24428,7 +24350,6 @@ class HuntarrClient extends BaseApiClient {
24428
24350
  }
24429
24351
  async setupEasiarrApps(apps, env) {
24430
24352
  const results = [];
24431
- const { getApp: getApp2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
24432
24353
  for (const appConfig of apps) {
24433
24354
  if (!appConfig.enabled)
24434
24355
  continue;
@@ -24438,7 +24359,7 @@ class HuntarrClient extends BaseApiClient {
24438
24359
  const apiKey = env[`API_KEY_${appType.toUpperCase()}`];
24439
24360
  if (!apiKey)
24440
24361
  continue;
24441
- const appDef = getApp2(appType);
24362
+ const appDef = getApp(appType);
24442
24363
  const port = appDef?.defaultPort ?? 8989;
24443
24364
  const apiUrl = `http://${appType}:${port}`;
24444
24365
  const instanceName = appDef?.name ?? appType;
@@ -24453,7 +24374,7 @@ class HuntarrClient extends BaseApiClient {
24453
24374
  if (prowlarrConfig) {
24454
24375
  const prowlarrApiKey = env["API_KEY_PROWLARR"];
24455
24376
  if (prowlarrApiKey) {
24456
- const prowlarrDef = getApp2("prowlarr");
24377
+ const prowlarrDef = getApp("prowlarr");
24457
24378
  const prowlarrPort = prowlarrConfig.port || prowlarrDef?.defaultPort || 9696;
24458
24379
  const prowlarrUrl = `http://prowlarr:${prowlarrPort}`;
24459
24380
  const configured = await this.configureProwlarr(prowlarrUrl, prowlarrApiKey);
@@ -25234,9 +25155,6 @@ class ProfilarrApiClient {
25234
25155
  }
25235
25156
  }
25236
25157
 
25237
- // src/setup/actions/utilities.ts
25238
- init_registry();
25239
-
25240
25158
  // src/config/slskd-config.ts
25241
25159
  init_env();
25242
25160
  import { randomBytes as randomBytes2 } from "crypto";
@@ -26270,7 +26188,6 @@ Press Enter to go back.
26270
26188
  }
26271
26189
 
26272
26190
  // src/ui/screens/setup/HomepageSetup.ts
26273
- init_registry();
26274
26191
  import { TextRenderable as TextRenderable18 } from "@opentui/core";
26275
26192
  class HomepageSetup extends BaseAppSetupScreen {
26276
26193
  previewContent = "";
@@ -26832,7 +26749,6 @@ class JellyseerrSetup extends BaseAppSetupScreen {
26832
26749
 
26833
26750
  // src/ui/screens/setup/ProwlarrSetup.ts
26834
26751
  import { TextNodeRenderable as TextNodeRenderable2, TextRenderable as TextRenderable21 } from "@opentui/core";
26835
- init_registry();
26836
26752
  init_env();
26837
26753
  var ARR_APP_TYPES2 = {
26838
26754
  radarr: "Radarr",
@@ -27970,9 +27886,6 @@ function getCFNamesForCategories(app, categories) {
27970
27886
  return categories.flatMap((cat) => cfNames[cat] || []);
27971
27887
  }
27972
27888
 
27973
- // src/ui/screens/setup/TRaSHProfileSetup.ts
27974
- init_registry();
27975
-
27976
27889
  // src/data/lidarr-custom-formats.ts
27977
27890
  var CF_PREFERRED_GROUPS = {
27978
27891
  name: "Preferred Groups",
@@ -28676,7 +28589,7 @@ class QuickSetup {
28676
28589
  }
28677
28590
  const children = this.container.getChildren();
28678
28591
  for (const child of children) {
28679
- this.container.remove(child.id);
28592
+ this.container.remove(child);
28680
28593
  }
28681
28594
  switch (this.step) {
28682
28595
  case "welcome":
@@ -28806,7 +28719,7 @@ class QuickSetup {
28806
28719
  }
28807
28720
  const children = this.container.getChildren();
28808
28721
  for (const child of children) {
28809
- this.container.remove(child.id);
28722
+ this.container.remove(child);
28810
28723
  }
28811
28724
  const { container: page, content } = createPageLayout(this.renderer, {
28812
28725
  title: "About easiarr",
@@ -29645,7 +29558,7 @@ class App {
29645
29558
  this.currentScreen = screen;
29646
29559
  const children = this.screenContainer.getChildren();
29647
29560
  for (const child of children) {
29648
- this.screenContainer.remove(child.id);
29561
+ this.screenContainer.remove(child);
29649
29562
  }
29650
29563
  switch (screen) {
29651
29564
  case "main":