#!/bin/bash clear echo -e "\e[1;36m" cat << "EOF" ███████╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗ ██╗██████╗ ██╗ ████████╗██████╗ █████╗ ██╔════╝██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗ ██║ ██║██╔══██╗██║ ╚══██╔══╝██╔══██╗██╔══██╗ █████╗ █████╗ ██║ ██║ ╚████╔╝ ███████║ ██║ ██║██████╔╝██║ ██║ ██████╔╝███████║ ██╔══╝ ██╔══╝ ██║ ██║ ╚██╔╝ ██╔══██║ ██║ ██║██╔══██╗██║ ██║ ██╔══██╗██╔══██║ ██║ ███████╗██████╔╝ ██║ ██║ ██║ ╚██████╔╝██║ ██║███████╗██║ ██║ ██║██║ ██║ ╚═╝ ╚══════╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ EOF echo -e "\e[0m" echo -e "\e[1;35mFEDYA PRO FILE SERVER v5.0 — PROFESSIONAL EDITION\e[0m" echo -e "\e[1;34m══════════════════════════════════════════════════════════════════════\e[0m" echo # Проверка за root права [ "$EUID" -ne 0 ] && { echo -e "\e[1;31m❌ sudo required!\e[0m"; exit 1; } # Събиране на потребителски настройки echo -e "\e[1;33m⚙️ Server Configuration:\e[0m" read -p "$(echo -e '\e[1;36m📁 Files directory\e[0m [\e[1;32m/var/www/files\e[0m]: ')" DIR DIR=${DIR:-"/var/www/files"} mkdir -p "$DIR" read -p "$(echo -e '\e[1;36m🌐 Port\e[0m [\e[1;32m8080\e[0m]: ')" PORT PORT=${PORT:-"8080"} read -p "$(echo -e '\e[1;36m🏷️ Server name\e[0m [\e[1;32mFedya'\''s File Server\e[0m]: ')" NAME NAME=${NAME:-"Fedya's File Server"} read -p "$(echo -e '\e[1;36m🔧 Install as systemd service?\e[0m [\e[1;32mY\e[0m/n]: ')" SVC [[ "$SVC" =~ ^[Nn]$ ]] && SERVICE=false || SERVICE=true echo -e "\n\e[1;33m📦 Installing Go...\e[0m" apt update -qq && apt install -y golang-go &>/dev/null || true # Подготовка на временна директория TMP=$(mktemp -d) cd "$TMP" echo -e "\e[1;33m🔨 Creating Professional Go Server...\e[0m" # Създаване на PROFESSIONAL Go код с всички действия (FIXED VERSION) cat > main.go <<'EOF' package main import ( "archive/zip" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "html/template" "image" "image/jpeg" _ "image/gif" _ "image/png" "io" "log" "net/http" "os" "path" "path/filepath" "sort" "strings" "time" ) var ( fileDir string absFileDir string port string serverName string tmpl *template.Template links = make(map[string]struct { Path string Expiry time.Time OneTime bool Used bool }) ) func init() { fileDir = os.Getenv("FILE_DIR") if fileDir == "" { fileDir = "/var/www/files" } port = os.Getenv("PORT") if port == "" { port = "8080" } serverName = os.Getenv("SERVER_NAME") if serverName == "" { serverName = "Fedya's File Server" } var err error fileDir, err = filepath.Abs(fileDir) if err != nil { log.Fatalf("Error getting absolute path: %v", err) } absFileDir = fileDir // Create base directories os.MkdirAll(filepath.Join(fileDir, "pdf"), 0755) os.MkdirAll(filepath.Join(fileDir, "images"), 0755) tmpl = template.Must(template.New("index").Parse(html)) } const html = ` {{.Name}}
Professional File Management • Temporary Links • Auto-Sorting • Thumbnails
📊 Files: {{.Files}}
📁 Folders: {{.Dirs}}
🖼️ Images: {{.Imgs}}
📦 Total Size: {{.TotalSize}}
📂 Path: /{{.Path}}
📦 Download as ZIP
{{if ne .Path "/"}} {{end}} {{range .Items}} {{end}}
Name Size Date Actions
⬆️ Back to Parent Directory
{{if .Thumb}} {{else}}
{{if .Dir}}📁{{else if eq .Ext ".pdf"}}📄{{else if .Img}}🖼️{{else}}📄{{end}}
{{end}}
{{.Name}} {{if .Dir}}Folder{{else if eq .Ext ".pdf"}}PDF Document{{else if .Img}}Image{{else}}File{{end}} {{if not .Dir}}• {{.Ext}}{{end}}
{{.Size}} {{if not .Dir}}
{{.Bytes}} bytes {{end}}
{{.Date}}
{{.Time}}
{{if not .Dir}} {{end}}
` type Item struct { Name, Size, Date, Time, Ext, Thumb, Bytes string Dir, Img bool } type Data struct { Name, Path, Cur, Zip, TotalSize string Items []Item Files, Dirs, Imgs int } type FileInfo struct { Name string `json:"name"` Path string `json:"path"` Size string `json:"size"` Bytes int64 `json:"bytes"` Type string `json:"type"` Extension string `json:"extension"` MimeType string `json:"mimeType"` Modified string `json:"modified"` Permissions string `json:"permissions"` IsDir bool `json:"isDir"` } func isPathSafe(p string) bool { cleanedPath := filepath.Clean(p) return strings.HasPrefix(cleanedPath, absFileDir) } func main() { // Clean expired links go func() { for range time.Tick(10 * time.Minute) { now := time.Now() for k, v := range links { if now.After(v.Expiry) { delete(links, k) } } } }() http.HandleFunc("/", handler) http.HandleFunc("/t", tempHandler) http.HandleFunc("/info", infoHandler) http.Handle("/thumb/", http.StripPrefix("/thumb/", http.HandlerFunc(thumb))) log.Printf("🚀 Professional Server '%s' starting on port %s", serverName, port) log.Printf("📁 Directory: %s", absFileDir) log.Fatal(http.ListenAndServe(":"+port, nil)) } func handler(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/temp/") { serveTemp(w, r) return } if strings.HasPrefix(r.URL.Path, "/thumb/") { thumb(w, r) return } reqPath := path.Clean(r.URL.Path) if reqPath == "/" { reqPath = "" } full := filepath.Join(fileDir, filepath.FromSlash(reqPath)) if !isPathSafe(full) { http.Error(w, "Forbidden: Path Traversal", 403) return } if r.Method == "POST" { // Handle rename if r.FormValue("rename") != "" { oldPath := r.FormValue("oldpath") newName := strings.TrimSpace(r.FormValue("newname")) if newName != "" && !strings.ContainsAny(newName, "/\\") { oldFull := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(oldPath, "/"))) newFull := filepath.Join(filepath.Dir(oldFull), newName) if isPathSafe(oldFull) && isPathSafe(newFull) { os.Rename(oldFull, newFull) } } http.Redirect(w, r, r.URL.Path, 303) return } // Handle delete if r.FormValue("delete") != "" { delPath := r.FormValue("path") fullPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(delPath, "/"))) if isPathSafe(fullPath) { os.RemoveAll(fullPath) } http.Redirect(w, r, r.URL.Path, 303) return } // Handle folder creation if r.FormValue("mkdir") != "" { name := strings.TrimSpace(r.FormValue("name")) if name != "" && !strings.ContainsAny(name, "/\\") { os.Mkdir(filepath.Join(full, name), 0755) } http.Redirect(w, r, r.URL.Path, 303) return } // Handle file upload if err := r.ParseMultipartForm(500 << 20); err != nil { http.Error(w, "Upload error: "+err.Error(), 500) return } for _, h := range r.MultipartForm.File["f"] { f, err := h.Open() if err != nil { continue } defer f.Close() ext := strings.ToLower(filepath.Ext(h.Filename)) dest := full // Auto-sorting if ext == ".pdf" { dest = filepath.Join(fileDir, "pdf") os.MkdirAll(dest, 0755) } else if strings.Contains(".jpg.jpeg.png.gif.webp", ext) { dest = filepath.Join(fileDir, "images") os.MkdirAll(dest, 0755) } out, err := os.Create(filepath.Join(dest, h.Filename)) if err != nil { continue } defer out.Close() io.Copy(out, f) } http.Redirect(w, r, r.URL.Path, 303) return } // Show files entries, err := os.ReadDir(full) if err != nil { if os.IsNotExist(err) { http.ServeFile(w, r, full) } else { http.Error(w, "Read error", 500) } return } var items []Item files, dirs, imgs := 0, 0, 0 var totalSize int64 = 0 // ZIP functionality if strings.HasPrefix(reqPath, "/zip/") { zipPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(reqPath, "/zip"))) if !isPathSafe(zipPath) { http.Error(w, "Forbidden", 403) return } w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", filepath.Base(zipPath))) archive := zip.NewWriter(w) defer archive.Close() filepath.Walk(zipPath, func(p string, info os.FileInfo, err error) error { if err != nil { return err } if p == zipPath { return nil } relPath, err := filepath.Rel(zipPath, p) if err != nil { return err } if info.IsDir() { _, err = archive.Create(relPath + "/") return err } file, err := os.Open(p) if err != nil { return err } defer file.Close() writer, err := archive.Create(relPath) if err != nil { return err } _, err = io.Copy(writer, file) return err }) return } for _, e := range entries { info, err := e.Info() if err != nil { continue } item := Item{ Name: e.Name(), Date: info.ModTime().Format("02 Jan 2006"), Time: info.ModTime().Format("15:04:05"), Dir: e.IsDir(), } if e.IsDir() { item.Size = "Folder" item.Bytes = "-" dirs++ } else { size := info.Size() totalSize += size item.Bytes = formatBytes(size) switch { case size < 1024: item.Size = fmt.Sprintf("%d B", size) case size < 1024*1024: item.Size = fmt.Sprintf("%.1f KB", float64(size)/1024) case size < 1024*1024*1024: item.Size = fmt.Sprintf("%.1f MB", float64(size)/(1024*1024)) default: item.Size = fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024)) } files++ ext := strings.ToLower(filepath.Ext(e.Name())) item.Ext = ext if strings.Contains(".jpg.jpeg.png.gif.webp", ext) { imgs++ item.Img = true item.Thumb = "/thumb" + path.Join("/", reqPath, e.Name()) } } items = append(items, item) } sort.Slice(items, func(i, j int) bool { if items[i].Dir != items[j].Dir { return items[i].Dir } return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name) }) totalSizeStr := formatBytes(totalSize) if totalSize == 0 { totalSizeStr = "0 B" } data := Data{ Name: serverName, Path: reqPath, Cur: "/" + reqPath, Zip: reqPath, Items: items, Files: files, Dirs: dirs, Imgs: imgs, TotalSize: totalSizeStr, } tmpl.Execute(w, data) } func infoHandler(w http.ResponseWriter, r *http.Request) { pathParam := r.URL.Query().Get("path") if pathParam == "" { http.Error(w, "Path parameter required", 400) return } fullPath := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(pathParam, "/"))) if !isPathSafe(fullPath) { http.Error(w, "Forbidden", 403) return } info, err := os.Stat(fullPath) if err != nil { http.Error(w, "File not found", 404) return } fileInfo := FileInfo{ Name: filepath.Base(fullPath), Path: pathParam, Bytes: info.Size(), Modified: info.ModTime().Format("02 Jan 2006 15:04:05"), Permissions: info.Mode().String(), IsDir: info.IsDir(), } if info.IsDir() { fileInfo.Type = "Folder" fileInfo.Size = "Folder" fileInfo.Extension = "-" fileInfo.MimeType = "-" } else { ext := filepath.Ext(fullPath) fileInfo.Type = "File" fileInfo.Extension = ext fileInfo.MimeType = getMimeType(ext) size := info.Size() switch { case size < 1024: fileInfo.Size = fmt.Sprintf("%d B", size) case size < 1024*1024: fileInfo.Size = fmt.Sprintf("%.1f KB", float64(size)/1024) case size < 1024*1024*1024: fileInfo.Size = fmt.Sprintf("%.1f MB", float64(size)/(1024*1024)) default: fileInfo.Size = fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024)) } } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(fileInfo) } func getMimeType(ext string) string { switch strings.ToLower(ext) { case ".pdf": return "application/pdf" case ".jpg", ".jpeg": return "image/jpeg" case ".png": return "image/png" case ".gif": return "image/gif" case ".txt": return "text/plain" case ".html", ".htm": return "text/html" case ".zip": return "application/zip" case ".mp4": return "video/mp4" case ".mp3": return "audio/mpeg" default: return "application/octet-stream" } } func formatBytes(bytes int64) string { if bytes < 1024 { return fmt.Sprintf("%d", bytes) } else if bytes < 1024*1024 { return fmt.Sprintf("%.0f", float64(bytes)/1024) } else if bytes < 1024*1024*1024 { return fmt.Sprintf("%.0f", float64(bytes)/(1024*1024)) } else { return fmt.Sprintf("%.1f", float64(bytes)/(1024*1024*1024)) } } func thumb(w http.ResponseWriter, r *http.Request) { p := filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(r.URL.Path, "/thumb"))) if !isPathSafe(p) { return } f, err := os.Open(p) if err != nil { return } defer f.Close() img, _, err := image.Decode(f) if err != nil { return } b := img.Bounds() nw, nh := 100, 80 if b.Dx() > b.Dy() { nh = b.Dy() * 100 / b.Dx() } else { nw = b.Dx() * 80 / b.Dy() } resized := image.NewRGBA(image.Rect(0, 0, nw, nh)) for y := 0; y < nh; y++ { for x := 0; x < nw; x++ { resized.Set(x, y, img.At(x*b.Dx()/nw, y*b.Dy()/nh)) } } w.Header().Set("Content-Type", "image/jpeg") jpeg.Encode(w, resized, &jpeg.Options{Quality: 85}) } func tempHandler(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Method not allowed", 405) return } if err := r.ParseForm(); err != nil { http.Error(w, "Parse error", 500) return } p := r.FormValue("p") e := r.FormValue("e") cleanedPath := filepath.Clean(filepath.Join(fileDir, filepath.FromSlash(strings.TrimPrefix(p, "/")))) if !isPathSafe(cleanedPath) { http.Error(w, "Invalid path", 400) return } var exp time.Time once := false switch e { case "1h": exp = time.Now().Add(1 * time.Hour) case "24h": exp = time.Now().Add(24 * time.Hour) case "7d": exp = time.Now().Add(7 * 24 * time.Hour) case "once": once = true exp = time.Now().Add(30 * 24 * time.Hour) default: exp = time.Now().Add(24 * time.Hour) } tok := make([]byte, 16) rand.Read(tok) token := hex.EncodeToString(tok) links[token] = struct { Path string Expiry time.Time OneTime bool Used bool }{ Path: cleanedPath, Expiry: exp, OneTime: once, } link := fmt.Sprintf("http://%s/temp/%s", r.Host, token) fmt.Fprintf(w, ``, exp.Format("02 Jan 2006 15:04"), link) } func serveTemp(w http.ResponseWriter, r *http.Request) { token := strings.TrimPrefix(r.URL.Path, "/temp/") l, ok := links[token] if !ok || time.Now().After(l.Expiry) || (l.OneTime && l.Used) { http.Error(w, "Link expired", 410) return } if !isPathSafe(l.Path) { http.Error(w, "Invalid link", 403) return } if l.OneTime { links[token] = struct { Path string Expiry time.Time OneTime bool Used bool }{ Path: l.Path, Expiry: l.Expiry, OneTime: true, Used: true, } } http.ServeFile(w, r, l.Path) } EOF # Създаване на Go модул и компилиране echo 'module fedya' > go.mod echo -e "\e[1;33m🔨 Compiling Professional Server...\e[0m" if ! CGO_ENABLED=0 go build -ldflags="-s -w" -o fedya-pro 2>&1; then echo -e "\e[1;31m❌ Compilation error!\e[0m" exit 1 fi # Инсталиране на изпълнимия файл install -m 755 fedya-pro /usr/local/bin/fedya-pro # Спиране на старата услуга systemctl stop fedya-pro 2>/dev/null || true if $SERVICE; then # Създаване на systemd услуга cat > /etc/systemd/system/fedya-pro.service <