#!/bin/bash # # Автор: Федя Серафиев / urocibg.eu RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "${GREEN}[ИНФО]${NC} $1"; } warn() { echo -e "${YELLOW}[ПРЕДУПРЕЖДЕНИЕ]${NC} $1"; } error() { echo -e "${RED}[ГРЕШКА]${NC} $1"; exit 1; } [ "$EUID" -ne 0 ] && error "Стартирай със sudo!" log "Fedya's File Server – Професионална версия с красив дизайн" read -p "Директория за файловете [/var/www/files]: " file_dir file_dir=${file_dir:-"/var/www/files"} mkdir -p "$file_dir" read -p "Порт [8080]: " port port=${port:-"8080"} read -p "Име на сървъра [Fedya File Server]: " server_name server_name=${server_name:-"Fedya File Server"} read -p "Стартиране като услуга? [Y/n]: " svc [[ "$svc" =~ ^[Nn]$ ]] && auto_start=false || auto_start=true log "Инсталиране на Go..." command -v go >/dev/null || { apt update && apt install -y golang-go; } tmp=$(mktemp -d) cd "$tmp" go mod init fedya-files >/dev/null 2>&1 cat > main.go <<'EOF' package main import ( "archive/zip" "fmt" "html/template" "image" "image/jpeg" _ "image/png" _ "image/gif" "io" "log" "net/http" "os" "path/filepath" "sort" "strings" "time" ) var ( fileDir = getEnv("FILE_DIR", "/var/www/files") port = getEnv("PORT", "8080") serverName = getEnv("SERVER_NAME", "Fedya File Server") tmpl = template.Must(template.New("index").Parse(htmlTemplate)) ) func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } const htmlTemplate = ` {{.ServerName}}

{{.ServerName}}

