Ostatnio aktywny 1764478533

Fedya's File Server - професионален файлов сървър на Go, който може да се инсталира за минути.

Rewizja 6f35f2839a834452df8ce61146f3e5c1d68c4e51

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