@muhammedaksam/easiarr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Muhammed Mustafa AKŞAM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # easiarr
2
+
3
+ > **It could be easiarr.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@muhammedaksam/easiarr.svg)](https://www.npmjs.com/package/@muhammedaksam/easiarr)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Bun](https://img.shields.io/badge/Bun-000?logo=bun&logoColor=fff)](https://bun.sh/)
9
+ [![CI](https://github.com/muhammedaksam/easiarr/workflows/CI/badge.svg)](https://github.com/muhammedaksam/easiarr/actions)
10
+
11
+ > ⚠️ **Work In Progress** - This project is in early experimental development. Features may be incomplete, unstable, or change without notice.
12
+
13
+ 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.
14
+
15
+ A terminal-based wizard that helps you set up Radarr, Sonarr, Prowlarr, and other \*arr applications with Docker Compose, following best practices from [TRaSH Guides](https://trash-guides.info/).
16
+
17
+ ## Features
18
+
19
+ - 📦 **Quick Setup Wizard** - Get started in minutes with a guided setup flow
20
+ - 🐳 **Docker Compose Generation** - Automatically generates optimized `docker-compose.yml`
21
+ - ✅ **TRaSH Guides Compliant** - Follows best practices for folder structure and hardlinks
22
+ - 🎮 **Container Control** - Start, stop, and restart containers directly from the TUI
23
+ - ⚙️ **App Management** - Add or remove apps from your stack with ease
24
+ - 💾 **Persistent Configuration** - Settings saved to `~/.easiarr/config.json`
25
+
26
+ ## Quick Start
27
+
28
+ ### Run directly with bunx (no installation required)
29
+
30
+ ```bash
31
+ bunx @muhammedaksam/easiarr
32
+ ```
33
+
34
+ ### Or install globally
35
+
36
+ ```bash
37
+ bun add -g @muhammedaksam/easiarr
38
+ easiarr
39
+ ```
40
+
41
+ ### Or clone and run locally
42
+
43
+ ```bash
44
+ git clone https://github.com/muhammedaksam/easiarr.git
45
+ cd easiarr
46
+ bun install
47
+ bun run start
48
+ ```
49
+
50
+ ## Requirements
51
+
52
+ - [Bun](https://bun.sh/) >= 1.0
53
+ - [Docker](https://www.docker.com/) with Docker Compose v2
54
+
55
+ ## Supported Applications (41 apps across 10 categories)
56
+
57
+ ### Media Management (Servarr)
58
+
59
+ - **Radarr** - Movie collection manager
60
+ - **Sonarr** - TV series collection manager
61
+ - **Lidarr** - Music collection manager
62
+ - **Readarr** - Book collection manager
63
+ - **Bazarr** - Subtitle manager for Sonarr/Radarr
64
+ - **Mylar3** - Comic book collection manager
65
+ - **Whisparr** - Adult media collection manager
66
+ - **Audiobookshelf** - Audiobook and podcast server
67
+
68
+ ### Indexers
69
+
70
+ - **Prowlarr** - Indexer manager for \*arr apps
71
+ - **Jackett** - Alternative indexer manager
72
+ - **FlareSolverr** - Cloudflare bypass proxy
73
+
74
+ ### Download Clients
75
+
76
+ - **qBittorrent** - BitTorrent client
77
+ - **SABnzbd** - Usenet downloader
78
+
79
+ ### Media Servers
80
+
81
+ - **Plex** - Media server with streaming
82
+ - **Jellyfin** - Free open-source media server
83
+ - **Tautulli** - Plex monitoring and statistics
84
+ - **Tdarr** - Audio/video transcoding automation
85
+
86
+ ### Request Management
87
+
88
+ - **Overseerr** - Request management for Plex
89
+ - **Jellyseerr** - Request management for Jellyfin
90
+
91
+ ### Dashboards
92
+
93
+ - **Homarr** - Modern dashboard for all services
94
+ - **Heimdall** - Application dashboard and launcher
95
+ - **Homepage** - Highly customizable application dashboard
96
+
97
+ ### Utilities
98
+
99
+ - **Portainer** - Docker container management UI
100
+ - **Huntarr** - Missing content manager for \*arr apps
101
+ - **Unpackerr** - Archive extraction for \*arr apps
102
+ - **FileBot** - Media file renaming and automator
103
+ - **Chromium** - Web browser for secure remote browsing
104
+ - **Guacamole** - Clientless remote desktop gateway
105
+ - **DDNS-Updater** - Dynamic DNS record updater
106
+
107
+ ### VPN
108
+
109
+ - **Gluetun** - VPN client container for routing traffic
110
+
111
+ ### Monitoring
112
+
113
+ - **Grafana** - Visual monitoring dashboard
114
+ - **Prometheus** - Systems and service monitoring
115
+ - **Dozzle** - Real-time log viewer for Docker containers
116
+ - **Uptime Kuma** - Self-hosted monitoring tool
117
+
118
+ ### Infrastructure
119
+
120
+ - **Traefik** - Reverse proxy and load balancer
121
+ - **Traefik Certs Dumper** - Extracts certificates from Traefik
122
+ - **CrowdSec** - Intrusion prevention system
123
+ - **Headscale** - Open-source Tailscale control server
124
+ - **Headplane** - Headscale web UI
125
+ - **Tailscale** - VPN mesh network client
126
+ - **Authentik** - Identity provider and SSO
127
+ - **PostgreSQL** - Database server
128
+ - **Valkey** - Redis-compatible key-value store
129
+
130
+ ## Configuration
131
+
132
+ easiarr stores its configuration in `~/.easiarr/`:
133
+
134
+ ```bash
135
+ ~/.easiarr/
136
+ ├── config.json # Your easiarr configuration
137
+ ├── docker-compose.yml # Generated Docker Compose file
138
+ └── backups/ # Configuration backups
139
+ ```
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ # Install dependencies
145
+ bun install
146
+
147
+ # Run in development mode (with watch)
148
+ bun run dev
149
+
150
+ # Type check
151
+ bun run typecheck
152
+
153
+ # Lint
154
+ bun run lint
155
+
156
+ # Format
157
+ bun run format
158
+
159
+ # Run all checks (typecheck + lint + format:check)
160
+ bun run check
161
+
162
+ # Fix all issues (lint:fix + format)
163
+ bun run fix
164
+ ```
165
+
166
+ ## License
167
+
168
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
169
+
170
+ ## Related Projects
171
+
172
+ - [TRaSH Guides](https://trash-guides.info/) - Quality guides for Radarr, Sonarr, and more
173
+ - [OpenTUI](https://github.com/opentui/opentui) - Terminal UI framework used by easiarr
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@muhammedaksam/easiarr",
3
+ "version": "0.1.0",
4
+ "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",
5
+ "module": "src/index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "easiarr": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "author": "Muhammed Mustafa AKŞAM <info@muhammedaksam.com.tr> (https://github.com/muhammedaksam)",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/muhammedaksam/easiarr.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/muhammedaksam/easiarr/issues"
21
+ },
22
+ "homepage": "https://github.com/muhammedaksam/easiarr#readme",
23
+ "keywords": [
24
+ "arr",
25
+ "radarr",
26
+ "sonarr",
27
+ "prowlarr",
28
+ "docker",
29
+ "docker-compose",
30
+ "trash-guides",
31
+ "tui",
32
+ "opentui",
33
+ "cli",
34
+ "media-server"
35
+ ],
36
+ "scripts": {
37
+ "dev": "bun run --watch src/index.ts",
38
+ "build": "bun build src/index.ts --outdir dist --target bun",
39
+ "typecheck": "bun x tsc --noEmit",
40
+ "lint": "eslint src/",
41
+ "lint:fix": "eslint src/ --fix",
42
+ "format": "prettier --write src/",
43
+ "format:check": "prettier --check src/",
44
+ "test": "jest",
45
+ "test:watch": "jest --watch",
46
+ "check": "bun run typecheck && bun run lint && bun run format:check && bun run test",
47
+ "fix": "bun run lint:fix && bun run format",
48
+ "start": "bun run src/index.ts"
49
+ },
50
+ "devDependencies": {
51
+ "@eslint/js": "^9.39.1",
52
+ "@types/bun": "latest",
53
+ "@types/jest": "^30.0.0",
54
+ "@types/node": "^25.0.0",
55
+ "eslint": "^9.39.1",
56
+ "jest": "^30.2.0",
57
+ "jiti": "^2.6.1",
58
+ "prettier": "^3.7.4",
59
+ "ts-jest": "^29.4.6",
60
+ "typescript-eslint": "^8.49.0",
61
+ "yaml": "^2.8.2"
62
+ },
63
+ "peerDependencies": {
64
+ "typescript": "^5.9.3"
65
+ },
66
+ "dependencies": {
67
+ "@opentui/core": "^0.1.60"
68
+ },
69
+ "engines": {
70
+ "bun": ">=1.0"
71
+ }
72
+ }
@@ -0,0 +1,12 @@
1
+ import packageJson from "../package.json" with { type: "json" }
2
+
3
+ const pkg = packageJson
4
+
5
+ export const VersionInfo = {
6
+ version: pkg.version,
7
+ name: pkg.name,
8
+ description: pkg.description,
9
+ author: pkg.author,
10
+ }
11
+
12
+ export const getVersion = () => `v${VersionInfo.version}`
@@ -0,0 +1,198 @@
1
+ /**
2
+ * *arr API Client
3
+ * Interacts with Radarr, Sonarr, Lidarr, Readarr, Whisparr APIs
4
+ */
5
+
6
+ // Types for Root Folder API
7
+ export interface RootFolder {
8
+ id?: number
9
+ path: string
10
+ accessible?: boolean
11
+ freeSpace?: number | null
12
+ unmappedFolders?: { name: string | null; path: string | null; relativePath: string | null }[]
13
+ }
14
+
15
+ // Types for Download Client API
16
+ export interface DownloadClientConfig {
17
+ name: string
18
+ implementation: string
19
+ configContract: string
20
+ enable?: boolean
21
+ priority?: number
22
+ fields: { name: string; value: unknown }[]
23
+ }
24
+
25
+ export interface DownloadClient extends DownloadClientConfig {
26
+ id?: number
27
+ }
28
+
29
+ import type { AppId } from "../config/schema"
30
+
31
+ // Get category name for an app
32
+ function getCategoryForApp(appId: AppId): string {
33
+ switch (appId) {
34
+ case "radarr":
35
+ return "movies"
36
+ case "sonarr":
37
+ return "tv"
38
+ case "lidarr":
39
+ return "music"
40
+ case "readarr":
41
+ return "books"
42
+ case "whisparr":
43
+ return "adult"
44
+ default:
45
+ return "default"
46
+ }
47
+ }
48
+
49
+ // Get category field name for an app (different apps use different field names)
50
+ function getCategoryFieldName(appId: AppId): string {
51
+ switch (appId) {
52
+ case "radarr":
53
+ case "whisparr":
54
+ return "movieCategory"
55
+ case "sonarr":
56
+ return "tvCategory"
57
+ case "lidarr":
58
+ return "musicCategory"
59
+ case "readarr":
60
+ return "bookCategory"
61
+ default:
62
+ return "category"
63
+ }
64
+ }
65
+
66
+ // qBittorrent download client config
67
+ export function createQBittorrentConfig(
68
+ host: string,
69
+ port: number,
70
+ username: string,
71
+ password: string,
72
+ appId?: AppId
73
+ ): DownloadClientConfig {
74
+ const category = appId ? getCategoryForApp(appId) : "default"
75
+ const categoryField = appId ? getCategoryFieldName(appId) : "category"
76
+
77
+ return {
78
+ name: "qBittorrent",
79
+ implementation: "QBittorrent",
80
+ configContract: "QBittorrentSettings",
81
+ enable: true,
82
+ priority: 1,
83
+ fields: [
84
+ { name: "host", value: host },
85
+ { name: "port", value: port },
86
+ { name: "username", value: username },
87
+ { name: "password", value: password },
88
+ { name: categoryField, value: category },
89
+ { name: "recentMoviePriority", value: 0 },
90
+ { name: "olderMoviePriority", value: 0 },
91
+ { name: "initialState", value: 0 },
92
+ { name: "sequentialOrder", value: false },
93
+ { name: "firstAndLast", value: false },
94
+ ],
95
+ }
96
+ }
97
+
98
+ // SABnzbd download client config
99
+ export function createSABnzbdConfig(host: string, port: number, apiKey: string, appId?: AppId): DownloadClientConfig {
100
+ const category = appId ? getCategoryForApp(appId) : "default"
101
+ const categoryField = appId ? getCategoryFieldName(appId) : "category"
102
+
103
+ return {
104
+ name: "SABnzbd",
105
+ implementation: "Sabnzbd",
106
+ configContract: "SabnzbdSettings",
107
+ enable: true,
108
+ priority: 1,
109
+ fields: [
110
+ { name: "host", value: host },
111
+ { name: "port", value: port },
112
+ { name: "apiKey", value: apiKey },
113
+ { name: categoryField, value: category },
114
+ { name: "recentMoviePriority", value: -100 },
115
+ { name: "olderMoviePriority", value: -100 },
116
+ ],
117
+ }
118
+ }
119
+
120
+ export type ApiVersion = "v1" | "v3"
121
+
122
+ /**
123
+ * *arr API Client
124
+ */
125
+ export class ArrApiClient {
126
+ private baseUrl: string
127
+ private apiKey: string
128
+ private apiVersion: ApiVersion
129
+
130
+ constructor(host: string, port: number, apiKey: string, apiVersion: ApiVersion = "v3") {
131
+ this.baseUrl = `http://${host}:${port}`
132
+ this.apiKey = apiKey
133
+ this.apiVersion = apiVersion
134
+ }
135
+
136
+ private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
137
+ const url = `${this.baseUrl}/api/${this.apiVersion}${endpoint}`
138
+ const headers = {
139
+ "X-Api-Key": this.apiKey,
140
+ "Content-Type": "application/json",
141
+ ...options.headers,
142
+ }
143
+
144
+ const response = await fetch(url, { ...options, headers })
145
+
146
+ if (!response.ok) {
147
+ throw new Error(`API request failed: ${response.status} ${response.statusText}`)
148
+ }
149
+
150
+ // Handle empty responses (DELETE returns empty body)
151
+ const text = await response.text()
152
+ if (!text) return {} as T
153
+
154
+ return JSON.parse(text) as T
155
+ }
156
+
157
+ // Root Folder methods
158
+ async getRootFolders(): Promise<RootFolder[]> {
159
+ return this.request<RootFolder[]>("/rootfolder")
160
+ }
161
+
162
+ async addRootFolder(path: string): Promise<RootFolder> {
163
+ return this.request<RootFolder>("/rootfolder", {
164
+ method: "POST",
165
+ body: JSON.stringify({ path }),
166
+ })
167
+ }
168
+
169
+ async deleteRootFolder(id: number): Promise<void> {
170
+ await this.request(`/rootfolder/${id}`, { method: "DELETE" })
171
+ }
172
+
173
+ // Download Client methods
174
+ async getDownloadClients(): Promise<DownloadClient[]> {
175
+ return this.request<DownloadClient[]>("/downloadclient")
176
+ }
177
+
178
+ async addDownloadClient(config: DownloadClientConfig): Promise<DownloadClient> {
179
+ return this.request<DownloadClient>("/downloadclient", {
180
+ method: "POST",
181
+ body: JSON.stringify(config),
182
+ })
183
+ }
184
+
185
+ async deleteDownloadClient(id: number): Promise<void> {
186
+ await this.request(`/downloadclient/${id}`, { method: "DELETE" })
187
+ }
188
+
189
+ // Health check
190
+ async isHealthy(): Promise<boolean> {
191
+ try {
192
+ await this.request("/system/status")
193
+ return true
194
+ } catch {
195
+ return false
196
+ }
197
+ }
198
+ }
@@ -0,0 +1 @@
1
+ export * from "./arr-api"
@@ -0,0 +1,14 @@
1
+ import type { AppCategory } from "../config/schema"
2
+
3
+ export const CATEGORY_ORDER: { id: AppCategory; short: string }[] = [
4
+ { id: "servarr", short: "Media" },
5
+ { id: "indexer", short: "Index" },
6
+ { id: "downloader", short: "DL" },
7
+ { id: "mediaserver", short: "Server" },
8
+ { id: "request", short: "Request" },
9
+ { id: "dashboard", short: "Dash" },
10
+ { id: "utility", short: "Utils" },
11
+ { id: "vpn", short: "VPN" },
12
+ { id: "monitoring", short: "Monitor" },
13
+ { id: "infrastructure", short: "Infra" },
14
+ ]
@@ -0,0 +1,2 @@
1
+ export * from "./registry"
2
+ export * from "./categories"