From 1b0cbbf5cd441b6283bef0c11c2b623b2042232a Mon Sep 17 00:00:00 2001 From: nxshock Date: Tue, 31 Aug 2021 19:07:19 +0500 Subject: [PATCH] Import project --- PKGBUILD | 32 +++++++ config.go | 47 +++++++++++ consts_linux.go | 5 ++ consts_windows.go | 5 ++ handlers.go | 117 +++++++++++++++++++++++++ index.htm | 178 +++++++++++++++++++++++++++++++++++++++ main.go | 53 ++++++++++++ simplefileshare.conf | 10 +++ simplefileshare.service | 41 +++++++++ simplefileshare.sysusers | 1 + templates.go | 27 ++++++ utils.go | 28 ++++++ walker.go | 44 ++++++++++ 13 files changed, 588 insertions(+) create mode 100644 PKGBUILD create mode 100644 config.go create mode 100644 consts_linux.go create mode 100644 consts_windows.go create mode 100644 handlers.go create mode 100644 index.htm create mode 100644 main.go create mode 100644 simplefileshare.conf create mode 100644 simplefileshare.service create mode 100644 simplefileshare.sysusers create mode 100644 templates.go create mode 100644 utils.go create mode 100644 walker.go diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..ce3d7ed --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,32 @@ +pkgname=simplefileshare +pkgver=0.1.0 +pkgrel=0 +pkgdesc="Simple file share" +arch=('x86_64' 'aarch64') +license=('GPL') +url="https://github.com/nxshock/$pkgname" +makedepends=('go' 'git') +options=('!strip') +backup=("etc/$pkgname.toml") +source=("git+https://github.com/nxshock/$pkgname.git") +sha256sums=('SKIP') + +build() { + cd "$srcdir/$pkgname" + + export CGO_CPPFLAGS="${CPPFLAGS}" + export CGO_CFLAGS="${CFLAGS}" + export CGO_CXXFLAGS="${CXXFLAGS}" + export CGO_LDFLAGS="${LDFLAGS}" + + go build -o $pkgname -buildmode=pie -trimpath -ldflags="-linkmode=external -s -w" +} + +package() { + cd "$srcdir/$pkgname" + + install -Dm755 "$pkgname" "$pkgdir/usr/bin/$pkgname" + install -Dm644 "$pkgname.conf" "$pkgdir/etc/$pkgname.conf" + install -Dm644 "$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service" + install -Dm644 "$pkgname.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgname.conf" +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..c48a027 --- /dev/null +++ b/config.go @@ -0,0 +1,47 @@ +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/BurntSushi/toml" + log "github.com/sirupsen/logrus" +) + +var config *Config + +type Config struct { + ListenAddress string + StoragePath string + RemoveFilePeriod uint // hours + LogLevel log.Level +} + +func initConfig() error { + log.Debugln("Сonfig initialization started.") + defer log.Debugln("Сonfig initialization finished.") + + var configFilePath string + + if len(os.Args) < 2 { + configFilePath = defaultConfigFilePath + } else { + configFilePath = os.Args[1] + } + + _, err := toml.DecodeFile(configFilePath, &config) + if err != nil { + return err + } + + stat, err := os.Stat(config.StoragePath) + if err != nil { + return fmt.Errorf("os.Stat(config.StoragePath): %v", err) + } + if !stat.IsDir() { + return errors.New("StoragePath is not a dir") + } + + return nil +} diff --git a/consts_linux.go b/consts_linux.go new file mode 100644 index 0000000..352839e --- /dev/null +++ b/consts_linux.go @@ -0,0 +1,5 @@ +package main + +const ( + defaultConfigFilePath = "/etc/simplefileshare.conf" +) diff --git a/consts_windows.go b/consts_windows.go new file mode 100644 index 0000000..bf14874 --- /dev/null +++ b/consts_windows.go @@ -0,0 +1,5 @@ +package main + +const ( + defaultConfigFilePath = "simplefileshare.conf" +) diff --git a/handlers.go b/handlers.go new file mode 100644 index 0000000..9383ee4 --- /dev/null +++ b/handlers.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "io" + "mime" + "net/http" + "os" + "path/filepath" + "strconv" +) + +func HandleRoot(w http.ResponseWriter, r *http.Request) { + if r.RequestURI != "/" { + http.Error(w, "", http.StatusNotFound) + return + } + + type FileInfo struct { + Name string + Size string + Date string + } + + var data []FileInfo + + err := filepath.Walk(config.StoragePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + data = append(data, FileInfo{filepath.Base(path), sizeToApproxHuman(info.Size()), info.ModTime().Format("02.01.2006 15:04")}) + + return nil + }) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + err = templates.ExecuteTemplate(w, "index.htm", data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func HandleUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "wrong method", http.StatusBadRequest) + return + } + + err := r.ParseMultipartForm(32 << 20) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + file, header, err := r.FormFile("file") + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + filePath := filepath.Join(config.StoragePath, header.Filename) + if _, err := os.Stat(filePath); !os.IsNotExist(err) { + http.Error(w, "файл с таким именем уже существует", http.StatusBadRequest) + return + } + + f, err := os.Create(filePath) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer f.Close() + + _, err = io.Copy(f, file) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } +} + +func HandleDownload(w http.ResponseWriter, r *http.Request) { + filename := filepath.Base(r.FormValue("filename")) + + if filename == "" { + http.Error(w, `"filename" field can not be empty`, http.StatusBadRequest) + return + } + + f, err := os.Open(filepath.Join(config.StoragePath, filename)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer f.Close() + + fileStat, err := f.Stat() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) + w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(filename))) + w.Header().Set("Accept-Ranges", "none") + w.Header().Set("Content-Length", strconv.Itoa(int(fileStat.Size()))) + + io.CopyBuffer(w, f, make([]byte, 4096)) +} diff --git a/index.htm b/index.htm new file mode 100644 index 0000000..6e632fb --- /dev/null +++ b/index.htm @@ -0,0 +1,178 @@ + + + + + + + File Storage + + + + +
+ File Storage + +
+
+ + + + + + + + + +{{range .}} + + + + +{{end}}
ИмяРазмерДата
{{.Name}}
{{.Size}}
{{.Date}}
+
+ + + + diff --git a/main.go b/main.go new file mode 100644 index 0000000..cb166ec --- /dev/null +++ b/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" +) + +func init() { + log.SetOutput(os.Stderr) + log.SetFormatter(&logrus.TextFormatter{ForceColors: true, DisableTimestamp: true}) + log.SetLevel(log.ErrorLevel) + + err := initConfig() + if err != nil { + log.Fatalln("initConfig:", err) + } + + log.SetLevel(config.LogLevel) + + err = initTemplates() + if err != nil { + log.Fatalln("initTemplates:", err) + } + + if config.RemoveFilePeriod > 0 { + go removeOldFilesThread(config.StoragePath, time.Duration(config.RemoveFilePeriod)*time.Hour) + } + + http.HandleFunc("/", HandleRoot) + http.HandleFunc("/upload", HandleUpload) + http.HandleFunc("/download", HandleDownload) +} + +func main() { + go func() { + err := http.ListenAndServe(config.ListenAddress, nil) + if err != nil { + log.Fatalln(err) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + <-c + log.Debugln("Stop signal received.") +} diff --git a/simplefileshare.conf b/simplefileshare.conf new file mode 100644 index 0000000..2684d49 --- /dev/null +++ b/simplefileshare.conf @@ -0,0 +1,10 @@ +# HTTP-server listen address +ListenAddress = ":8000" + +# File storage path +StoragePath = "files" + +# File removing period (hours) +RemoveFilePeriod = 1 + +LogLevel = "debug" diff --git a/simplefileshare.service b/simplefileshare.service new file mode 100644 index 0000000..18f7b69 --- /dev/null +++ b/simplefileshare.service @@ -0,0 +1,41 @@ +[Unit] +Description=simplefileshare service + +[Service] +Type=simple +User=simplefileshare +ExecStart=/usr/bin/simplefileshare +Restart=on-failure +RestartSec=10s + +SecureBits=keep-caps +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_BIND_SERVICE +DevicePolicy=closed +IPAccounting=true +LockPersonality=true +MemoryDenyWriteExecute=true +NoNewPrivileges=true +PrivateDevices=true +PrivateTmp=true +ProtectClock=true +ProtectControlGroups=true +ProtectControlGroups=true +ProtectHome=true +ProtectHostname=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectSystem=strict +ReadWritePaths= +RemoveIPC=true +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallFilter=~@resources +UMask=0027 + +[Install] +WantedBy=multi-user.target diff --git a/simplefileshare.sysusers b/simplefileshare.sysusers new file mode 100644 index 0000000..ee78e1f --- /dev/null +++ b/simplefileshare.sysusers @@ -0,0 +1 @@ +u simplefileshare - "simplefileshare user" diff --git a/templates.go b/templates.go new file mode 100644 index 0000000..43da7a2 --- /dev/null +++ b/templates.go @@ -0,0 +1,27 @@ +package main + +import ( + "embed" + "html/template" + + log "github.com/sirupsen/logrus" +) + +//go:embed index.htm +var templatesFS embed.FS + +var templates *template.Template + +func initTemplates() error { + log.Debugln("Templates initialization started.") + defer log.Debugln("Templates initialization finished.") + + var err error + templates, err = template.ParseFS(templatesFS, "*.htm") + if err != nil { + return err + } + templatesFS = embed.FS{} // free memory + + return nil +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..25983f5 --- /dev/null +++ b/utils.go @@ -0,0 +1,28 @@ +package main + +import "fmt" + +func sizeToApproxHuman(s int64) string { + t := []struct { + Name string + Val int64 + }{ + {"EiB", 1 << 60}, + {"PiB", 1 << 50}, + {"TiB", 1 << 40}, + {"GiB", 1 << 30}, + {"MiB", 1 << 20}, + {"KiB", 1 << 10}} + + var v float64 + for i := 0; i < len(t); i++ { + v = float64(s) / float64(t[i].Val) + if v < 1.0 { + continue + } + + return fmt.Sprintf("%.1f %s", v, t[i].Name) + } + + return fmt.Sprintf("%.1f KiB", v) +} diff --git a/walker.go b/walker.go new file mode 100644 index 0000000..c11c383 --- /dev/null +++ b/walker.go @@ -0,0 +1,44 @@ +package main + +import ( + "os" + "path/filepath" + "time" + + log "github.com/sirupsen/logrus" +) + +func removeOldFilesThread(path string, olderThan time.Duration) { + ticker := time.NewTicker(olderThan) + + for _ = range ticker.C { + log.Debugln("Removing old files...") + err := removeOldFiles(path, olderThan) + if err != nil { + log.Println(err) + } + log.Debugln("Removing old files completed.") + } +} + +func removeOldFiles(path string, olderThan time.Duration) error { + return filepath.Walk(config.StoragePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + if info.ModTime().Add(olderThan).Before(time.Now()) { + log.WithField("filepath", path).Debugln("Removing file...") + err := os.Remove(path) + if err != nil { + log.Println(err) + } + } + + return nil + }) + +}