Ultima attività 1764478533

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

Revisione 17f8f94a2f1c3ed5a2d85367827a7d3e61249eb3

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