最后活跃于 1764498367

urocibg 修订了这个 Gist 1764498367. 转到此修订

1 file changed, 1334 insertions

fedya-v5.0.sh(文件已创建)

@@ -0,0 +1,1334 @@
1 + #!/bin/bash
2 + clear
3 +
4 + echo -e "\e[1;36m"
5 + cat << "EOF"
6 + ███████╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗ ██╗██████╗ ██╗ ████████╗██████╗ █████╗
7 + ██╔════╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗ ██║ ██║██╔══██╗██║ ╚══██╔══╝██╔══██╗██╔══██╗
8 + █████╗ █████╗ ██║ ██║ ╚████╔╝ ███████║ ██║ ██║██████╔╝██║ ██║ ██████╔╝███████║
9 + ██╔══╝ ██╔══╝ ██║ ██║ ╚██╔╝ ██╔══██║ ██║ ██║██╔══██╗██║ ██║ ██╔══██╗██╔══██║
10 + ██║ ███████╗██████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗██║ ██║ ██║██║ ██║
11 + ╚═╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝
12 + EOF
13 + echo -e "\e[0m"
14 +
15 + echo -e "\e[1;35mFEDYA PRO FILE SERVER v5.0 — PROFESSIONAL EDITION\e[0m"
16 + echo -e "\e[1;34m══════════════════════════════════════════════════════════════════════\e[0m"
17 + echo
18 +
19 + # Проверка за root права
20 + [ "$EUID" -ne 0 ] && { echo -e "\e[1;31m❌ sudo required!\e[0m"; exit 1; }
21 +
22 + # Събиране на потребителски настройки
23 + echo -e "\e[1;33m⚙️ Server Configuration:\e[0m"
24 + read -p "$(echo -e '\e[1;36m📁 Files directory\e[0m [\e[1;32m/var/www/files\e[0m]: ')" DIR
25 + DIR=${DIR:-"/var/www/files"}
26 + mkdir -p "$DIR"
27 +
28 + read -p "$(echo -e '\e[1;36m🌐 Port\e[0m [\e[1;32m8080\e[0m]: ')" PORT
29 + PORT=${PORT:-"8080"}
30 +
31 + read -p "$(echo -e '\e[1;36m🏷️ Server name\e[0m [\e[1;32mFedya'\''s File Server\e[0m]: ')" NAME
32 + NAME=${NAME:-"Fedya's File Server"}
33 +
34 + read -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 +
37 + echo -e "\n\e[1;33m📦 Installing Go...\e[0m"
38 + apt update -qq && apt install -y golang-go &>/dev/null || true
39 +
40 + # Подготовка на временна директория
41 + TMP=$(mktemp -d)
42 + cd "$TMP"
43 +
44 + echo -e "\e[1;33m🔨 Creating Professional Go Server...\e[0m"
45 +
46 + # Създаване на PROFESSIONAL Go код с всички действия (FIXED VERSION)
47 + cat > main.go <<'EOF'
48 + package main
49 +
50 + import (
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 +
72 + var (
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 +
86 + func 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 +
114 + const 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 +
761 + type Item struct {
762 + Name, Size, Date, Time, Ext, Thumb, Bytes string
763 + Dir, Img bool
764 + }
765 +
766 + type Data struct {
767 + Name, Path, Cur, Zip, TotalSize string
768 + Items []Item
769 + Files, Dirs, Imgs int
770 + }
771 +
772 + type 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 +
785 + func isPathSafe(p string) bool {
786 + cleanedPath := filepath.Clean(p)
787 + return strings.HasPrefix(cleanedPath, absFileDir)
788 + }
789 +
790 + func 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 +
813 + func 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 +
1049 + func 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 +
1105 + func 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 +
1120 + func 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 +
1132 + func 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 +
1168 + func 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 +
1226 + func 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 + }
1256 + EOF
1257 +
1258 + # Създаване на Go модул и компилиране
1259 + echo 'module fedya' > go.mod
1260 + echo -e "\e[1;33m🔨 Compiling Professional Server...\e[0m"
1261 +
1262 + if ! 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
1265 + fi
1266 +
1267 + # Инсталиране на изпълнимия файл
1268 + install -m 755 fedya-pro /usr/local/bin/fedya-pro
1269 +
1270 + # Спиране на старата услуга
1271 + systemctl stop fedya-pro 2>/dev/null || true
1272 +
1273 + if $SERVICE; then
1274 + # Създаване на systemd услуга
1275 + cat > /etc/systemd/system/fedya-pro.service <<EOF
1276 + [Unit]
1277 + Description=$NAME - Professional File Server
1278 + After=network.target
1279 +
1280 + [Service]
1281 + Type=simple
1282 + Environment=FILE_DIR=$DIR
1283 + Environment=PORT=$PORT
1284 + Environment=SERVER_NAME=$NAME
1285 + ExecStart=/usr/local/bin/fedya-pro
1286 + WorkingDirectory=$DIR
1287 + Restart=always
1288 + RestartSec=3
1289 + User=root
1290 +
1291 + [Install]
1292 + WantedBy=multi-user.target
1293 + EOF
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
1303 + else
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"
1306 + fi
1307 +
1308 + # Финална информация
1309 + IP=$(hostname -I | awk '{print $1}')
1310 + echo
1311 + echo -e "\e[1;32m🎉 FEDYA PRO FILE SERVER v5.0 IS READY!\e[0m"
1312 + echo
1313 + echo -e "\e[1;36m🌐 Access the server:\e[0m"
1314 + echo -e " \e[1;33mhttp://$IP:$PORT\e[0m"
1315 + echo
1316 + echo -e "\e[1;35m🚀 PROFESSIONAL FEATURES:\e[0m"
1317 + echo -e " 📥 Download files with one click"
1318 + echo -e " ✏️ Rename files and folders"
1319 + echo -e " 🗑️ Delete items with confirmation"
1320 + echo -e " ℹ️ Detailed file information"
1321 + echo -e " 🔗 Temporary links with expiry"
1322 + echo -e " 📊 File size in bytes and human-readable"
1323 + echo -e " 📁 Folder creation"
1324 + echo -e " 📦 ZIP download of folders"
1325 + echo -e " 🖼️ Image thumbnails"
1326 + echo -e " 📤 File upload with progress"
1327 + echo -e " 🎨 Professional color-coded buttons"
1328 + echo -e " 📱 Responsive design for all devices"
1329 + echo -e " 🔧 Bulk actions (coming soon)"
1330 + echo
1331 + echo -e "\e[1;33m💡 Tip: Hover over buttons to see tooltips with descriptions!\e[0m"
1332 +
1333 + # Почистване
1334 + rm -rf "$TMP"
上一页 下一页