Професионално споделяне на файлове с миниатюри
📊 Общо файлове: {{.FileCount}}
📁 Папки: {{.DirCount}}
🖼️ Картинки: {{.ImageCount}}
Път: /{{.Current}}
📦 Изтегли като ZIP
{{if ne .Current "/"}} {{end}} {{range .Files}} {{end}}
Име Размер Дата
⬆️ Назад
{{if .IsDir}}
📁 {{.Name}} [Отвори]
{{else}} {{end}}
{{.Size}} {{.ModTime}}
` type File struct { Name string Size string ModTime string IsDir bool Thumbnail string } type PageData struct { Files []File Current string CurrentPath string ServerName string FileCount int DirCount int ImageCount int CurrentTime string } func main() { log.Printf("Стартиране на %s на порт %s за директория %s", serverName, port, fileDir) http.HandleFunc("/", handler) http.HandleFunc("/zip", zipHandler) http.HandleFunc("/thumb/", thumbHandler) log.Printf("Сървърът е готов на http://0.0.0.0:%s", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } func handler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/zip") { zipHandler(w, r) return } if strings.HasPrefix(r.URL.Path, "/thumb/") { thumbHandler(w, r) return } reqPath := filepath.Clean(r.URL.Path) if reqPath == "/" || reqPath == "." { reqPath = "" } fullPath := filepath.Join(fileDir, reqPath) if !strings.HasPrefix(fullPath, fileDir) { http.Error(w, "Достъпът е отказан", http.StatusForbidden) return } fi, err := os.Stat(fullPath) if err != nil { http.NotFound(w, r) return } if fi.IsDir() { if r.Method == "POST" { upload(w, r, fullPath) return } listDir(w, reqPath, fullPath) return } http.ServeFile(w, r, fullPath) } func upload(w http.ResponseWriter, r *http.Request, dir string) { if err := r.ParseMultipartForm(500 << 20); err != nil { http.Error(w, "Файлът е твърде голям (максимум 500MB)", http.StatusBadRequest) return } file, header, err := r.FormFile("f") if err != nil { http.Error(w, "Грешка при качване", http.StatusBadRequest) return } defer file.Close() filePath := filepath.Join(dir, header.Filename) dst, err := os.Create(filePath) if err != nil { http.Error(w, "Грешка при създаване на файл", http.StatusInternalServerError) return } defer dst.Close() if _, err := io.Copy(dst, file); err != nil { http.Error(w, "Грешка при запис на файл", http.StatusInternalServerError) return } http.Redirect(w, r, r.URL.Path, http.StatusSeeOther) } func listDir(w http.ResponseWriter, relPath, fullPath string) { entries, err := os.ReadDir(fullPath) if err != nil { http.Error(w, "Грешка при четене на директория", http.StatusInternalServerError) return } var files []File fileCount := 0 dirCount := 0 imageCount := 0 for _, e := range entries { info, err := e.Info() if err != nil { continue } size := "📁 папка" if !e.IsDir() { fileCount++ s := info.Size() switch { case s < 1024: size = fmt.Sprintf("%d Б", s) case s < 1048576: size = fmt.Sprintf("%.1f КБ", float64(s)/1024) case s < 1073741824: size = fmt.Sprintf("%.1f МБ", float64(s)/1048576) default: size = fmt.Sprintf("%.1f ГБ", float64(s)/1073741824) } } else { dirCount++ } thumbnail := "" if !e.IsDir() && isImageFile(e.Name()) { imageCount++ thumbnail = fmt.Sprintf("/thumb%s/%s", relPath, e.Name()) } files = append(files, File{ Name: e.Name(), Size: size, ModTime: info.ModTime().Format("02.01.2006 15:04"), IsDir: e.IsDir(), Thumbnail: thumbnail, }) } sort.Slice(files, func(i, j int) bool { if files[i].IsDir != files[j].IsDir { return files[i].IsDir } return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name) }) current := relPath if current == "" { current = "/" } currentPath := relPath if currentPath != "" && !strings.HasPrefix(currentPath, "/") { currentPath = "/" + currentPath } data := PageData{ Files: files, Current: current, CurrentPath: currentPath, ServerName: serverName, FileCount: fileCount, DirCount: dirCount, ImageCount: imageCount, CurrentTime: time.Now().Format("02.01.2006 15:04:05"), } if err := tmpl.Execute(w, data); err != nil { http.Error(w, "Грешка при генериране на страница", http.StatusInternalServerError) } } func thumbHandler(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/thumb") fullPath := filepath.Join(fileDir, filepath.Clean(path)) if !strings.HasPrefix(fullPath, fileDir) { http.Error(w, "Достъпът е отказан", http.StatusForbidden) return } if _, err := os.Stat(fullPath); os.IsNotExist(err) || !isImageFile(fullPath) { http.NotFound(w, r) return } generateThumbnail(w, fullPath) } func generateThumbnail(w http.ResponseWriter, filePath string) { file, err := os.Open(filePath) if err != nil { http.Error(w, "Грешка при отваряне на файл", http.StatusInternalServerError) return } defer file.Close() img, _, err := image.Decode(file) if err != nil { http.Error(w, "Невалидно изображение", http.StatusInternalServerError) return } bounds := img.Bounds() width := bounds.Dx() height := bounds.Dy() newWidth := 90 newHeight := 70 if width > height { newHeight = int(float64(height) * float64(newWidth) / float64(width)) } else { newWidth = int(float64(width) * float64(newHeight) / float64(height)) } dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) for y := 0; y < newHeight; y++ { for x := 0; x < newWidth; x++ { srcX := x * width / newWidth srcY := y * height / newHeight dst.Set(x, y, img.At(srcX, srcY)) } } w.Header().Set("Content-Type", "image/jpeg") if err := jpeg.Encode(w, dst, &jpeg.Options{Quality: 85}); err != nil { http.Error(w, "Грешка при кодиране на миниатюра", http.StatusInternalServerError) return } } func isImageFile(filename string) bool { ext := strings.ToLower(filepath.Ext(filename)) imageExts := []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".svg"} for _, imgExt := range imageExts { if ext == imgExt { return true } } return false } func zipHandler(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/zip") if path == "" { path = "/" } fullPath := filepath.Join(fileDir, filepath.Clean(path)) if !strings.HasPrefix(fullPath, fileDir) { http.Error(w, "Достъпът е отказан", http.StatusForbidden) return } if _, err := os.Stat(fullPath); os.IsNotExist(err) { http.NotFound(w, r) return } w.Header().Set("Content-Type", "application/zip") filename := filepath.Base(fullPath) if filename == "" || filename == "." || filename == "/" { filename = "files" } w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filename)) zw := zip.NewWriter(w) defer zw.Close() filepath.Walk(fullPath, func(filePath string, info os.FileInfo, err error) error { if err != nil { return err } relPath, err := filepath.Rel(fullPath, filePath) if err != nil { return err } if relPath == "." { return nil } header, err := zip.FileInfoHeader(info) if err != nil { return err } header.Name = relPath if info.IsDir() { header.Name += "/" } else { header.Method = zip.Deflate } writer, err := zw.CreateHeader(header) if err != nil { return err } if !info.IsDir() { file, err := os.Open(filePath) if err != nil { return err } defer file.Close() _, err = io.Copy(writer, file) if err != nil { return err } } return nil }) } EOF log "Компилиране на професионалната версия..." CGO_ENABLED=0 go build -ldflags="-s -w" -o fedya-server || error "Грешка при компилация!" install -m 755 fedya-server /usr/local/bin/fedya-server # Спиране на старите услуги systemctl stop file-server sabork-server fedya-server 2>/dev/null || true systemctl disable file-server sabork-server 2>/dev/null || true if $auto_start; then log "Конфигуриране на systemd услуга..." cat > /etc/systemd/system/fedya-server.service </dev/null 2>&1 if systemctl start fedya-server; then log "Услугата $server_name е стартирана успешно!" sleep 2 log "Статус на услугата:" systemctl status fedya-server --no-pager -l else error "Неуспешно стартиране на услугата. Провери: journalctl -u fedya-server -f" fi else log "Ръчно стартиране:" echo "FILE_DIR='$file_dir' PORT='$port' SERVER_NAME='$server_name' /usr/local/bin/fedya-server" fi rm -rf "$tmp" IP=$(hostname -I | awk '{print $1}') log "ГОТОВО! Отвори: http://$IP:$port" log "🎉 Професионален дизайн • Цветно лого • Фавикон • Статистики" if $auto_start; then echo log "Команди за управление:" echo " systemctl status fedya-server" echo " journalctl -u fedya-server -f" echo " systemctl restart fedya-server" fi echo log "💼 Сървърът е готов за колегите!" echo " 🌐 Може да го споделиш на: http://$IP:$port" echo " 📊 Има статистики за файлове, папки и картинки" echo " 🎨 Професионален дизайн с анимации и ефекти" echo " 🔒 Безопасен и стабилен за корпоративна среда"