Ultima attività 1764498367

Revisione 0f97fcf9c0b2fbd0f063ab179a12c7028d363724

fedya-v5.0.sh Raw
1#!/bin/bash
2clear
3
4echo -e "\e[1;36m"
5cat << "EOF"
6 ███████╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗ ██╗██████╗ ██╗ ████████╗██████╗ █████╗
7 ██╔════╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗ ██║ ██║██╔══██╗██║ ╚══██╔══╝██╔══██╗██╔══██╗
8 █████╗ █████╗ ██║ ██║ ╚████╔╝ ███████║ ██║ ██║██████╔╝██║ ██║ ██████╔╝███████║
9 ██╔══╝ ██╔══╝ ██║ ██║ ╚██╔╝ ██╔══██║ ██║ ██║██╔══██╗██║ ██║ ██╔══██╗██╔══██║
10 ██║ ███████╗██████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗██║ ██║ ██║██║ ██║
11 ╚═╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝
12EOF
13echo -e "\e[0m"
14
15echo -e "\e[1;35mFEDYA PRO FILE SERVER v5.0 — PROFESSIONAL EDITION\e[0m"
16echo -e "\e[1;34m══════════════════════════════════════════════════════════════════════\e[0m"
17echo
18
19# Проверка за root права
20[ "$EUID" -ne 0 ] && { echo -e "\e[1;31m❌ sudo required!\e[0m"; exit 1; }
21
22# Събиране на потребителски настройки
23echo -e "\e[1;33m⚙️ Server Configuration:\e[0m"
24read -p "$(echo -e '\e[1;36m📁 Files directory\e[0m [\e[1;32m/var/www/files\e[0m]: ')" DIR
25DIR=${DIR:-"/var/www/files"}
26mkdir -p "$DIR"
27
28read -p "$(echo -e '\e[1;36m🌐 Port\e[0m [\e[1;32m8080\e[0m]: ')" PORT
29PORT=${PORT:-"8080"}
30
31read -p "$(echo -e '\e[1;36m🏷️ Server name\e[0m [\e[1;32mFedya'\''s File Server\e[0m]: ')" NAME
32NAME=${NAME:-"Fedya's File Server"}
33
34read -p "$(echo -e '\e[1;36m🔧 Install as systemd service?\e[0m [\e[1;32mY\e[0m/n]: ')" SVC
35[[ "$SVC" =~ ^[Nn]$ ]] && SERVICE=false || SERVICE=true
36
37echo -e "\n\e[1;33m📦 Installing Go...\e[0m"
38apt update -qq && apt install -y golang-go &>/dev/null || true
39
40# Подготовка на временна директория
41TMP=$(mktemp -d)
42cd "$TMP"
43
44echo -e "\e[1;33m🔨 Creating Professional Go Server...\e[0m"
45
46# Създаване на PROFESSIONAL Go код с всички действия (FIXED VERSION)
47cat > main.go <<'EOF'
48package main
49
50import (
51 "archive/zip"
52 "crypto/rand"
53 "encoding/hex"
54 "encoding/json"
55 "fmt"
56 "html/template"
57 "image"
58 "image/jpeg"
59 _ "image/gif"
60 _ "image/png"
61 "io"
62 "log"
63 "net/http"
64 "os"
65 "path"
66 "path/filepath"
67 "sort"
68 "strings"
69 "time"
70)
71
72var (
73 fileDir string
74 absFileDir string
75 port string
76 serverName string
77 tmpl *template.Template
78 links = make(map[string]struct {
79 Path string
80 Expiry time.Time
81 OneTime bool
82 Used bool
83 })
84)
85
86func init() {
87 fileDir = os.Getenv("FILE_DIR")
88 if fileDir == "" {
89 fileDir = "/var/www/files"
90 }
91 port = os.Getenv("PORT")
92 if port == "" {
93 port = "8080"
94 }
95 serverName = os.Getenv("SERVER_NAME")
96 if serverName == "" {
97 serverName = "Fedya's File Server"
98 }
99
100 var err error
101 fileDir, err = filepath.Abs(fileDir)
102 if err != nil {
103 log.Fatalf("Error getting absolute path: %v", err)
104 }
105 absFileDir = fileDir
106
107 // Create base directories
108 os.MkdirAll(filepath.Join(fileDir, "pdf"), 0755)
109 os.MkdirAll(filepath.Join(fileDir, "images"), 0755)
110
111 tmpl = template.Must(template.New("index").Parse(html))
112}
113
114const html = `<!DOCTYPE html>
115<html lang="en">
116<head>
117 <meta charset="UTF-8">
118 <meta name="viewport" content="width=device-width,initial-scale=1">
119 <title>{{.Name}}</title>
120 <!-- PROFESSIONAL FAVICON -->
121 <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHJ4PSIxMiIgZmlsbD0idXJsKCNncmFkKSIvPgogIDxkZWZzPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3R5bGU9InN0b3AtY29sb3I6IzRGNjBFNTtzdG9wLW9wYWNpdHk6MSIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiMwNkI2RDQ7c3RvcC1vcGFjaXR5OjEiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgogIDxwYXRoIGQ9Ik0yMCAyMEMyMCAxNy43OTA5IDE3Ljc5MDkgMTYgMTUgMTZDMTIuMjA5MSAxNiAxMCAxNy43OTA5IDEwIDIwQzEwIDIyLjIwOTEgMTIuMjA5MSAyNCAxNSAyNEMxNy43OTA5IDI0IDIwIDIyLjIwOTEgMjAgMjBaIiBmaWxsPSJ3aGl0ZSIvPgogIDxwYXRoIGQ9Ik0zOCAyMEMzOCAxNy43OTA5IDM1Ljc5MDkgMTYgMzMgMTZDMzAuMjA5MSAxNiAyOCAxNy43OTA5IDI4IDIwQzI4IDIyLjIwOTEgMzAuMjA5MSAyNCAzMyAyNEMzNS43OTA5IDI0IDM4IDIyLjIwOTEgMzggMjBaIiBmaWxsPSJ3aGl0ZSIvPgogIDxwYXRoIGQ9Ik01NCAyMEM1NCAxNy43OTA5IDUxLjc5MDkgMTYgNDkgMTZDNDYuMjA5MSAxNiA0NCAxNy43OTA5IDQ0IDIwQzQ0IDIyLjIwOTEgNDYuMjA5MSAyNCA0OSAyNEM1MS43OTA5IDI0IDU0IDIyLjIwOTEgNTQgMjBaIiBmaWxsPSJ3aGl0ZSIvPgogIDxwYXRoIGQ9Ik0yMCA0MEMyMCAzNy43OTA5IDE3Ljc5MDkgMzYgMTUgMzZDMTIuMjA5MSAzNiAxMCAzNy43OTA5IDEwIDQwQzEwIDQyLjIwOTEgMTIuMjA5MSA0NCAxNSA0NEMxNy43OTA5IDQ0IDIwIDQyLjIwOTEgMjAgNDBaIiBmaWxsPSJ3aGl0ZSIvPgogIDxwYXRoIGQ9Ik0zOCA0MEMzOCAzNy43OTA5IDM1Ljc5MDkgMzYgMzMgMzZDMzAuMjA5MSAzNiAyOCAzNy43OTA5IDI4IDQwQzI8IDQyLjIwOTEgMzAuMjA5MSA0NCAzMyA0NEMzNS43OTA5IDQ0IDM4IDQyLjIwOTEgMzggNDBaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K"/>
122 <style>
123 :root {
124 --primary: #4F46E5;
125 --primary-dark: #4338CA;
126 --secondary: #06B6D4;
127 --success: #10B981;
128 --danger: #EF4444;
129 --warning: #F59E0B;
130 --info: #8B5CF6;
131 --dark: #1E293B;
132 --light: #F8FAFC;
133 --gray: #64748B;
134 --border: #E2E8F0;
135 }
136
137 * { margin: 0; padding: 0; box-sizing: border-box; }
138
139 body {
140 font-family: 'Segoe UI', system-ui, sans-serif;
141 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
142 color: var(--dark);
143 min-height: 100vh;
144 padding: 20px;
145 line-height: 1.6;
146 }
147
148 .container {
149 max-width: 1800px;
150 margin: 0 auto;
151 background: white;
152 border-radius: 20px;
153 box-shadow: 0 25px 80px rgba(0,0,0,0.15);
154 overflow: hidden;
155 }
156
157 .header {
158 background: linear-gradient(135deg, var(--primary), var(--secondary));
159 color: white;
160 padding: 3rem 2rem;
161 text-align: center;
162 position: relative;
163 overflow: hidden;
164 }
165
166 .header::before {
167 content: '';
168 position: absolute;
169 top: 0;
170 left: 0;
171 right: 0;
172 bottom: 0;
173 background: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23ffffff' fill-opacity='0.1' fill-rule='evenodd'/%3E%3C/svg%3E");
174 }
175
176 .logo {
177 font-size: 3.5rem;
178 font-weight: 900;
179 background: linear-gradient(135deg, #FFF, #E0F2FE);
180 -webkit-background-clip: text;
181 -webkit-text-fill-color: transparent;
182 margin-bottom: 0.5rem;
183 text-shadow: 0 2px 4px rgba(0,0,0,0.1);
184 }
185
186 .subtitle {
187 font-size: 1.3rem;
188 opacity: 0.9;
189 font-weight: 300;
190 }
191
192 .stats {
193 padding: 1.5rem;
194 background: var(--light);
195 text-align: center;
196 font-weight: 600;
197 display: flex;
198 justify-content: center;
199 gap: 2rem;
200 flex-wrap: wrap;
201 }
202
203 .stat-item {
204 display: flex;
205 align-items: center;
206 gap: 0.5rem;
207 color: var(--gray);
208 }
209
210 .path {
211 padding: 1.2rem 2rem;
212 background: var(--light);
213 border-bottom: 1px solid var(--border);
214 font-weight: 600;
215 color: var(--primary);
216 display: flex;
217 align-items: center;
218 gap: 0.5rem;
219 }
220
221 .actions {
222 padding: 2rem;
223 background: var(--light);
224 display: flex;
225 flex-wrap: wrap;
226 gap: 1.5rem;
227 align-items: center;
228 border-bottom: 1px solid var(--border);
229 }
230
231 .upload-box {
232 background: white;
233 padding: 2rem;
234 border-radius: 16px;
235 box-shadow: 0 8px 25px rgba(139,92,246,0.1);
236 border: 2px dashed #DDD6FE;
237 flex: 1;
238 min-width: 360px;
239 transition: all 0.3s ease;
240 }
241
242 .upload-box:hover {
243 border-color: var(--primary);
244 transform: translateY(-3px);
245 box-shadow: 0 15px 35px rgba(139,92,246,0.15);
246 }
247
248 button, .btn {
249 padding: 12px 24px;
250 border: none;
251 border-radius: 12px;
252 color: white;
253 background: linear-gradient(135deg, var(--primary), var(--primary-dark));
254 cursor: pointer;
255 font-weight: 600;
256 transition: all 0.3s ease;
257 display: inline-flex;
258 align-items: center;
259 gap: 0.5rem;
260 text-decoration: none;
261 font-size: 0.95rem;
262 }
263
264 button:hover, .btn:hover {
265 transform: translateY(-2px);
266 box-shadow: 0 10px 25px rgba(79,70,229,0.3);
267 }
268
269 .btn-success {
270 background: linear-gradient(135deg, var(--success), #059669);
271 }
272
273 .btn-warning {
274 background: linear-gradient(135deg, var(--warning), #D97706);
275 }
276
277 .btn-danger {
278 background: linear-gradient(135deg, var(--danger), #DC2626);
279 }
280
281 .btn-info {
282 background: linear-gradient(135deg, var(--info), #7C3AED);
283 }
284
285 .action-btn {
286 padding: 8px 12px;
287 border: none;
288 border-radius: 8px;
289 color: white;
290 cursor: pointer;
291 transition: all 0.2s ease;
292 display: inline-flex;
293 align-items: center;
294 gap: 0.3rem;
295 font-size: 0.85rem;
296 margin: 2px;
297 }
298
299 .action-btn:hover {
300 transform: translateY(-1px);
301 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
302 }
303
304 .btn-download { background: var(--success); }
305 .btn-delete { background: var(--danger); }
306 .btn-rename { background: var(--warning); }
307 .btn-link { background: var(--info); }
308 .btn-info { background: var(--primary); }
309 .btn-zip { background: #7C3AED; }
310
311 .modal {
312 display: none;
313 position: fixed;
314 top: 0;
315 left: 0;
316 right: 0;
317 bottom: 0;
318 background: rgba(0,0,0,0.7);
319 align-items: center;
320 justify-content: center;
321 z-index: 1000;
322 }
323
324 .modal-content {
325 background: white;
326 padding: 2.5rem;
327 border-radius: 20px;
328 max-width: 500px;
329 width: 90%;
330 box-shadow: 0 25px 50px rgba(0,0,0,0.25);
331 }
332
333 .close {
334 float: right;
335 font-size: 2rem;
336 cursor: pointer;
337 color: var(--gray);
338 }
339
340 table {
341 width: 100%;
342 border-collapse: collapse;
343 background: white;
344 }
345
346 th {
347 background: var(--light);
348 padding: 1.5rem;
349 text-align: left;
350 font-weight: 700;
351 color: var(--gray);
352 border-bottom: 3px solid var(--border);
353 }
354
355 td {
356 padding: 1.2rem 1.5rem;
357 border-bottom: 1px solid var(--border);
358 }
359
360 tr:hover {
361 background: #F0F9FF;
362 }
363
364 .thumbnail {
365 width: 80px;
366 height: 60px;
367 object-fit: cover;
368 border-radius: 8px;
369 border: 2px solid var(--border);
370 cursor: pointer;
371 }
372
373 .thumbnail:hover {
374 transform: scale(1.05);
375 border-color: var(--primary);
376 }
377
378 .file-icon {
379 font-size: 1.8rem;
380 margin-right: 10px;
381 }
382
383 .folder-icon { color: var(--warning); }
384 .pdf-icon { color: var(--danger); }
385 .image-icon { color: var(--success); }
386 .file-icon-default { color: var(--primary); }
387
388 .link-result {
389 background: #ECFDF5;
390 padding: 1.5rem;
391 border-radius: 12px;
392 margin: 1.5rem 0;
393 border: 2px solid var(--success);
394 }
395
396 .file-info {
397 background: #EFF6FF;
398 padding: 1.5rem;
399 border-radius: 12px;
400 margin: 1rem 0;
401 border: 2px solid var(--primary);
402 }
403
404 .info-grid {
405 display: grid;
406 grid-template-columns: 1fr 1fr;
407 gap: 1rem;
408 margin-top: 1rem;
409 }
410
411 .info-item {
412 display: flex;
413 flex-direction: column;
414 gap: 0.3rem;
415 }
416
417 .info-label {
418 font-weight: 600;
419 color: var(--gray);
420 font-size: 0.9rem;
421 }
422
423 .info-value {
424 font-weight: 500;
425 color: var(--dark);
426 }
427
428 .footer {
429 padding: 2.5rem;
430 text-align: center;
431 color: var(--gray);
432 background: var(--light);
433 }
434
435 .action-buttons {
436 display: flex;
437 gap: 0.3rem;
438 flex-wrap: wrap;
439 }
440
441 @media (max-width: 1200px) {
442 .action-buttons {
443 flex-direction: column;
444 }
445 .action-btn {
446 width: 100%;
447 justify-content: center;
448 }
449 }
450 </style>
451</head>
452<body>
453 <div class="container">
454 <div class="header">
455 <div class="logo">{{.Name}}</div>
456 <div class="subtitle">Professional File Management • Temporary Links • Auto-Sorting • Thumbnails</div>
457 </div>
458
459 <div class="stats">
460 <div class="stat-item">📊 Files: <b>{{.Files}}</b></div>
461 <div class="stat-item">📁 Folders: <b>{{.Dirs}}</b></div>
462 <div class="stat-item">🖼️ Images: <b>{{.Imgs}}</b></div>
463 <div class="stat-item">📦 Total Size: <b>{{.TotalSize}}</b></div>
464 </div>
465
466 <div class="path">
467 <span>📂</span>
468 <span>Path: <b>/{{.Path}}</b></span>
469 </div>
470
471 <div class="actions">
472 <div class="upload-box">
473 <form method="post" enctype="multipart/form-data" id="uploadForm">
474 <div style="display:flex;gap:15px;flex-wrap:wrap;align-items:center">
475 <input type="file" name="f" multiple required
476 style="flex:1;min-width:250px;padding:14px;border:2px solid var(--border);border-radius:12px">
477 <button type="submit">📤 Upload Files</button>
478 </div>
479 </form>
480 </div>
481
482 <button onclick="document.getElementById('modalFolder').style.display='flex'">📁 New Folder</button>
483 <a href="/zip{{.Zip}}" class="btn btn-zip">📦 Download as ZIP</a>
484 <button onclick="showBulkActions()" class="btn btn-info">🔧 Bulk Actions</button>
485 </div>
486
487 <table>
488 <thead>
489 <tr>
490 <th style="width: 40%">Name</th>
491 <th style="width: 15%">Size</th>
492 <th style="width: 20%">Date</th>
493 <th style="width: 25%">Actions</th>
494 </tr>
495 </thead>
496 <tbody>
497 {{if ne .Path "/"}}
498 <tr>
499 <td colspan="4">
500 <a href=".." style="color:var(--primary);font-weight:700;text-decoration:none;display:flex;align-items:center;gap:8px">
501 <span>⬆️</span>
502 <span>Back to Parent Directory</span>
503 </a>
504 </td>
505 </tr>
506 {{end}}
507
508 {{range .Items}}
509 <tr>
510 <td style="display:flex;align-items:center;gap:15px">
511 {{if .Thumb}}
512 <img src="{{.Thumb}}" class="thumbnail" onclick="window.open('{{$.Cur}}/{{.Name}}','_blank')">
513 {{else}}
514 <div class="file-icon {{if .Dir}}folder-icon{{else if eq .Ext ".pdf"}}pdf-icon{{else if .Img}}image-icon{{else}}file-icon-default{{end}}">
515 {{if .Dir}}📁{{else if eq .Ext ".pdf"}}📄{{else if .Img}}🖼️{{else}}📄{{end}}
516 </div>
517 {{end}}
518
519 <div style="display:flex;flex-direction:column">
520 <a href="{{if .Dir}}{{.Name}}/{{else}}{{.Name}}{{end}}"
521 style="color:var(--dark);text-decoration:none;font-weight:500">
522 <span style="font-weight:600">{{.Name}}</span>
523 </a>
524 <small style="color:var(--gray);font-size:0.85rem">
525 {{if .Dir}}Folder{{else if eq .Ext ".pdf"}}PDF Document{{else if .Img}}Image{{else}}File{{end}}
526 {{if not .Dir}}• {{.Ext}}{{end}}
527 </small>
528 </div>
529 </td>
530 <td>
531 <span style="font-weight:500">{{.Size}}</span>
532 {{if not .Dir}}
533 <br><small style="color:var(--gray)">{{.Bytes}} bytes</small>
534 {{end}}
535 </td>
536 <td>
537 <span style="font-weight:500">{{.Date}}</span>
538 <br><small style="color:var(--gray)">{{.Time}}</small>
539 </td>
540 <td>
541 <div class="action-buttons">
542 {{if not .Dir}}
543 <button class="action-btn btn-download" onclick="downloadFile('{{$.Cur}}/{{.Name}}')" title="Download">
544 📥
545 </button>
546 <button class="action-btn btn-link" onclick="generateTempLink('{{$.Cur}}/{{.Name}}')" title="Temporary Link">
547 🔗
548 </button>
549 {{end}}
550 <button class="action-btn btn-rename" onclick="renameItem('{{$.Cur}}/{{.Name}}', '{{.Name}}', {{.Dir}})" title="Rename">
551 ✏️
552 </button>
553 <button class="action-btn btn-info" onclick="showFileInfo('{{$.Cur}}/{{.Name}}')" title="File Info">
554 ℹ️
555 </button>
556 <button class="action-btn btn-delete" onclick="deleteItem('{{$.Cur}}/{{.Name}}', '{{.Name}}', {{.Dir}})" title="Delete">
557 🗑️
558 </button>
559 </div>
560 </td>
561 </tr>
562 {{end}}
563 </tbody>
564 </table>
565
566 <div class="footer">
567 <div style="margin-bottom:1rem;font-size:1.1rem;font-weight:600;color:var(--primary)">
568 🚀 {{.Name}} v5.0 Professional
569 </div>
570 <div style="color:var(--gray);font-size:0.9rem">
571 © 2025 {{.Name}} |Created with ❤️ by Fedya Serafiev
572 </div>
573 </div>
574 </div>
575
576 <!-- Modal Windows -->
577 <div id="modalFolder" class="modal">
578 <div class="modal-content">
579 <span class="close" onclick="document.getElementById('modalFolder').style.display='none'">×</span>
580 <h2>📁 Create New Folder</h2>
581 <form method="post">
582 <input type="hidden" name="mkdir" value="1">
583 <input name="name" placeholder="Enter folder name" required
584 style="width:100%;padding:14px;border:2px solid var(--border);border-radius:12px;margin:1rem 0">
585 <button type="submit" style="width:100%;justify-content:center">✅ Create Folder</button>
586 </form>
587 </div>
588 </div>
589
590 <div id="modalTempLink" class="modal">
591 <div class="modal-content">
592 <span class="close" onclick="document.getElementById('modalTempLink').style.display='none'">×</span>
593 <h2>🔗 Create Temporary Link</h2>
594 <div id="linkResult"></div>
595 <div style="margin-top:1.5rem;display:flex;gap:12px;flex-wrap:wrap;justify-content:center">
596 <button onclick="setExpiry('1h')" class="btn-success">1 hour</button>
597 <button onclick="setExpiry('24h')">24 hours</button>
598 <button onclick="setExpiry('7d')" class="btn-warning">7 days</button>
599 <button onclick="setExpiry('once')" class="btn-info">One-time</button>
600 </div>
601 </div>
602 </div>
603
604 <div id="modalRename" class="modal">
605 <div class="modal-content">
606 <span class="close" onclick="document.getElementById('modalRename').style.display='none'">×</span>
607 <h2>✏️ Rename Item</h2>
608 <form id="renameForm" method="post">
609 <input type="hidden" name="rename" value="1">
610 <input type="hidden" id="renameOldPath" name="oldpath">
611 <input type="hidden" id="renameIsDir" name="isdir">
612 <input id="renameNewName" name="newname" placeholder="Enter new name" required
613 style="width:100%;padding:14px;border:2px solid var(--border);border-radius:12px;margin:1rem 0">
614 <button type="submit" style="width:100%;justify-content:center">✅ Rename</button>
615 </form>
616 </div>
617 </div>
618
619 <div id="modalDelete" class="modal">
620 <div class="modal-content">
621 <span class="close" onclick="document.getElementById('modalDelete').style.display='none'">×</span>
622 <h2>🗑️ Delete Item</h2>
623 <p>Are you sure you want to delete "<span id="deleteItemName"></span>"?</p>
624 <p style="color:var(--danger);font-weight:600;margin:1rem 0">This action cannot be undone!</p>
625 <form id="deleteForm" method="post">
626 <input type="hidden" name="delete" value="1">
627 <input type="hidden" id="deleteItemPath" name="path">
628 <div style="display:flex;gap:12px;margin-top:1.5rem">
629 <button type="button" onclick="document.getElementById('modalDelete').style.display='none'"
630 style="flex:1;background:var(--gray)">❌ Cancel</button>
631 <button type="submit" style="flex:1" class="btn-danger">🗑️ Delete</button>
632 </div>
633 </form>
634 </div>
635 </div>
636
637 <div id="modalFileInfo" class="modal">
638 <div class="modal-content">
639 <span class="close" onclick="document.getElementById('modalFileInfo').style.display='none'">×</span>
640 <h2>ℹ️ File Information</h2>
641 <div id="fileInfoContent"></div>
642 </div>
643 </div>
644
645 <script>
646 let currentPath = "";
647
648 function downloadFile(path) {
649 window.open(path, '_blank');
650 }
651
652 function generateTempLink(path) {
653 currentPath = path;
654 document.getElementById('modalTempLink').style.display = 'flex';
655 document.getElementById('linkResult').innerHTML = '<p style="text-align:center;color:var(--gray)">Select expiration time...</p>';
656 }
657
658 function renameItem(path, oldName, isDir) {
659 document.getElementById('modalRename').style.display = 'flex';
660 document.getElementById('renameOldPath').value = path;
661 document.getElementById('renameIsDir').value = isDir;
662 document.getElementById('renameNewName').value = oldName;
663 document.getElementById('renameNewName').select();
664 }
665
666 function deleteItem(path, name, isDir) {
667 document.getElementById('modalDelete').style.display = 'flex';
668 document.getElementById('deleteItemName').textContent = name;
669 document.getElementById('deleteItemPath').value = path;
670 }
671
672 function showFileInfo(path) {
673 fetch('/info?path=' + encodeURIComponent(path))
674 .then(r => r.json())
675 .then(data => {
676 // Generate the HTML for file-specific info only if it's not a directory
677 var fileSpecificInfo = '';
678 if (!data.IsDir) {
679 fileSpecificInfo = ''
680 + '<div class="info-item">'
681 + ' <span class="info-label">Extension</span>'
682 + ' <span class="info-value">' + data.Extension + '</span>'
683 + '</div>'
684 + '<div class="info-item">'
685 + ' <span class="info-label">MIME Type</span>'
686 + ' <span class="info-value">' + data.MimeType + '</span>'
687 + '</div>';
688 }
689
690 var content = ''
691 + '<div class="file-info">'
692 + ' <h3 style="margin-bottom:1rem">' + data.Name + '</h3>'
693 + ' <div class="info-grid">'
694 + ' <div class="info-item">'
695 + ' <span class="info-label">Type</span>'
696 + ' <span class="info-value">' + data.Type + '</span>'
697 + ' </div>'
698 + ' <div class="info-item">'
699 + ' <span class="info-label">Size</span>'
700 + ' <span class="info-value">' + data.Size + ' (' + data.Bytes + ' bytes)</span>'
701 + ' </div>'
702 + ' <div class="info-item">'
703 + ' <span class="info-label">Modified</span>'
704 + ' <span class="info-value">' + data.Modified + '</span>'
705 + ' </div>'
706 + ' <div class="info-item">'
707 + ' <span class="info-label">Permissions</span>'
708 + ' <span class="info-value">' + data.Permissions + '</span>'
709 + ' </div>'
710 + fileSpecificInfo
711 + ' </div>'
712 + '</div>'
713 + '<div style="margin-top:1.5rem;display:flex;gap:12px;flex-wrap:wrap">'
714 + ' <button onclick="downloadFile(\'' + data.Path.replace(/'/g, "\\'") + '\')" class="btn-success">Download</button>'
715 + ' <button onclick="generateTempLink(\'' + data.Path.replace(/'/g, "\\'") + '\')" class="btn-info">Temporary Link</button>'
716 + ' <button onclick="renameItem(\'' + data.Path.replace(/'/g, "\\'") + '\', \'' + data.Name.replace(/'/g, "\\'") + '\', ' + (data.IsDir ? 'true' : 'false') + ')" class="btn-warning">Rename</button>'
717 + '</div>';
718
719 document.getElementById('fileInfoContent').innerHTML = content;
720 document.getElementById('modalFileInfo').style.display = 'flex';
721 })
722 .catch(err => {
723 document.getElementById('fileInfoContent').innerHTML = '<p style="color:var(--danger)">Error loading file information</p>';
724 document.getElementById('modalFileInfo').style.display = 'flex';
725 });
726 }
727
728 function setExpiry(time) {
729 fetch('/t', {
730 method: 'POST',
731 body: 'p=' + encodeURIComponent(currentPath) + '&e=' + time,
732 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
733 })
734 .then(r => r.text())
735 .then(html => {
736 document.getElementById('linkResult').innerHTML = html;
737 });
738 }
739
740 function showBulkActions() {
741 alert('Bulk actions feature coming soon!');
742 }
743
744 window.onclick = function(event) {
745 if (event.target.classList.contains('modal')) {
746 event.target.style.display = 'none';
747 }
748 }
749
750 // Progress indicator for uploads
751 document.getElementById('uploadForm').onsubmit = function() {
752 const button = this.querySelector('button[type="submit"]');
753 button.innerHTML = '<span>⏳ Uploading...</span>';
754 button.disabled = true;
755 };
756</script>
757
758</body>
759</html>`
760
761type Item struct {
762 Name, Size, Date, Time, Ext, Thumb, Bytes string
763 Dir, Img bool
764}
765
766type Data struct {
767 Name, Path, Cur, Zip, TotalSize string
768 Items []Item
769 Files, Dirs, Imgs int
770}
771
772type FileInfo struct {
773 Name string `json:"name"`
774 Path string `json:"path"`
775 Size string `json:"size"`
776 Bytes int64 `json:"bytes"`
777 Type string `json:"type"`
778 Extension string `json:"extension"`
779 MimeType string `json:"mimeType"`
780 Modified string `json:"modified"`
781 Permissions string `json:"permissions"`
782 IsDir bool `json:"isDir"`
783}
784
785func isPathSafe(p string) bool {
786 cleanedPath := filepath.Clean(p)
787 return strings.HasPrefix(cleanedPath, absFileDir)
788}
789
790func main() {
791 // Clean expired links
792 go func() {
793 for range time.Tick(10 * time.Minute) {
794 now := time.Now()
795 for k, v := range links {
796 if now.After(v.Expiry) {
797 delete(links, k)
798 }
799 }
800 }
801 }()
802
803 http.HandleFunc("/", handler)
804 http.HandleFunc("/t", tempHandler)
805 http.HandleFunc("/info", infoHandler)
806 http.Handle("/thumb/", http.StripPrefix("/thumb/", http.HandlerFunc(thumb)))
807
808 log.Printf("🚀 Professional Server '%s' starting on port %s", serverName, port)
809 log.Printf("📁 Directory: %s", absFileDir)
810 log.Fatal(http.ListenAndServe(":"+port, nil))
811}
812
813func handler(w http.ResponseWriter, r *http.Request) {
814 if strings.HasPrefix(r.URL.Path, "/temp/") {
815 serveTemp(w, r)
816 return
817 }
818 if strings.HasPrefix(r.URL.Path, "/thumb/") {
819 thumb(w, r)
820 return
821 }
822
823 reqPath := path.Clean(r.URL.Path)
824 if reqPath == "/" {
825 reqPath = ""
826 }
827
828 full := filepath.Join(fileDir, filepath.FromSlash(reqPath))
829
830 if !isPathSafe(full) {
831 http.Error(w, "Forbidden: Path Traversal", 403)
832 return
833 }
834
835 if r.Method == "POST" {
836 // Handle rename
837 if r.FormValue("rename") != "" {
838 oldPath := r.FormValue("oldpath")
839 newName := strings.TrimSpace(r.FormValue("newname"))
840
841 if newName != "" && !strings.ContainsAny(newName, "/\\") {
842 oldFull := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(oldPath, "/")))
843 newFull := filepath.Join(filepath.Dir(oldFull), newName)
844
845 if isPathSafe(oldFull) && isPathSafe(newFull) {
846 os.Rename(oldFull, newFull)
847 }
848 }
849 http.Redirect(w, r, r.URL.Path, 303)
850 return
851 }
852
853 // Handle delete
854 if r.FormValue("delete") != "" {
855 delPath := r.FormValue("path")
856 fullPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(delPath, "/")))
857
858 if isPathSafe(fullPath) {
859 os.RemoveAll(fullPath)
860 }
861 http.Redirect(w, r, r.URL.Path, 303)
862 return
863 }
864
865 // Handle folder creation
866 if r.FormValue("mkdir") != "" {
867 name := strings.TrimSpace(r.FormValue("name"))
868 if name != "" && !strings.ContainsAny(name, "/\\") {
869 os.Mkdir(filepath.Join(full, name), 0755)
870 }
871 http.Redirect(w, r, r.URL.Path, 303)
872 return
873 }
874
875 // Handle file upload
876 if err := r.ParseMultipartForm(500 << 20); err != nil {
877 http.Error(w, "Upload error: "+err.Error(), 500)
878 return
879 }
880
881 for _, h := range r.MultipartForm.File["f"] {
882 f, err := h.Open()
883 if err != nil {
884 continue
885 }
886 defer f.Close()
887
888 ext := strings.ToLower(filepath.Ext(h.Filename))
889 dest := full
890
891 // Auto-sorting
892 if ext == ".pdf" {
893 dest = filepath.Join(fileDir, "pdf")
894 os.MkdirAll(dest, 0755)
895 } else if strings.Contains(".jpg.jpeg.png.gif.webp", ext) {
896 dest = filepath.Join(fileDir, "images")
897 os.MkdirAll(dest, 0755)
898 }
899
900 out, err := os.Create(filepath.Join(dest, h.Filename))
901 if err != nil {
902 continue
903 }
904 defer out.Close()
905
906 io.Copy(out, f)
907 }
908 http.Redirect(w, r, r.URL.Path, 303)
909 return
910 }
911
912 // Show files
913 entries, err := os.ReadDir(full)
914 if err != nil {
915 if os.IsNotExist(err) {
916 http.ServeFile(w, r, full)
917 } else {
918 http.Error(w, "Read error", 500)
919 }
920 return
921 }
922
923 var items []Item
924 files, dirs, imgs := 0, 0, 0
925 var totalSize int64 = 0
926
927 // ZIP functionality
928 if strings.HasPrefix(reqPath, "/zip/") {
929 zipPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(reqPath, "/zip")))
930 if !isPathSafe(zipPath) {
931 http.Error(w, "Forbidden", 403)
932 return
933 }
934
935 w.Header().Set("Content-Type", "application/zip")
936 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filepath.Base(zipPath)))
937
938 archive := zip.NewWriter(w)
939 defer archive.Close()
940
941 filepath.Walk(zipPath, func(p string, info os.FileInfo, err error) error {
942 if err != nil {
943 return err
944 }
945
946 if p == zipPath {
947 return nil
948 }
949
950 relPath, err := filepath.Rel(zipPath, p)
951 if err != nil {
952 return err
953 }
954
955 if info.IsDir() {
956 _, err = archive.Create(relPath + "/")
957 return err
958 }
959
960 file, err := os.Open(p)
961 if err != nil {
962 return err
963 }
964 defer file.Close()
965
966 writer, err := archive.Create(relPath)
967 if err != nil {
968 return err
969 }
970
971 _, err = io.Copy(writer, file)
972 return err
973 })
974 return
975 }
976
977 for _, e := range entries {
978 info, err := e.Info()
979 if err != nil {
980 continue
981 }
982
983 item := Item{
984 Name: e.Name(),
985 Date: info.ModTime().Format("02 Jan 2006"),
986 Time: info.ModTime().Format("15:04:05"),
987 Dir: e.IsDir(),
988 }
989
990 if e.IsDir() {
991 item.Size = "Folder"
992 item.Bytes = "-"
993 dirs++
994 } else {
995 size := info.Size()
996 totalSize += size
997 item.Bytes = formatBytes(size)
998
999 switch {
1000 case size < 1024:
1001 item.Size = fmt.Sprintf("%d B", size)
1002 case size < 1024*1024:
1003 item.Size = fmt.Sprintf("%.1f KB", float64(size)/1024)
1004 case size < 1024*1024*1024:
1005 item.Size = fmt.Sprintf("%.1f MB", float64(size)/(1024*1024))
1006 default:
1007 item.Size = fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024))
1008 }
1009
1010 files++
1011 ext := strings.ToLower(filepath.Ext(e.Name()))
1012 item.Ext = ext
1013 if strings.Contains(".jpg.jpeg.png.gif.webp", ext) {
1014 imgs++
1015 item.Img = true
1016 item.Thumb = "/thumb" + path.Join("/", reqPath, e.Name())
1017 }
1018 }
1019 items = append(items, item)
1020 }
1021
1022 sort.Slice(items, func(i, j int) bool {
1023 if items[i].Dir != items[j].Dir {
1024 return items[i].Dir
1025 }
1026 return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name)
1027 })
1028
1029 totalSizeStr := formatBytes(totalSize)
1030 if totalSize == 0 {
1031 totalSizeStr = "0 B"
1032 }
1033
1034 data := Data{
1035 Name: serverName,
1036 Path: reqPath,
1037 Cur: "/" + reqPath,
1038 Zip: reqPath,
1039 Items: items,
1040 Files: files,
1041 Dirs: dirs,
1042 Imgs: imgs,
1043 TotalSize: totalSizeStr,
1044 }
1045
1046 tmpl.Execute(w, data)
1047}
1048
1049func infoHandler(w http.ResponseWriter, r *http.Request) {
1050 pathParam := r.URL.Query().Get("path")
1051 if pathParam == "" {
1052 http.Error(w, "Path parameter required", 400)
1053 return
1054 }
1055
1056 fullPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(pathParam, "/")))
1057 if !isPathSafe(fullPath) {
1058 http.Error(w, "Forbidden", 403)
1059 return
1060 }
1061
1062 info, err := os.Stat(fullPath)
1063 if err != nil {
1064 http.Error(w, "File not found", 404)
1065 return
1066 }
1067
1068 fileInfo := FileInfo{
1069 Name: filepath.Base(fullPath),
1070 Path: pathParam,
1071 Bytes: info.Size(),
1072 Modified: info.ModTime().Format("02 Jan 2006 15:04:05"),
1073 Permissions: info.Mode().String(),
1074 IsDir: info.IsDir(),
1075 }
1076
1077 if info.IsDir() {
1078 fileInfo.Type = "Folder"
1079 fileInfo.Size = "Folder"
1080 fileInfo.Extension = "-"
1081 fileInfo.MimeType = "-"
1082 } else {
1083 ext := filepath.Ext(fullPath)
1084 fileInfo.Type = "File"
1085 fileInfo.Extension = ext
1086 fileInfo.MimeType = getMimeType(ext)
1087
1088 size := info.Size()
1089 switch {
1090 case size < 1024:
1091 fileInfo.Size = fmt.Sprintf("%d B", size)
1092 case size < 1024*1024:
1093 fileInfo.Size = fmt.Sprintf("%.1f KB", float64(size)/1024)
1094 case size < 1024*1024*1024:
1095 fileInfo.Size = fmt.Sprintf("%.1f MB", float64(size)/(1024*1024))
1096 default:
1097 fileInfo.Size = fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024))
1098 }
1099 }
1100
1101 w.Header().Set("Content-Type", "application/json")
1102 json.NewEncoder(w).Encode(fileInfo)
1103}
1104
1105func getMimeType(ext string) string {
1106 switch strings.ToLower(ext) {
1107 case ".pdf": return "application/pdf"
1108 case ".jpg", ".jpeg": return "image/jpeg"
1109 case ".png": return "image/png"
1110 case ".gif": return "image/gif"
1111 case ".txt": return "text/plain"
1112 case ".html", ".htm": return "text/html"
1113 case ".zip": return "application/zip"
1114 case ".mp4": return "video/mp4"
1115 case ".mp3": return "audio/mpeg"
1116 default: return "application/octet-stream"
1117 }
1118}
1119
1120func formatBytes(bytes int64) string {
1121 if bytes < 1024 {
1122 return fmt.Sprintf("%d", bytes)
1123 } else if bytes < 1024*1024 {
1124 return fmt.Sprintf("%.0f", float64(bytes)/1024)
1125 } else if bytes < 1024*1024*1024 {
1126 return fmt.Sprintf("%.0f", float64(bytes)/(1024*1024))
1127 } else {
1128 return fmt.Sprintf("%.1f", float64(bytes)/(1024*1024*1024))
1129 }
1130}
1131
1132func thumb(w http.ResponseWriter, r *http.Request) {
1133 p := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(r.URL.Path, "/thumb")))
1134
1135 if !isPathSafe(p) {
1136 return
1137 }
1138
1139 f, err := os.Open(p)
1140 if err != nil {
1141 return
1142 }
1143 defer f.Close()
1144
1145 img, _, err := image.Decode(f)
1146 if err != nil {
1147 return
1148 }
1149
1150 b := img.Bounds()
1151 nw, nh := 100, 80
1152 if b.Dx() > b.Dy() {
1153 nh = b.Dy() * 100 / b.Dx()
1154 } else {
1155 nw = b.Dx() * 80 / b.Dy()
1156 }
1157 resized := image.NewRGBA(image.Rect(0, 0, nw, nh))
1158 for y := 0; y < nh; y++ {
1159 for x := 0; x < nw; x++ {
1160 resized.Set(x, y, img.At(x*b.Dx()/nw, y*b.Dy()/nh))
1161 }
1162 }
1163
1164 w.Header().Set("Content-Type", "image/jpeg")
1165 jpeg.Encode(w, resized, &jpeg.Options{Quality: 85})
1166}
1167
1168func tempHandler(w http.ResponseWriter, r *http.Request) {
1169 if r.Method != "POST" {
1170 http.Error(w, "Method not allowed", 405)
1171 return
1172 }
1173
1174 if err := r.ParseForm(); err != nil {
1175 http.Error(w, "Parse error", 500)
1176 return
1177 }
1178
1179 p := r.FormValue("p")
1180 e := r.FormValue("e")
1181
1182 cleanedPath := filepath.Clean(filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(p, "/"))))
1183
1184 if !isPathSafe(cleanedPath) {
1185 http.Error(w, "Invalid path", 400)
1186 return
1187 }
1188
1189 var exp time.Time
1190 once := false
1191 switch e {
1192 case "1h":
1193 exp = time.Now().Add(1 * time.Hour)
1194 case "24h":
1195 exp = time.Now().Add(24 * time.Hour)
1196 case "7d":
1197 exp = time.Now().Add(7 * 24 * time.Hour)
1198 case "once":
1199 once = true
1200 exp = time.Now().Add(30 * 24 * time.Hour)
1201 default:
1202 exp = time.Now().Add(24 * time.Hour)
1203 }
1204
1205 tok := make([]byte, 16)
1206 rand.Read(tok)
1207 token := hex.EncodeToString(tok)
1208
1209 links[token] = struct {
1210 Path string
1211 Expiry time.Time
1212 OneTime bool
1213 Used bool
1214 }{
1215 Path: cleanedPath,
1216 Expiry: exp,
1217 OneTime: once,
1218 }
1219
1220 link := fmt.Sprintf("http://%s/temp/%s", r.Host, token)
1221 fmt.Fprintf(w, `<div class="link-result">Done! Valid until <b>%s</b><br><br>
1222 <input value="%s" readonly style="width:100%%;padding:12px;border-radius:8px;border:1px solid #10B981" onclick="this.select()">
1223 <br><small>Ctrl+C to copy</small></div>`, exp.Format("02 Jan 2006 15:04"), link)
1224}
1225
1226func serveTemp(w http.ResponseWriter, r *http.Request) {
1227 token := strings.TrimPrefix(r.URL.Path, "/temp/")
1228 l, ok := links[token]
1229
1230 if !ok || time.Now().After(l.Expiry) || (l.OneTime && l.Used) {
1231 http.Error(w, "Link expired", 410)
1232 return
1233 }
1234
1235 if !isPathSafe(l.Path) {
1236 http.Error(w, "Invalid link", 403)
1237 return
1238 }
1239
1240 if l.OneTime {
1241 links[token] = struct {
1242 Path string
1243 Expiry time.Time
1244 OneTime bool
1245 Used bool
1246 }{
1247 Path: l.Path,
1248 Expiry: l.Expiry,
1249 OneTime: true,
1250 Used: true,
1251 }
1252 }
1253
1254 http.ServeFile(w, r, l.Path)
1255}
1256EOF
1257
1258# Създаване на Go модул и компилиране
1259echo 'module fedya' > go.mod
1260echo -e "\e[1;33m🔨 Compiling Professional Server...\e[0m"
1261
1262if ! CGO_ENABLED=0 go build -ldflags="-s -w" -o fedya-pro 2>&1; then
1263 echo -e "\e[1;31m❌ Compilation error!\e[0m"
1264 exit 1
1265fi
1266
1267# Инсталиране на изпълнимия файл
1268install -m 755 fedya-pro /usr/local/bin/fedya-pro
1269
1270# Спиране на старата услуга
1271systemctl stop fedya-pro 2>/dev/null || true
1272
1273if $SERVICE; then
1274 # Създаване на systemd услуга
1275 cat > /etc/systemd/system/fedya-pro.service <<EOF
1276[Unit]
1277Description=$NAME - Professional File Server
1278After=network.target
1279
1280[Service]
1281Type=simple
1282Environment=FILE_DIR=$DIR
1283Environment=PORT=$PORT
1284Environment=SERVER_NAME=$NAME
1285ExecStart=/usr/local/bin/fedya-pro
1286WorkingDirectory=$DIR
1287Restart=always
1288RestartSec=3
1289User=root
1290
1291[Install]
1292WantedBy=multi-user.target
1293EOF
1294
1295 systemctl daemon-reload
1296 systemctl enable fedya-pro
1297 systemctl start fedya-pro
1298
1299 echo -e "\e[1;32m✅ Service is active!\e[0m"
1300 sleep 2
1301 echo -e "\e[1;33m📊 Service status:\e[0m"
1302 systemctl status fedya-pro --no-pager -l
1303else
1304 echo -e "\n\e[1;33m📝 Manual start:\e[0m"
1305 echo -e "\e[1;36mFILE_DIR='$DIR' PORT='$PORT' SERVER_NAME='$NAME' /usr/local/bin/fedya-pro\e[0m"
1306fi
1307
1308# Финална информация
1309IP=$(hostname -I | awk '{print $1}')
1310echo
1311echo -e "\e[1;32m🎉 FEDYA PRO FILE SERVER v5.0 IS READY!\e[0m"
1312echo
1313echo -e "\e[1;36m🌐 Access the server:\e[0m"
1314echo -e " \e[1;33mhttp://$IP:$PORT\e[0m"
1315echo
1316echo -e "\e[1;35m🚀 PROFESSIONAL FEATURES:\e[0m"
1317echo -e " 📥 Download files with one click"
1318echo -e " ✏️ Rename files and folders"
1319echo -e " 🗑️ Delete items with confirmation"
1320echo -e " ℹ️ Detailed file information"
1321echo -e " 🔗 Temporary links with expiry"
1322echo -e " 📊 File size in bytes and human-readable"
1323echo -e " 📁 Folder creation"
1324echo -e " 📦 ZIP download of folders"
1325echo -e " 🖼️ Image thumbnails"
1326echo -e " 📤 File upload with progress"
1327echo -e " 🎨 Professional color-coded buttons"
1328echo -e " 📱 Responsive design for all devices"
1329echo -e " 🔧 Bulk actions (coming soon)"
1330echo
1331echo -e "\e[1;33m💡 Tip: Hover over buttons to see tooltips with descriptions!\e[0m"
1332
1333# Почистване
1334rm -rf "$TMP"