@muhammedaksam/easiarr 1.4.0 → 1.4.1
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/dist/index.js +29837 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
|
@@ -1,460 +0,0 @@
|
|
|
1
|
-
import { CliRenderer, KeyEvent, TextRenderable } from "@opentui/core"
|
|
2
|
-
|
|
3
|
-
import type { MenuItem, SetupResult } from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
4
|
-
import { CloudflareApi, setupCloudflaredTunnel } from "~/api/cloudflare-api"
|
|
5
|
-
import { saveCompose } from "~/compose"
|
|
6
|
-
import { saveConfig } from "~/config"
|
|
7
|
-
import { EasiarrConfig } from "~/config/schema"
|
|
8
|
-
import { BaseAppSetupScreen } from "~/ui/screens/setup/BaseAppSetupScreen"
|
|
9
|
-
import { readEnvSync, updateEnv } from "~/utils/env"
|
|
10
|
-
|
|
11
|
-
type SubStep = "api_token" | "domain_config" | "vpn_config" | "confirm"
|
|
12
|
-
|
|
13
|
-
export class CloudflaredSetup extends BaseAppSetupScreen {
|
|
14
|
-
private subStep: SubStep = "api_token"
|
|
15
|
-
private editingField: string | null = null
|
|
16
|
-
private inputValue = ""
|
|
17
|
-
private error: string | null = null
|
|
18
|
-
|
|
19
|
-
// Form values
|
|
20
|
-
private apiToken = ""
|
|
21
|
-
private domain = ""
|
|
22
|
-
private tunnelName = "easiarr"
|
|
23
|
-
private accessEmail = ""
|
|
24
|
-
private enableVpn = false
|
|
25
|
-
private privateNetworkCidr = ""
|
|
26
|
-
|
|
27
|
-
constructor(cliRenderer: CliRenderer, config: EasiarrConfig, onBack: () => void) {
|
|
28
|
-
super(cliRenderer, config, onBack)
|
|
29
|
-
|
|
30
|
-
// Load existing values from .env
|
|
31
|
-
const env = readEnvSync()
|
|
32
|
-
this.apiToken = env.CLOUDFLARE_API_TOKEN || ""
|
|
33
|
-
this.domain = env.CLOUDFLARE_DNS_ZONE || config.traefik?.domain || ""
|
|
34
|
-
|
|
35
|
-
this.refreshContent()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
getTitle(): string {
|
|
39
|
-
return "☁️ Cloudflare Tunnel Setup"
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getStepInfo(): string {
|
|
43
|
-
if (this.currentStep === "running") return "Step 5/5: Setting up tunnel..."
|
|
44
|
-
if (this.currentStep === "done") return "Setup Complete!"
|
|
45
|
-
|
|
46
|
-
const steps: Record<SubStep, string> = {
|
|
47
|
-
api_token: "Step 1/5: Enter Cloudflare API Token",
|
|
48
|
-
domain_config: "Step 2/5: Configure Domain",
|
|
49
|
-
vpn_config: "Step 3/5: Zero Trust VPN (Optional)",
|
|
50
|
-
confirm: "Step 4/5: Confirm Settings",
|
|
51
|
-
}
|
|
52
|
-
return steps[this.subStep]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
getMenuItems(): MenuItem[] {
|
|
56
|
-
if (this.currentStep !== "menu") return []
|
|
57
|
-
|
|
58
|
-
switch (this.subStep) {
|
|
59
|
-
case "api_token":
|
|
60
|
-
return [
|
|
61
|
-
{
|
|
62
|
-
name: `API Token: ${this.apiToken ? "********" : "[Enter Token]"}`,
|
|
63
|
-
description: "Edit your Cloudflare API Token",
|
|
64
|
-
action: () => this.startEditing("apiToken"),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: "➡️ Continue",
|
|
68
|
-
description: "Verify token and continue",
|
|
69
|
-
action: () => this.verifyToken(),
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "✕ Cancel",
|
|
73
|
-
description: "Return to main menu",
|
|
74
|
-
action: () => this.onBack(),
|
|
75
|
-
},
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
case "domain_config":
|
|
79
|
-
return [
|
|
80
|
-
{
|
|
81
|
-
name: `Domain: ${this.domain || "[Required]"}`,
|
|
82
|
-
description: "Base domain for your services (e.g. example.com)",
|
|
83
|
-
action: () => this.startEditing("domain"),
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: `Tunnel Name: ${this.tunnelName}`,
|
|
87
|
-
description: "Technical name for the tunnel",
|
|
88
|
-
action: () => this.startEditing("tunnelName"),
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: `Access Email: ${this.accessEmail || "[Optional-Recommended]"}`,
|
|
92
|
-
description: "Enable email login protection",
|
|
93
|
-
action: () => this.startEditing("accessEmail"),
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: "➡️ Continue",
|
|
97
|
-
description: "Configure VPN options",
|
|
98
|
-
action: () => this.goToVpn(),
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "◀ Back",
|
|
102
|
-
description: "Go back to API token",
|
|
103
|
-
action: () => {
|
|
104
|
-
this.subStep = "api_token"
|
|
105
|
-
this.menuIndex = 0
|
|
106
|
-
this.refreshContent()
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
case "vpn_config":
|
|
112
|
-
return [
|
|
113
|
-
{
|
|
114
|
-
name: `VPN Access: ${this.enableVpn ? "✓ Enabled" : "✗ Disabled"}`,
|
|
115
|
-
description: "Toggle Zero Trust VPN access",
|
|
116
|
-
action: () => {
|
|
117
|
-
this.enableVpn = !this.enableVpn
|
|
118
|
-
this.refreshContent()
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
...(this.enableVpn
|
|
122
|
-
? [
|
|
123
|
-
{
|
|
124
|
-
name: `Network CIDR: ${this.privateNetworkCidr}`,
|
|
125
|
-
description: "Private network to route (e.g. 192.168.1.0/24)",
|
|
126
|
-
action: () => this.startEditing("privateNetworkCidr"),
|
|
127
|
-
},
|
|
128
|
-
]
|
|
129
|
-
: []),
|
|
130
|
-
{
|
|
131
|
-
name: "➡️ Continue",
|
|
132
|
-
description: "Review and confirm setup",
|
|
133
|
-
action: () => {
|
|
134
|
-
this.subStep = "confirm"
|
|
135
|
-
this.menuIndex = 0
|
|
136
|
-
this.refreshContent()
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: "◀ Back",
|
|
141
|
-
description: "Go back to domain config",
|
|
142
|
-
action: () => {
|
|
143
|
-
this.subStep = "domain_config"
|
|
144
|
-
this.menuIndex = 0
|
|
145
|
-
this.refreshContent()
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
]
|
|
149
|
-
|
|
150
|
-
case "confirm":
|
|
151
|
-
return [
|
|
152
|
-
{
|
|
153
|
-
name: "🚀 Setup Tunnel Now",
|
|
154
|
-
description: "Create tunnel, configure DNS, and update .env",
|
|
155
|
-
action: () => this.runSetup(),
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: "◀ Back",
|
|
159
|
-
description: "Go back to VPN settings",
|
|
160
|
-
action: () => {
|
|
161
|
-
this.subStep = "vpn_config"
|
|
162
|
-
this.menuIndex = 0
|
|
163
|
-
this.refreshContent()
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
name: "✕ Cancel",
|
|
168
|
-
description: "Return to main menu",
|
|
169
|
-
action: () => this.onBack(),
|
|
170
|
-
},
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
protected renderCustomContent(): boolean {
|
|
176
|
-
if (this.currentStep === "running" || this.currentStep === "done") {
|
|
177
|
-
this.renderResults()
|
|
178
|
-
return true
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (this.editingField) {
|
|
182
|
-
this.renderInputField()
|
|
183
|
-
return true
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (this.subStep === "api_token") {
|
|
187
|
-
this.renderApiInfo()
|
|
188
|
-
} else if (this.subStep === "confirm") {
|
|
189
|
-
this.renderReview()
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (this.error) {
|
|
193
|
-
this.contentBox.add(
|
|
194
|
-
new TextRenderable(this.cliRenderer, {
|
|
195
|
-
content: `⚠️ ${this.error}\n\n`,
|
|
196
|
-
fg: "#ff6666",
|
|
197
|
-
})
|
|
198
|
-
)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
this.renderMenu()
|
|
202
|
-
return true
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private renderApiInfo(): void {
|
|
206
|
-
const lines = [
|
|
207
|
-
"Enter your Cloudflare API Token with these permissions:",
|
|
208
|
-
" • Account:Account Settings:Read (required)",
|
|
209
|
-
" • Account:Cloudflare Tunnel:Edit",
|
|
210
|
-
" • Zone:DNS:Edit",
|
|
211
|
-
" • Account:Zero Trust:Edit (for VPN access)",
|
|
212
|
-
" • Account:Access: Apps and Policies:Edit (optional)\n",
|
|
213
|
-
"Create at: dash.cloudflare.com/profile/api-tokens\n",
|
|
214
|
-
]
|
|
215
|
-
lines.forEach((l) =>
|
|
216
|
-
this.contentBox.add(
|
|
217
|
-
new TextRenderable(this.cliRenderer, {
|
|
218
|
-
content: l + "\n",
|
|
219
|
-
fg: l.includes("dash.cloudflare") ? "#4a9eff" : "#888888",
|
|
220
|
-
})
|
|
221
|
-
)
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private renderReview(): void {
|
|
226
|
-
const lines = [
|
|
227
|
-
"Review your settings:\n",
|
|
228
|
-
` Domain: ${this.domain}`,
|
|
229
|
-
` Tunnel name: ${this.tunnelName}`,
|
|
230
|
-
` Ingress: *.${this.domain} → traefik:80\n`,
|
|
231
|
-
"This will create the tunnel, configure DNS, and save to .env.\n",
|
|
232
|
-
]
|
|
233
|
-
lines.forEach((l) =>
|
|
234
|
-
this.contentBox.add(
|
|
235
|
-
new TextRenderable(this.cliRenderer, { content: l + "\n", fg: "#aaaaaa" })
|
|
236
|
-
)
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
private renderInputField(): void {
|
|
241
|
-
const labels: Record<string, string> = {
|
|
242
|
-
apiToken: "Enter Cloudflare API Token:",
|
|
243
|
-
domain: "Enter domain (e.g. example.com):",
|
|
244
|
-
tunnelName: "Enter tunnel name (default: easiarr):",
|
|
245
|
-
accessEmail: "Enter email for login protection (optional):",
|
|
246
|
-
privateNetworkCidr: "Enter private network CIDR (e.g. 192.168.1.0/24):",
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
this.contentBox.add(
|
|
250
|
-
new TextRenderable(this.cliRenderer, {
|
|
251
|
-
content: `${labels[this.editingField!]}\n\n`,
|
|
252
|
-
fg: "#8be9fd",
|
|
253
|
-
})
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
const displayValue = this.editingField === "apiToken" ? "********" : this.inputValue
|
|
257
|
-
this.contentBox.add(
|
|
258
|
-
new TextRenderable(this.cliRenderer, {
|
|
259
|
-
content: `> ${displayValue}_`,
|
|
260
|
-
fg: "#ffffff",
|
|
261
|
-
})
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
this.contentBox.add(
|
|
265
|
-
new TextRenderable(this.cliRenderer, {
|
|
266
|
-
content: "\n\n(Enter to confirm, Esc to cancel, Ctrl+V to paste)",
|
|
267
|
-
fg: "#6272a4",
|
|
268
|
-
})
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
protected handleCustomKeys(key: KeyEvent): boolean {
|
|
273
|
-
if (this.editingField) {
|
|
274
|
-
if (key.name === "return") {
|
|
275
|
-
this.finishEditing(true)
|
|
276
|
-
} else if (key.name === "escape") {
|
|
277
|
-
this.finishEditing(false)
|
|
278
|
-
} else if (key.name === "backspace") {
|
|
279
|
-
this.inputValue = this.inputValue.slice(0, -1)
|
|
280
|
-
this.refreshContent()
|
|
281
|
-
} else if (key.name === "v" && key.ctrl) {
|
|
282
|
-
// We can't actually access clipboard easily here, but we can't stop the user from thinking they can
|
|
283
|
-
// If the console supports it, the sequence will just arrive.
|
|
284
|
-
} else if (key.sequence && key.sequence.length === 1 && !key.ctrl) {
|
|
285
|
-
this.inputValue += key.sequence
|
|
286
|
-
this.refreshContent()
|
|
287
|
-
}
|
|
288
|
-
return true
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (this.currentStep === "menu") {
|
|
292
|
-
if (key.name === "escape") {
|
|
293
|
-
this.onBack()
|
|
294
|
-
return true
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return false
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// ============================================
|
|
302
|
-
// WIZARD LOGIC
|
|
303
|
-
// ============================================
|
|
304
|
-
|
|
305
|
-
private startEditing(field: string): void {
|
|
306
|
-
this.editingField = field
|
|
307
|
-
this.inputValue = (this as unknown as Record<string, string>)[field] || ""
|
|
308
|
-
this.refreshContent()
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private finishEditing(save: boolean): void {
|
|
312
|
-
if (save && this.editingField) {
|
|
313
|
-
;(this as unknown as Record<string, string>)[this.editingField] = this.inputValue.trim()
|
|
314
|
-
}
|
|
315
|
-
this.editingField = null
|
|
316
|
-
this.refreshContent()
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
private async verifyToken(): Promise<void> {
|
|
320
|
-
if (!this.apiToken.trim()) {
|
|
321
|
-
this.error = "API Token is required"
|
|
322
|
-
this.refreshContent()
|
|
323
|
-
return
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
this.currentStep = "running"
|
|
327
|
-
this.results = [{ name: "Verify API Token", status: "configuring" }]
|
|
328
|
-
this.refreshContent()
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
const api = new CloudflareApi(this.apiToken)
|
|
332
|
-
const zones = await api.listZones()
|
|
333
|
-
if (zones.length === 0) {
|
|
334
|
-
throw new Error("No zones found. Check token permissions.")
|
|
335
|
-
}
|
|
336
|
-
if (!this.domain && zones.length > 0) {
|
|
337
|
-
this.domain = zones[0].name
|
|
338
|
-
}
|
|
339
|
-
this.subStep = "domain_config"
|
|
340
|
-
this.error = null
|
|
341
|
-
this.currentStep = "menu"
|
|
342
|
-
this.menuIndex = 0
|
|
343
|
-
} catch (e) {
|
|
344
|
-
this.error = `Invalid token: ${(e as Error).message}`
|
|
345
|
-
this.currentStep = "menu"
|
|
346
|
-
}
|
|
347
|
-
this.refreshContent()
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
private goToVpn(): void {
|
|
351
|
-
if (!this.domain.trim()) {
|
|
352
|
-
this.error = "Domain is required"
|
|
353
|
-
this.refreshContent()
|
|
354
|
-
return
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Auto-detect private network CIDR
|
|
358
|
-
const env = readEnvSync()
|
|
359
|
-
const localIp = env["LOCAL_DOCKER_IP"] || "192.168.1.1"
|
|
360
|
-
const parts = localIp.split(".")
|
|
361
|
-
if (parts.length === 4) {
|
|
362
|
-
this.privateNetworkCidr = `${parts[0]}.${parts[1]}.${parts[2]}.0/24`
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
this.subStep = "vpn_config"
|
|
366
|
-
this.menuIndex = 0
|
|
367
|
-
this.error = null
|
|
368
|
-
this.refreshContent()
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async runSetup(): Promise<void> {
|
|
372
|
-
this.currentStep = "running"
|
|
373
|
-
this.results = [
|
|
374
|
-
{ name: "Create Tunnel", status: "pending" },
|
|
375
|
-
{ name: "Configure DNS", status: "pending" },
|
|
376
|
-
{ name: "Save Config", status: "pending" },
|
|
377
|
-
{ name: "Access Protection", status: "pending" },
|
|
378
|
-
]
|
|
379
|
-
this.refreshContent()
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
// Step 1: Tunnel & DNS
|
|
383
|
-
this.updateStatus(0, "configuring")
|
|
384
|
-
const tunnelResult = await setupCloudflaredTunnel(
|
|
385
|
-
this.apiToken,
|
|
386
|
-
this.domain,
|
|
387
|
-
this.tunnelName,
|
|
388
|
-
this.enableVpn
|
|
389
|
-
)
|
|
390
|
-
this.updateStatus(0, "success", this.tunnelName)
|
|
391
|
-
this.updateStatus(1, "success", `*.${this.domain}`)
|
|
392
|
-
|
|
393
|
-
// Step 2: Save credentials
|
|
394
|
-
this.updateStatus(2, "configuring")
|
|
395
|
-
await updateEnv({
|
|
396
|
-
CLOUDFLARE_API_TOKEN: this.apiToken,
|
|
397
|
-
CLOUDFLARE_TUNNEL_TOKEN: tunnelResult.tunnelToken,
|
|
398
|
-
CLOUDFLARE_TUNNEL_ID: tunnelResult.tunnelId,
|
|
399
|
-
CLOUDFLARE_ACCOUNT_ID: tunnelResult.accountId,
|
|
400
|
-
CLOUDFLARE_DNS_ZONE: this.domain,
|
|
401
|
-
...(this.enableVpn ? { CLOUDFLARE_PRIVATE_NETWORK: this.privateNetworkCidr } : {}),
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
// Update in-memory config
|
|
405
|
-
const app = this.config.apps.find((a) => a.id === "cloudflared")
|
|
406
|
-
if (app) app.enabled = true
|
|
407
|
-
else this.config.apps.push({ id: "cloudflared", enabled: true })
|
|
408
|
-
|
|
409
|
-
if (this.config.traefik) {
|
|
410
|
-
this.config.traefik.domain = this.domain
|
|
411
|
-
this.config.traefik.entrypoint = "web"
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
await saveConfig(this.config)
|
|
415
|
-
await saveCompose(this.config)
|
|
416
|
-
this.updateStatus(2, "success")
|
|
417
|
-
|
|
418
|
-
// Step 3: Access
|
|
419
|
-
this.updateStatus(3, "configuring")
|
|
420
|
-
if (this.accessEmail.trim()) {
|
|
421
|
-
const api = new CloudflareApi(this.apiToken)
|
|
422
|
-
let publicIp: string | undefined
|
|
423
|
-
try {
|
|
424
|
-
const res = await fetch("https://1.1.1.1/cdn-cgi/trace")
|
|
425
|
-
const text = await res.text()
|
|
426
|
-
const match = text.match(/ip=(.+)/)
|
|
427
|
-
if (match) publicIp = `${match[1].trim()}/32`
|
|
428
|
-
} catch {
|
|
429
|
-
// Ignore
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
await api.setupAccessProtection(this.domain, [this.accessEmail.trim()], "easiarr", publicIp)
|
|
433
|
-
this.updateStatus(3, "success", `Protected for ${this.accessEmail}`)
|
|
434
|
-
} else {
|
|
435
|
-
// Fallback to basic auth handled by backend if needed, or just skip
|
|
436
|
-
this.updateStatus(3, "skipped", "No email provided")
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Step 4: VPN (if enabled)
|
|
440
|
-
if (this.enableVpn) {
|
|
441
|
-
// Logic already included in setupCloudflaredTunnel for basic route,
|
|
442
|
-
// but we could add more specific VPN results here if we wanted.
|
|
443
|
-
}
|
|
444
|
-
} catch (e) {
|
|
445
|
-
const idx = this.results.findIndex((r) => r.status === "configuring")
|
|
446
|
-
if (idx !== -1) this.updateStatus(idx, "error", (e as Error).message)
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
this.currentStep = "done"
|
|
450
|
-
this.refreshContent()
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private updateStatus(index: number, status: SetupResult["status"], message?: string): void {
|
|
454
|
-
if (this.results[index]) {
|
|
455
|
-
this.results[index].status = status
|
|
456
|
-
if (message) this.results[index].message = message
|
|
457
|
-
}
|
|
458
|
-
this.refreshContent()
|
|
459
|
-
}
|
|
460
|
-
}
|