Compare commits
141 Commits
Author | SHA1 | Date | |
---|---|---|---|
7218fbc27f | |||
5148202a0f | |||
1edba52d32 | |||
9c42144b45 | |||
58197af85d | |||
4e098ec665 | |||
5567de7de1 | |||
63e03f6459 | |||
90837f1299 | |||
b50a80a33d | |||
92212c97eb | |||
3f16c3e799 | |||
34c31448bb | |||
514c6208e2 | |||
88b989bfbb | |||
7915091b96 | |||
f22fa1ee1d | |||
34890ff93f | |||
fea3e7104d | |||
2dc523271c | |||
4048390bfb | |||
6106e817cf | |||
30ffa6805b | |||
17781c5445 | |||
3b15052330 | |||
271c27f4ad | |||
532e8d9da6 | |||
72d3104150 | |||
5bd6120ae5 | |||
d919d00cc0 | |||
1e30e5e8ff | |||
c486acef79 | |||
aaa6aca743 | |||
d251d174e3 | |||
82915fb300 | |||
e7e45259ba | |||
df8baf153b | |||
b513a5ff1d | |||
c2278fdd2e | |||
df3c5e0678 | |||
266cf9dbb0 | |||
6b56037ded | |||
9e20c424be | |||
d77552049a | |||
59e0629597 | |||
6b8a7dffc7 | |||
084872be61 | |||
5590b0ad71 | |||
e430f0bfc9 | |||
1c7f03053f | |||
7e4af6b977 | |||
5c6cf2586f | |||
9c72131dc8 | |||
d81a29bd5b | |||
d5052994b8 | |||
2d4169245c | |||
949c2e68c8 | |||
3799c1d178 | |||
99adb7fbf8 | |||
aa3ca9a4ad | |||
96fd7e5883 | |||
31f84721ad | |||
e695becbcd | |||
114d3cc931 | |||
9a889e746a | |||
345869608e | |||
d94030a4e8 | |||
54f87951c1 | |||
da001dbe39 | |||
51494e87be | |||
92710f7f5a | |||
2abf5a9d7f | |||
860d719cdd | |||
30513b3300 | |||
b695d8128d | |||
85f853f48e | |||
dd22577521 | |||
1e4836ca86 | |||
e47e592b01 | |||
38e6813d11 | |||
b18338e4e1 | |||
0677147b63 | |||
a9e230ad61 | |||
a7972b8e13 | |||
c5faef303b | |||
5101a892de | |||
8eb8a24a23 | |||
afbaad971a | |||
fccb81d3a5 | |||
3327b30d12 | |||
59544da625 | |||
85529890a5 | |||
9510027d05 | |||
55ba1fd67d | |||
203c0158ce | |||
282a00c328 | |||
99eed674fc | |||
cf7e240e8a | |||
705a4ede76 | |||
dab675474a | |||
3f79eb5b08 | |||
f0fc34c8e7 | |||
2ef85c6f29 | |||
b16ec84e86 | |||
f8351b935d | |||
64132ec18f | |||
fb9cee2c0a | |||
185bd80750 | |||
a04adf7fa1 | |||
6e9ad9323c | |||
06dfcaac8e | |||
35468af206 | |||
3696e1dfe1 | |||
14ec537d82 | |||
14efad4a4a | |||
9c5bad04d4 | |||
21d05e7488 | |||
ec2656c719 | |||
b95052709a | |||
af0492e5a0 | |||
57aea7ef77 | |||
badeda87ad | |||
cc1cbeffcd | |||
332e9a1bb3 | |||
931f98d12e | |||
11dfbfbc23 | |||
470bbb04ee | |||
deed268b4f | |||
33f69eaf44 | |||
c85c6555e2 | |||
c43c314a78 | |||
ce6d81ad55 | |||
88ec5e5efe | |||
33ec19647b | |||
31d2c4fd21 | |||
7e1b56bbc5 | |||
50ca035a1f | |||
c53dab9e6b | |||
15dd184106 | |||
cb1dd51750 | |||
d7191d0fbd |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ bin/*
|
|||||||
!bin/.keep
|
!bin/.keep
|
||||||
.vscode
|
.vscode
|
||||||
*.jade.go
|
*.jade.go
|
||||||
|
jade.go
|
26
Makefile
26
Makefile
@ -5,50 +5,44 @@ SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
|
|||||||
SYSDDIR=${SYSDDIR_:/%=%}
|
SYSDDIR=${SYSDDIR_:/%=%}
|
||||||
DESTDIR=/
|
DESTDIR=/
|
||||||
|
|
||||||
VERSION=23.19.0
|
VERSION=23.34.0
|
||||||
|
|
||||||
|
FLAGS=-trimpath -mod=readonly -modcacherw
|
||||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
||||||
|
|
||||||
all: ${TARGET}
|
all: web/*.jade.go ${TARGET}
|
||||||
|
|
||||||
.PHONY: ${TARGET}
|
.PHONY: ${TARGET}
|
||||||
|
|
||||||
${TARGET}:
|
${TARGET}:
|
||||||
go generate web/web.go
|
go build -o bin/$@ ${FLAGS} ${LDFLAGS} cmd/$@/main.go
|
||||||
go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go
|
go build -o bin/$@-clean ${FLAGS} ${LDFLAGS} cmd/$@-clean/main.go
|
||||||
go build -o bin/$@-clean ${LDFLAGS} cmd/$@-clean/main.go
|
|
||||||
|
|
||||||
install-jade:
|
web/*.jade.go: web/templates/*.jade
|
||||||
go install github.com/Joker/jade/cmd/jade@latest
|
go install github.com/Joker/jade/cmd/jade@latest
|
||||||
|
go generate web/web.go
|
||||||
|
|
||||||
run:
|
|
||||||
bin/${TARGET} -conf configs/config.yaml
|
|
||||||
|
|
||||||
run-clean:
|
|
||||||
bin/${TARGET}-clean -conf configs/config.yaml
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET}
|
install -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET}
|
||||||
install -Dm 0755 bin/${TARGET}-clean ${DESTDIR}usr/bin/${TARGET}-clean
|
install -Dm 0755 bin/${TARGET}-clean ${DESTDIR}usr/bin/${TARGET}-clean
|
||||||
install -Dm 0644 configs/config.yaml ${DESTDIR}etc/dwelling/upload.yaml
|
install -Dm 0755 tools/gen-salt.sh ${DESTDIR}usr/bin/${TARGET}-gen-salt
|
||||||
install -Dm 0644 configs/logrotate ${DESTDIR}etc/logrotate.d/${TARGET}
|
install -Dm 0644 configs/logrotate ${DESTDIR}etc/logrotate.d/${TARGET}
|
||||||
|
|
||||||
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||||
install -Dm 0644 init/systemd/${TARGET}-clean.timer ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
install -Dm 0644 init/systemd/${TARGET}-clean.timer ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
||||||
install -Dm 0644 init/systemd/${TARGET}-clean.service ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
install -Dm 0644 init/systemd/${TARGET}-clean.service ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
||||||
|
|
||||||
install -Dm 0644 build/dwelling-upload.conf ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm ${DESTDIR}usr/bin/${TARGET}
|
rm ${DESTDIR}usr/bin/${TARGET}
|
||||||
rm ${DESTDIR}usr/bin/${TARGET}-clean
|
rm ${DESTDIR}usr/bin/${TARGET}-clean
|
||||||
|
rm ${DESTDIR}usr/bin/${TARGET}-gen-salt
|
||||||
rm ${DESTDIR}etc/logrotate.d/${TARGET}
|
rm ${DESTDIR}etc/logrotate.d/${TARGET}
|
||||||
|
|
||||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
|
||||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
|
||||||
|
|
||||||
rm ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
rm -f web/*.jade.go
|
||||||
go clean
|
go clean
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||||
pkgname=dwelling-upload
|
pkgname=dwelling-upload
|
||||||
pkgver=23.19.0
|
pkgver=23.34.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Arav's Dwelling / Upload"
|
pkgdesc="Arav's Dwelling / Upload"
|
||||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
||||||
@ -9,19 +9,16 @@ license=('MIT')
|
|||||||
makedepends=('go>=1.17')
|
makedepends=('go>=1.17')
|
||||||
provides=('dwelling-upload')
|
provides=('dwelling-upload')
|
||||||
conflicts=('dwelling-upload')
|
conflicts=('dwelling-upload')
|
||||||
backup=('etc/dwelling/upload.yaml')
|
|
||||||
source=("https://git.arav.su/Arav/dwelling-upload/archive/v${pkgver}.tar.gz")
|
source=("https://git.arav.su/Arav/dwelling-upload/archive/v${pkgver}.tar.gz")
|
||||||
md5sums=('SKIP')
|
md5sums=('SKIP')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "$srcdir/$pkgname"
|
cd "$srcdir/$pkgname"
|
||||||
|
|
||||||
export GOPATH="$srcdir"/gopath
|
export GOPATH="$srcdir"/gopath
|
||||||
|
export CGO_CPPFLAGS="${CPPFLAGS}"
|
||||||
if [ ! -f "$(go env GOPATH)/bin/jade" ]; then
|
export CGO_CFLAGS="${CFLAGS}"
|
||||||
make DESTDIR="$pkgdir/" install-jade
|
export CGO_CXXFLAGS="${CXXFLAGS}"
|
||||||
fi
|
export CGO_LDFLAGS="${LDFLAGS}"
|
||||||
|
|
||||||
make VERSION=$pkgver DESTDIR="$pkgdir/"
|
make VERSION=$pkgver DESTDIR="$pkgdir/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# sysusers.d
|
|
||||||
g dwupload - -
|
|
||||||
u dwupload - -
|
|
||||||
m dwupload dwupload
|
|
@ -1,70 +1,48 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dwelling-upload/internal/configuration"
|
|
||||||
"dwelling-upload/pkg/logging"
|
|
||||||
"dwelling-upload/pkg/utils"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configPath *string = flag.String("conf", "config.yaml", "path to configuration file")
|
var (
|
||||||
var showVersion *bool = flag.Bool("v", false, "show version")
|
uploadDir = flag.String("dir", "/srv/upload", "path to a directory where uploaded files are stored")
|
||||||
|
expiry = flag.Duration("expiry", 36*time.Hour, "keep files for this much hours")
|
||||||
|
|
||||||
|
showVersion = flag.Bool("v", false, "show version")
|
||||||
|
)
|
||||||
|
|
||||||
var version string
|
var version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.SetFlags(log.Llongfile)
|
||||||
|
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
fmt.Println("dwelling-upload-clean Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
fmt.Println("dwelling-upload-clean Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := configuration.LoadConfiguration(*configPath)
|
uploadsDir, err := os.ReadDir(*uploadDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalf("failed to open a directory %s: %s\n", *uploadDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logErr, err := logging.New(config.Log.CleanError)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("failed to open error logger:", err)
|
|
||||||
}
|
|
||||||
defer logErr.Close()
|
|
||||||
|
|
||||||
logClean, err := logging.New(config.Log.Clean)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("failed to open error logger:", err)
|
|
||||||
}
|
|
||||||
defer logClean.Close()
|
|
||||||
|
|
||||||
uploadsDir, err := ioutil.ReadDir(config.Uploads.Directory)
|
|
||||||
if err != nil {
|
|
||||||
logErr.Fatalf("failed to open directory %s: %s\n", config.Uploads.Directory, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var deletedCount int64 = 0
|
|
||||||
var deletedSize int64 = 0
|
|
||||||
|
|
||||||
for _, entry := range uploadsDir {
|
for _, entry := range uploadsDir {
|
||||||
if time.Now().UTC().Sub(entry.ModTime().UTC()) >= time.Duration(config.Uploads.Limits.KeepForHours)*time.Hour {
|
file, err := os.Stat(path.Join(*uploadDir, entry.Name()))
|
||||||
if err := os.Remove(path.Join(config.Uploads.Directory, entry.Name())); err != nil {
|
if err != nil {
|
||||||
logErr.Println("failed to remove file ", entry.Name(), ": ", err)
|
log.Printf("failed to stat a file %s: %s", entry.Name(), err)
|
||||||
} else {
|
continue
|
||||||
deletedSize += entry.Size()
|
}
|
||||||
deletedCount++
|
|
||||||
|
if time.Now().UTC().Sub(file.ModTime().UTC()) >= *expiry {
|
||||||
|
if err := os.Remove(path.Join(*uploadDir, entry.Name())); err != nil {
|
||||||
|
log.Printf("failed to remove a file %s: %s", entry.Name(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, cFSz := utils.ConvertFileSize(deletedSize)
|
|
||||||
|
|
||||||
if deletedCount > 0 {
|
|
||||||
logClean.Printf("%d %s", deletedCount, cFSz)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,129 +1,120 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dwelling-upload/internal/configuration"
|
duihttp "dwelling-upload/internal/http"
|
||||||
"dwelling-upload/internal/http"
|
|
||||||
"dwelling-upload/pkg/logging"
|
|
||||||
"dwelling-upload/pkg/utils"
|
"dwelling-upload/pkg/utils"
|
||||||
"dwelling-upload/pkg/watcher"
|
"dwelling-upload/pkg/watcher"
|
||||||
|
"dwelling-upload/web"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.arav.su/Arav/httpr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configPath *string = flag.String("conf", "config.yaml", "path to configuration file")
|
var (
|
||||||
var showVersion *bool = flag.Bool("v", false, "show version")
|
listenAddress = flag.String("listen", "/var/run/dwelling-upload/sock", "listen address (ip:port|unix_path)")
|
||||||
|
uploadDir = flag.String("dir", "/srv/upload", "directory where uploaded files are stored")
|
||||||
|
expiry = flag.Int("expiry", 36, "keep files for this much hours")
|
||||||
|
storageSize = flag.Int64("storage", 102400, "storage size in MiB for uploads")
|
||||||
|
fileSize = flag.Int64("file", 128, "max. size in MiB for files")
|
||||||
|
|
||||||
|
showVersion *bool = flag.Bool("v", false, "show version")
|
||||||
|
)
|
||||||
|
|
||||||
var version string
|
var version string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.SetFlags(log.Llongfile)
|
||||||
|
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
fmt.Println("dwelling-upload Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
fmt.Println("dwelling-upload Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := configuration.LoadConfiguration(*configPath)
|
hashSalt, err := os.ReadFile(path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "salt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln("failed to read hash salt file:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logFilePath := path.Join(os.Getenv("LOGS_DIRECTORY"), "file.log")
|
||||||
|
|
||||||
|
logFileFd, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("failed to open file.log:", err)
|
||||||
|
}
|
||||||
|
defer logFileFd.Close()
|
||||||
|
|
||||||
|
logFile := log.New(logFileFd, "", log.LstdFlags)
|
||||||
|
|
||||||
|
uploadDirSize, err := utils.DirectorySize(*uploadDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to get initial size of %s: %s", *uploadDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hand := duihttp.NewUploadHandlers(logFile, *uploadDir, &uploadDirSize, string(hashSalt),
|
||||||
|
*expiry, *storageSize, *fileSize)
|
||||||
|
|
||||||
|
r := httpr.New()
|
||||||
|
|
||||||
|
r.NotFoundHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
duihttp.Error(w, r, "", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handler(http.MethodGet, "/", hand.Index)
|
||||||
|
r.Handler(http.MethodPost, "/", hand.Upload)
|
||||||
|
r.Handler(http.MethodGet, "/:hash/:name", hand.Download)
|
||||||
|
r.Handler(http.MethodPost, "/delete", hand.Delete)
|
||||||
|
r.Handler(http.MethodDelete, "/:hash", hand.Delete)
|
||||||
|
|
||||||
|
r.ServeStatic("/assets/*filepath", web.Assets())
|
||||||
|
r.Handler(http.MethodGet, "/robots.txt", duihttp.RobotsTxt)
|
||||||
|
r.Handler(http.MethodGet, "/favicon.svg", duihttp.Favicon)
|
||||||
|
|
||||||
|
srv := duihttp.NewHttpServer(r)
|
||||||
|
|
||||||
|
if err := srv.Start(*listenAddress); err != nil {
|
||||||
|
log.Fatalln("failed to start a server:", err)
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if typ, addr := config.SplitNetworkAddress(); typ == "unix" {
|
if err := srv.Stop(); err != nil {
|
||||||
os.Remove(addr)
|
log.Fatalln("failed to properly shutdown a server:", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logErr, err := logging.New(config.Log.Error)
|
doneSignal := make(chan os.Signal, 1)
|
||||||
if err != nil {
|
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
log.Fatalln("error logger:", err)
|
|
||||||
}
|
|
||||||
defer logErr.Close()
|
|
||||||
|
|
||||||
logUpload, err := logging.New(config.Log.Upload)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("upload logger:", err)
|
|
||||||
}
|
|
||||||
defer logUpload.Close()
|
|
||||||
|
|
||||||
logDownload, err := logging.New(config.Log.Download)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("download logger:", err)
|
|
||||||
}
|
|
||||||
defer logDownload.Close()
|
|
||||||
|
|
||||||
logDelete, err := logging.New(config.Log.Delete)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("delete logger:", err)
|
|
||||||
}
|
|
||||||
defer logDelete.Close()
|
|
||||||
|
|
||||||
watcha, err := watcher.NewInotifyWatcher()
|
watcha, err := watcher.NewInotifyWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logErr.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer watcha.Close()
|
defer watcha.Close()
|
||||||
|
|
||||||
if err := watcha.AddWatch(config.Uploads.Directory, watcher.CrDelMask); err != nil {
|
if err := watcha.AddWatch(*uploadDir, watcher.CrDelMask); err != nil {
|
||||||
logErr.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadDirNotify := make(chan uint32)
|
uploadDirNotify := make(chan uint32)
|
||||||
uploadDirSize, err := utils.DirectorySize(config.Uploads.Directory)
|
go watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
|
||||||
if err != nil {
|
|
||||||
logErr.Fatalf("failed to get initial size of %s: %s", config.Uploads.Directory, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
|
|
||||||
|
|
||||||
hand := http.NewUploadHandlers(config, logErr, logUpload, logDownload, logDelete, &uploadDirSize)
|
|
||||||
srv := http.NewHttpServer()
|
|
||||||
|
|
||||||
srv.SetNotFoundHandler(http.NotFound)
|
|
||||||
srv.GET("/robots.txt", http.RobotsTxt)
|
|
||||||
srv.ServeStatic("/assets/*filepath", hand.AssetsFS())
|
|
||||||
srv.GET("/", hand.Index)
|
|
||||||
srv.POST("/", hand.Upload)
|
|
||||||
srv.POST("/delete", hand.Delete)
|
|
||||||
srv.GET("/f/:hash/:name", hand.Download)
|
|
||||||
srv.DELETE("/:hash", hand.Delete)
|
|
||||||
|
|
||||||
if err := srv.Start(config.SplitNetworkAddress()); err != nil {
|
|
||||||
logErr.Fatalln("failed to start a server:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
doneSignal := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
logReopenSignal := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(logReopenSignal, syscall.SIGHUP)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-logReopenSignal:
|
|
||||||
logErr.Reopen(config.Log.Error)
|
|
||||||
logUpload.Reopen(config.Log.Upload)
|
|
||||||
logDownload.Reopen(config.Log.Download)
|
|
||||||
case <-uploadDirNotify:
|
case <-uploadDirNotify:
|
||||||
sz, err := utils.DirectorySize(config.Uploads.Directory)
|
uploadDirSize, err = utils.DirectorySize(*uploadDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logErr.Println("failed to get uploads directory size:", err)
|
log.Println("failed to get uploads directory size:", err)
|
||||||
}
|
|
||||||
|
|
||||||
if sz > 0 {
|
|
||||||
uploadDirSize = sz
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-doneSignal
|
<-doneSignal
|
||||||
|
|
||||||
if err := srv.Stop(); err != nil {
|
|
||||||
logErr.Fatalln("failed to properly shutdown a server:", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
# Sets network type (could be tcp{,4,6}, unix)
|
|
||||||
# and address:port or /path/to/unix.sock to
|
|
||||||
# listen on.
|
|
||||||
listen_on: "unix /var/run/dwelling-upload/sock"
|
|
||||||
# Salt for hash of uploaded files.
|
|
||||||
# Aim is to make links bruteforcing useless.
|
|
||||||
hash_salt: "iyP3oZWHI3xO3XBF7s78Vg"
|
|
||||||
# Logging options.
|
|
||||||
log:
|
|
||||||
# dwelling-upload logs.
|
|
||||||
error: "/var/log/dwelling-upload/error.log"
|
|
||||||
upload: "/var/log/dwelling-upload/upload.log"
|
|
||||||
download: "/var/log/dwelling-upload/download.log"
|
|
||||||
delete: "/var/log/dwelling-upload/delete.log"
|
|
||||||
# dwelling-upload-clean logs.
|
|
||||||
clean: "/var/log/dwelling-upload/clean.log"
|
|
||||||
clean_error: "/var/log/dwelling-upload/clean_error.log"
|
|
||||||
uploads:
|
|
||||||
# Path where to put uploaded files.
|
|
||||||
directory: "/srv/uploads"
|
|
||||||
limits:
|
|
||||||
# Maximum size of a file in MiB.
|
|
||||||
file_size: 128
|
|
||||||
# Amount of hours file will be keeped for.
|
|
||||||
keep_for_hours: 48
|
|
||||||
# Maximum size of a whole storage in MiB.
|
|
||||||
storage: 204800
|
|
@ -1,15 +1,12 @@
|
|||||||
/var/log/dwelling-upload/*log {
|
/var/log/dwelling-upload/*log {
|
||||||
nocreate
|
nocreate
|
||||||
|
copytruncate
|
||||||
size 1M
|
size 1M
|
||||||
compress
|
compress
|
||||||
compresscmd /usr/bin/zstd
|
compresscmd /usr/bin/zstd
|
||||||
compressext .zst
|
compressext .zst
|
||||||
compressoptions -T0 --long -15
|
compressoptions -T0 --long -15
|
||||||
uncompresscmd /usr/bin/unzstd
|
uncompresscmd /usr/bin/unzstd
|
||||||
sharedscripts
|
|
||||||
missingok
|
missingok
|
||||||
notifempty
|
notifempty
|
||||||
postrotate
|
|
||||||
/bin/pkill -HUP dwelling-upload
|
|
||||||
endscript
|
|
||||||
}
|
}
|
@ -1,22 +1,22 @@
|
|||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen 443 ssl http2;
|
||||||
# listen 8094; # Tor
|
listen 8094; # Tor I2P
|
||||||
listen 127.0.0.1:8114; # I2P
|
|
||||||
listen [300:a98d:d6d0:8a08::c]:80; # Yggdrasil
|
listen [300:a98d:d6d0:8a08::c]:80; # Yggdrasil
|
||||||
|
|
||||||
server_name upload.arav.su upload.arav.i2p;
|
server_name upload.arav.su upload.arav.i2p 4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion;
|
||||||
|
|
||||||
access_log /var/log/nginx/dwelling/upload.log main if=$nolog;
|
access_log /var/log/nginx/dwelling/upload.log main if=$nolog;
|
||||||
|
|
||||||
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.pem;
|
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.pem;
|
||||||
ssl_certificate_key /etc/letsencrypt/live/arav.su/privkey.pem;
|
ssl_certificate_key /etc/letsencrypt/live/arav.su/privkey.pem;
|
||||||
|
|
||||||
|
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; font-src 'self'; form-action 'self'";
|
add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; font-src 'self'; form-action 'self'";
|
||||||
add_header X-Frame-Options "DENY";
|
add_header X-Frame-Options "DENY";
|
||||||
add_header X-Content-Type-Options "nosniff";
|
add_header X-Content-Type-Options "nosniff";
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
|
||||||
# add_header Onion-Location "http://.onion$request_uri";
|
add_header Onion-Location "http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion$request_uri";
|
||||||
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
8
go.mod
8
go.mod
@ -2,8 +2,6 @@ module dwelling-upload
|
|||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require github.com/pkg/errors v0.9.1
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
|
||||||
github.com/pkg/errors v0.9.1
|
require git.arav.su/Arav/httpr v0.3.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
|
||||||
)
|
|
||||||
|
8
go.sum
8
go.sum
@ -1,8 +1,4 @@
|
|||||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
git.arav.su/Arav/httpr v0.3.1 h1:8ba90SJ4XYUWfIlC3V0Zuw3+CcOb9IYVkOZ/2mB9JO0=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
git.arav.su/Arav/httpr v0.3.1/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
@ -3,9 +3,8 @@ Description=dwelling-upload-clean
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
User=dwupload
|
DynamicUser=yes
|
||||||
Group=dwupload
|
ExecStart=/usr/bin/dwelling-upload-clean -dir /srv/upload -expiry 36h
|
||||||
ExecStart=/usr/bin/dwelling-upload-clean -conf /etc/dwelling/upload.yaml
|
|
||||||
|
|
||||||
ReadOnlyPaths=/
|
ReadOnlyPaths=/
|
||||||
# Set here path to directory where uploads are stored.
|
# Set here path to directory where uploads are stored.
|
||||||
@ -13,8 +12,6 @@ ReadWritePaths=/srv/upload
|
|||||||
NoExecPaths=/
|
NoExecPaths=/
|
||||||
ExecPaths=/usr/bin/dwelling-upload-clean
|
ExecPaths=/usr/bin/dwelling-upload-clean
|
||||||
|
|
||||||
LogsDirectory=dwelling-upload
|
|
||||||
|
|
||||||
AmbientCapabilities=
|
AmbientCapabilities=
|
||||||
CapabilityBoundingSet=
|
CapabilityBoundingSet=
|
||||||
|
|
||||||
@ -22,18 +19,33 @@ LockPersonality=true
|
|||||||
MemoryDenyWriteExecute=true
|
MemoryDenyWriteExecute=true
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateDevices=true
|
PrivateDevices=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProcSubset=pid
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
RestrictAddressFamilies=
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -5,9 +5,9 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
User=dwupload
|
DynamicUser=yes
|
||||||
Group=dwupload
|
ExecStart=/usr/bin/dwelling-upload -listen /var/run/dwelling-upload/sock \
|
||||||
ExecStart=/usr/bin/dwelling-upload -conf /etc/dwelling/upload.yaml
|
-dir /srv/upload -expiry 36 -storage 102400 -file 128
|
||||||
|
|
||||||
ReadOnlyPaths=/
|
ReadOnlyPaths=/
|
||||||
# Set here path to directory where uploads are stored.
|
# Set here path to directory where uploads are stored.
|
||||||
@ -18,6 +18,9 @@ ExecPaths=/usr/bin/dwelling-upload
|
|||||||
RuntimeDirectory=dwelling-upload
|
RuntimeDirectory=dwelling-upload
|
||||||
LogsDirectory=dwelling-upload
|
LogsDirectory=dwelling-upload
|
||||||
|
|
||||||
|
# Use gen-salt.sh to generate salt! It will create / append to an override.conf.
|
||||||
|
SetCredentialEncrypted=
|
||||||
|
|
||||||
AmbientCapabilities=
|
AmbientCapabilities=
|
||||||
CapabilityBoundingSet=
|
CapabilityBoundingSet=
|
||||||
|
|
||||||
@ -25,18 +28,33 @@ LockPersonality=true
|
|||||||
MemoryDenyWriteExecute=true
|
MemoryDenyWriteExecute=true
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateDevices=true
|
PrivateDevices=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProcSubset=pid
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
ProtectKernelLogs=true
|
ProtectKernelLogs=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=noaccess
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@clock
|
||||||
|
SystemCallFilter=~@cpu-emulation
|
||||||
|
SystemCallFilter=~@debug
|
||||||
|
SystemCallFilter=~@module
|
||||||
|
SystemCallFilter=~@mount
|
||||||
|
SystemCallFilter=~@obsolete
|
||||||
|
SystemCallFilter=~@privileged
|
||||||
|
SystemCallFilter=~@raw-io
|
||||||
|
SystemCallFilter=~@reboot
|
||||||
|
SystemCallFilter=~@swap
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configuration holds a list of process names to be tracked and a listen address.
|
|
||||||
type Configuration struct {
|
|
||||||
ListenOn string `yaml:"listen_on"`
|
|
||||||
HashSalt string `yaml:"hash_salt"`
|
|
||||||
User string `yaml:"user"`
|
|
||||||
Log struct {
|
|
||||||
Error string `yaml:"error"`
|
|
||||||
Upload string `yaml:"upload"`
|
|
||||||
Download string `yaml:"download"`
|
|
||||||
Delete string `yaml:"delete"`
|
|
||||||
Clean string `yaml:"clean"`
|
|
||||||
CleanError string `yaml:"clean_error"`
|
|
||||||
} `yaml:"log"`
|
|
||||||
Uploads struct {
|
|
||||||
Directory string `yaml:"directory"`
|
|
||||||
Limits struct {
|
|
||||||
FileSize int64 `yaml:"file_size"`
|
|
||||||
KeepForHours int `yaml:"keep_for_hours"`
|
|
||||||
Storage int64 `yaml:"storage"`
|
|
||||||
} `yaml:"limits"`
|
|
||||||
} `yaml:"uploads"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadConfiguration(path string) (*Configuration, error) {
|
|
||||||
configFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to open configuration file")
|
|
||||||
}
|
|
||||||
defer configFile.Close()
|
|
||||||
|
|
||||||
config := &Configuration{}
|
|
||||||
|
|
||||||
if err := yaml.NewDecoder(configFile).Decode(config); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to parse configuration file")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitNetworkAddress splits ListenOn option and returns as two strings
|
|
||||||
// network type (e.g. tcp, unix, udp) and address:port or /path/to/prog.socket
|
|
||||||
// to listen on.
|
|
||||||
func (c *Configuration) SplitNetworkAddress() (string, string) {
|
|
||||||
s := strings.Split(c.ListenOn, " ")
|
|
||||||
return s[0], s[1]
|
|
||||||
}
|
|
@ -2,111 +2,109 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"dwelling-upload/internal/configuration"
|
|
||||||
"dwelling-upload/pkg/logging"
|
|
||||||
"dwelling-upload/pkg/utils"
|
"dwelling-upload/pkg/utils"
|
||||||
"dwelling-upload/web"
|
"dwelling-upload/web"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.arav.su/Arav/httpr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UploadHandlers struct {
|
type UploadHandlers struct {
|
||||||
conf *configuration.Configuration
|
logFile *log.Logger
|
||||||
logErr *logging.Logger
|
|
||||||
logUpload *logging.Logger
|
|
||||||
logDownload *logging.Logger
|
|
||||||
logDelete *logging.Logger
|
|
||||||
|
|
||||||
|
uploadDir string
|
||||||
uploadDirSize *int64
|
uploadDirSize *int64
|
||||||
|
|
||||||
|
hashSalt string
|
||||||
|
|
||||||
|
keepForHours int
|
||||||
|
limitStorage int64
|
||||||
|
limitFileSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUploadHandlers(conf *configuration.Configuration, lErr, lUp, lDown, lDel *logging.Logger, uploadDirSize *int64) *UploadHandlers {
|
func NewUploadHandlers(lFile *log.Logger, uploadDir string, uploadDirSize *int64,
|
||||||
|
hashSalt string, keepForHours int, limStorage, limFileSz int64) *UploadHandlers {
|
||||||
return &UploadHandlers{
|
return &UploadHandlers{
|
||||||
conf: conf,
|
logFile: lFile,
|
||||||
logErr: lErr,
|
uploadDir: uploadDir,
|
||||||
logUpload: lUp,
|
uploadDirSize: uploadDirSize,
|
||||||
logDownload: lDown,
|
hashSalt: hashSalt,
|
||||||
logDelete: lDel,
|
keepForHours: keepForHours,
|
||||||
uploadDirSize: uploadDirSize}
|
limitStorage: limStorage,
|
||||||
}
|
limitFileSize: limFileSz}
|
||||||
|
|
||||||
func (*UploadHandlers) AssetsFS() http.FileSystem {
|
|
||||||
return web.Assets()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UploadHandlers) Index(w http.ResponseWriter, r *http.Request) {
|
func (h *UploadHandlers) Index(w http.ResponseWriter, r *http.Request) {
|
||||||
var storCapacity int64 = h.conf.Uploads.Limits.Storage << 20
|
var storCapacity int64 = h.limitStorage << 20
|
||||||
var fMaxSize int64 = h.conf.Uploads.Limits.FileSize << 20
|
var fMaxSize int64 = h.limitFileSize << 20
|
||||||
|
|
||||||
_, _, capStr := utils.ConvertFileSize(storCapacity)
|
|
||||||
_, _, usedStr := utils.ConvertFileSize(*h.uploadDirSize)
|
|
||||||
_, _, availStr := utils.ConvertFileSize(storCapacity - *h.uploadDirSize)
|
_, _, availStr := utils.ConvertFileSize(storCapacity - *h.uploadDirSize)
|
||||||
_, _, fMaxSzStr := utils.ConvertFileSize(fMaxSize)
|
_, _, fMaxSzStr := utils.ConvertFileSize(fMaxSize)
|
||||||
|
|
||||||
web.Index(utils.MainSite(r.Host), storCapacity, *h.uploadDirSize, h.conf.Uploads.Limits.KeepForHours, fMaxSzStr, usedStr, capStr, availStr, w)
|
web.Index(utils.MainSite(r.Host), h.keepForHours, fMaxSzStr, availStr, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||||
var fMaxSizeBytes int64 = h.conf.Uploads.Limits.FileSize << 20
|
var fMaxSizeBytes int64 = h.limitFileSize << 20
|
||||||
var storCapacity int64 = h.conf.Uploads.Limits.Storage << 20
|
var storCapacity int64 = h.limitStorage << 20
|
||||||
|
|
||||||
r.Body = http.MaxBytesReader(w, r.Body, fMaxSizeBytes)
|
r.Body = http.MaxBytesReader(w, r.Body, fMaxSizeBytes)
|
||||||
|
|
||||||
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
|
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
|
||||||
h.logErr.Println("failed to parse form:", err)
|
log.Println("failed to parse upload form:", err)
|
||||||
http.Error(w, err.Error(), http.StatusExpectationFailed)
|
Error(w, r, "Failed to parse upload form.", http.StatusExpectationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, fHandler, err := r.FormFile("file")
|
f, fHandler, err := r.FormFile("file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logErr.Println("failed to open incoming file:", err)
|
log.Println("failed to open incoming file:", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
Error(w, r, "Error reading an incoming file.", http.StatusInternalServerError)
|
||||||
http.Error(w, "cannot read incoming file", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer os.Remove(fHandler.Filename)
|
||||||
os.Remove(fHandler.Filename)
|
defer f.Close()
|
||||||
f.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
var leftSpace int64 = storCapacity - *h.uploadDirSize
|
var leftSpace int64 = storCapacity - *h.uploadDirSize
|
||||||
|
|
||||||
if leftSpace < fHandler.Size {
|
if leftSpace < fHandler.Size {
|
||||||
h.logErr.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
|
log.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
Error(w, r, "Not enough space left, sorry.", http.StatusInternalServerError)
|
||||||
web.ErrorNoSpace(utils.MainSite(r.Host), w)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s256 := sha256.New()
|
s256 := sha256.New()
|
||||||
if _, err := io.Copy(s256, f); err != nil {
|
if _, err := io.Copy(s256, f); err != nil {
|
||||||
h.logErr.Println("failed to compute SHA-256 hash:", err)
|
log.Println("failed to compute a SHA-256 hash:", err)
|
||||||
http.Error(w, "cannot compute hash for a file", http.StatusInternalServerError)
|
Error(w, r, "A hash for the file cannot be computed.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fHash := hex.EncodeToString(s256.Sum(nil))
|
fHash := hex.EncodeToString(s256.Sum(nil))
|
||||||
s256.Write([]byte(h.conf.HashSalt))
|
s256.Write([]byte(h.hashSalt))
|
||||||
|
s256.Write([]byte(time.Now().String()))
|
||||||
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
|
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
|
||||||
|
|
||||||
f.Seek(0, io.SeekStart)
|
f.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
fPath := path.Join(h.conf.Uploads.Directory, fSaltedHash)
|
fPath := path.Join(h.uploadDir, fSaltedHash)
|
||||||
|
|
||||||
_, err = os.Stat(fPath)
|
_, err = os.Stat(fPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
fDst, err := os.Create(fPath)
|
fDst, err := os.Create(fPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logErr.Println("failed to open file for writing", err)
|
log.Println("failed to open file for writing", err)
|
||||||
http.Error(w, "cannot create your file", http.StatusInternalServerError)
|
Error(w, r, "File cannot be written.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fDst.Close()
|
defer fDst.Close()
|
||||||
@ -119,17 +117,20 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
fDst.Write([]byte{0})
|
fDst.Write([]byte{0})
|
||||||
fDst.Seek(0, io.SeekStart)
|
fDst.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
_, err = io.Copy(fDst, f)
|
if _, err = io.Copy(fDst, f); err != nil {
|
||||||
if err != nil {
|
log.Println("failed to copy uploaded file to destination:", err)
|
||||||
h.logErr.Println("failed to copy uploaded file to destination:", err)
|
Error(w, r, "Failed to copy uploaded file to the storage.", http.StatusInternalServerError)
|
||||||
http.Error(w, "cannot copy file's content", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
typ, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logUpload.Printf("| %s | %s | %s | SHA256 %s | %s | %d | %s", r.Header.Get("X-Real-IP"), typ,
|
h.logFile.Printf("| up | %s | %s | %s | SHA256 %s | %s | %d | %s",
|
||||||
fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
|
ip, typ, fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
} else {
|
} else {
|
||||||
@ -137,46 +138,50 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadURL := path.Join("/f", fSaltedHash, fHandler.Filename)
|
downloadURL := path.Join("/", fSaltedHash, fHandler.Filename)
|
||||||
downloadURLParsed, _ := url.Parse(downloadURL)
|
downloadURLParsed, _ := url.Parse(downloadURL)
|
||||||
|
|
||||||
_, scheme := utils.NetworkType(r.Host)
|
_, scheme := utils.NetworkType(r.Host)
|
||||||
site := scheme + "://" + r.Host
|
site := scheme + "://" + r.Host
|
||||||
|
|
||||||
if strings.Contains(r.UserAgent(), "curl") {
|
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
||||||
w.Write([]byte(site + downloadURLParsed.String() + "\r\n"))
|
fmt.Fprintln(w, site+downloadURLParsed.String(), "will be kept for", h.keepForHours)
|
||||||
return
|
} else {
|
||||||
|
web.Uploaded(utils.MainSite(r.Host), site, downloadURLParsed.String(), h.keepForHours, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
web.Uploaded(utils.MainSite(r.Host), site, downloadURLParsed.String(), h.conf.Uploads.Limits.KeepForHours, w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
||||||
saltedHash := GetURLParam(r, "hash")
|
saltedHash := httpr.Param(r, "hash")
|
||||||
|
|
||||||
path := path.Join(h.conf.Uploads.Directory, saltedHash)
|
path := path.Join(h.uploadDir, saltedHash)
|
||||||
|
|
||||||
stat, err := os.Stat(path)
|
stat, err := os.Stat(path)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
NotFound(w, r)
|
Error(w, r, "", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := GetURLParam(r, "name")
|
name := httpr.Param(r, "name")
|
||||||
|
|
||||||
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", name))
|
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", name))
|
||||||
|
|
||||||
fd, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logErr.Println("failed to open file to read:", err)
|
log.Println("failed to open file to read:", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
Error(w, r, "Failed to open file to read.", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
netTyp, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logDownload.Printf("| %s | %s | %s | %s | %s", r.Header.Get("X-Real-IP"), netTyp, name, saltedHash, r.UserAgent())
|
h.logFile.Printf("| dw | %s | %s | %s | %s | %s",
|
||||||
|
ip, typ, name, saltedHash, r.UserAgent())
|
||||||
|
|
||||||
http.ServeContent(w, r, path, stat.ModTime(), fd)
|
http.ServeContent(w, r, path, stat.ModTime(), fd)
|
||||||
}
|
}
|
||||||
@ -184,44 +189,57 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
||||||
var saltedHash string
|
var saltedHash string
|
||||||
if r.Method == "DELETE" {
|
if r.Method == "DELETE" {
|
||||||
saltedHash = GetURLParam(r, "hash")
|
saltedHash = httpr.Param(r, "hash")
|
||||||
} else {
|
} else {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
saltedHash = r.FormValue("hash")
|
saltedHash = r.FormValue("hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
path := path.Join(h.conf.Uploads.Directory, saltedHash)
|
path := path.Join(h.uploadDir, saltedHash)
|
||||||
|
|
||||||
_, err := os.Stat(path)
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
if os.IsNotExist(err) {
|
Error(w, r, "", http.StatusNotFound)
|
||||||
NotFound(w, r)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(path)
|
if err := os.Remove(path); err != nil {
|
||||||
if err != nil {
|
log.Println("failed to remove a file:", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
Error(w, r, "Failed to remove a file.", http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
netTyp, _ := utils.NetworkType(r.Host)
|
typ, _ := utils.NetworkType(r.Host)
|
||||||
|
ip := r.Header.Get("X-Real-IP")
|
||||||
|
if typ != "www" && typ != "ygg" {
|
||||||
|
ip = ""
|
||||||
|
}
|
||||||
|
|
||||||
h.logDelete.Printf("| %s | %s | %s | %s", r.Header.Get("X-Real-IP"), netTyp, saltedHash, r.UserAgent())
|
h.logFile.Printf("| dt | %s | %s | %s | %s",
|
||||||
|
ip, typ, saltedHash, r.UserAgent())
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
||||||
|
fmt.Fprintln(w, "File was successfully deleted.")
|
||||||
|
} else {
|
||||||
|
web.Deleted(utils.MainSite(r.Host), w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotFound(w http.ResponseWriter, r *http.Request) {
|
func Error(w http.ResponseWriter, r *http.Request, reason string, code int) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
|
||||||
if !strings.Contains(r.UserAgent(), "curl") {
|
http.Error(w, reason, code)
|
||||||
web.Error404(utils.MainSite(r.Host), w)
|
return
|
||||||
} else {
|
|
||||||
fmt.Fprintln(w, "file not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(code)
|
||||||
|
web.ErrorXXX(utils.MainSite(r.Host), code, reason, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RobotsTxt(w http.ResponseWriter, r *http.Request) {
|
func RobotsTxt(w http.ResponseWriter, r *http.Request) {
|
||||||
data, _ := web.AssetsGetFile("robots.txt")
|
data, _ := web.AssetsGetFile("robots.txt")
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Favicon(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, _ := web.AssetsGetFile("img/favicon.svg")
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
@ -5,55 +5,41 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpServer struct {
|
type HttpServer struct {
|
||||||
server *http.Server
|
s http.Server
|
||||||
router *httprouter.Router
|
addr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpServer() *HttpServer {
|
func NewHttpServer(r http.Handler) *HttpServer {
|
||||||
r := httprouter.New()
|
return &HttpServer{s: http.Server{
|
||||||
return &HttpServer{
|
ReadTimeout: 3 * time.Second,
|
||||||
server: &http.Server{
|
WriteTimeout: 3 * time.Second,
|
||||||
ReadTimeout: 3 * time.Second,
|
Handler: r}}
|
||||||
WriteTimeout: 3 * time.Second,
|
}
|
||||||
Handler: r,
|
|
||||||
},
|
func (s *HttpServer) Start(address string) error {
|
||||||
router: r,
|
var network string
|
||||||
|
if !strings.ContainsRune(address, ':') {
|
||||||
|
network = "unix"
|
||||||
|
} else {
|
||||||
|
ap, err := netip.ParseAddrPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ap.Addr().Is4() {
|
||||||
|
network = "tcp4"
|
||||||
|
} else if ap.Addr().Is6() {
|
||||||
|
network = "tcp6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) GET(path string, handler http.HandlerFunc) {
|
|
||||||
s.router.Handler(http.MethodGet, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) POST(path string, handler http.HandlerFunc) {
|
|
||||||
s.router.Handler(http.MethodPost, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) DELETE(path string, handler http.HandlerFunc) {
|
|
||||||
s.router.Handler(http.MethodDelete, path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) ServeStatic(path string, fsys http.FileSystem) {
|
|
||||||
s.router.ServeFiles(path, fsys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) SetNotFoundHandler(handler http.HandlerFunc) {
|
|
||||||
s.router.NotFound = handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetURLParam wrapper around underlying router for getting URL parameters.
|
|
||||||
func GetURLParam(r *http.Request, param string) string {
|
|
||||||
return httprouter.ParamsFromContext(r.Context()).ByName(param)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *HttpServer) Start(network, address string) error {
|
|
||||||
listener, err := net.Listen(network, address)
|
listener, err := net.Listen(network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -63,8 +49,10 @@ func (s *HttpServer) Start(network, address string) error {
|
|||||||
os.Chmod(address, 0777)
|
os.Chmod(address, 0777)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.addr = listener.Addr()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -76,7 +64,11 @@ func (s *HttpServer) Stop() error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := s.server.Shutdown(ctx); err != nil {
|
if s.addr.Network() == "unix" {
|
||||||
|
defer os.Remove(s.addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.s.Shutdown(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
package logging
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
sync.Mutex
|
|
||||||
file io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a Logger instance with a given filename
|
|
||||||
func New(path string) (*Logger, error) {
|
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to open log file")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Logger{file: f}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Reopen(path string) error {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.file.Close()
|
|
||||||
l.file = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Println(v ...interface{}) {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
nowStr := time.Now().UTC().Format(time.RFC3339)
|
|
||||||
|
|
||||||
fmt.Fprintln(l.file, nowStr, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
// Ensure a new line will be written
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
nowStr := time.Now().UTC().Format(time.RFC3339)
|
|
||||||
|
|
||||||
fmt.Fprintf(l.file, nowStr+" "+format, v...)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Fatalln(v ...interface{}) {
|
|
||||||
l.Lock()
|
|
||||||
|
|
||||||
nowStr := time.Now().UTC().Format(time.RFC3339)
|
|
||||||
|
|
||||||
fmt.Fprintln(l.file, nowStr, v)
|
|
||||||
|
|
||||||
l.file.Close()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
|
||||||
l.Lock()
|
|
||||||
|
|
||||||
// Ensure a new line will be written
|
|
||||||
if !strings.HasSuffix(format, "\n") {
|
|
||||||
format += "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
nowStr := time.Now().UTC().Format(time.RFC3339)
|
|
||||||
|
|
||||||
fmt.Fprintf(l.file, nowStr+" "+format, v...)
|
|
||||||
|
|
||||||
l.file.Close()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Logger) Close() error {
|
|
||||||
return l.file.Close()
|
|
||||||
}
|
|
@ -25,6 +25,8 @@ func NetworkType(host string) (string, string) {
|
|||||||
return "i2p", "http"
|
return "i2p", "http"
|
||||||
} else if strings.Contains(host, "onion") {
|
} else if strings.Contains(host, "onion") {
|
||||||
return "tor", "http"
|
return "tor", "http"
|
||||||
|
} else if strings.Contains(host, "[300:") {
|
||||||
|
return "ygg", "http"
|
||||||
} else {
|
} else {
|
||||||
return "www", "https"
|
return "www", "https"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"os"
|
||||||
"path/filepath"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -14,10 +14,10 @@ var sizeSuffixes = [...]string{"B", "KiB", "MiB", "GiB", "TiB"}
|
|||||||
// ConvertFileSize converts size in bytes down to biggest units it represents.
|
// ConvertFileSize converts size in bytes down to biggest units it represents.
|
||||||
// Returns converted size, unit and, a concatenation of size and unit
|
// Returns converted size, unit and, a concatenation of size and unit
|
||||||
func ConvertFileSize(size int64) (float64, string, string) {
|
func ConvertFileSize(size int64) (float64, string, string) {
|
||||||
var idx int
|
idx := 0
|
||||||
var fSize float64 = float64(size)
|
fSize := float64(size)
|
||||||
|
|
||||||
for idx = 0; fSize >= 1024; fSize /= 1024 {
|
for ; fSize >= 1024; fSize /= 1024 {
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,23 +25,23 @@ func ConvertFileSize(size int64) (float64, string, string) {
|
|||||||
fSizeStr = strings.TrimRight(fSizeStr, "0")
|
fSizeStr = strings.TrimRight(fSizeStr, "0")
|
||||||
fSizeStr = strings.TrimSuffix(fSizeStr, ".")
|
fSizeStr = strings.TrimSuffix(fSizeStr, ".")
|
||||||
|
|
||||||
return fSize, sizeSuffixes[idx], strings.Join([]string{fSizeStr, sizeSuffixes[idx]}, " ")
|
return fSize, sizeSuffixes[idx],
|
||||||
|
strings.Join([]string{fSizeStr, sizeSuffixes[idx]}, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func DirectorySize(path string) (dirSz int64, err error) {
|
func DirectorySize(dirPath string) (dirSz int64, err error) {
|
||||||
err = filepath.Walk(path, func(_ string, info fs.FileInfo, err error) error {
|
dir, err := os.ReadDir(dirPath)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to compute %s directory size", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
dirSz += info.Size()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, errors.Wrapf(err, "failed to compute %s directory size", dirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dirSz - 4096, nil
|
for _, entry := range dir {
|
||||||
|
file, err := os.Stat(path.Join(dirPath, entry.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "failed to stat a file %s", entry.Name())
|
||||||
|
}
|
||||||
|
dirSz += file.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirSz, nil
|
||||||
}
|
}
|
||||||
|
@ -54,26 +54,24 @@ func (w *InotifyWatcher) AddWatch(path string, mask uint32) error {
|
|||||||
// WatchForMask checks for events specified in `mask` and sends them
|
// WatchForMask checks for events specified in `mask` and sends them
|
||||||
// to channel `fired`. See `man inotify` for events.
|
// to channel `fired`. See `man inotify` for events.
|
||||||
func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) {
|
func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) {
|
||||||
go func() {
|
for !w.closed {
|
||||||
for !w.closed {
|
buffer := make([]byte, syscall.SizeofInotifyEvent*inotifyCount)
|
||||||
buffer := make([]byte, syscall.SizeofInotifyEvent*inotifyCount)
|
n, err := syscall.Read(w.fd, buffer)
|
||||||
n, err := syscall.Read(w.fd, buffer)
|
if err != nil {
|
||||||
if err != nil {
|
break
|
||||||
break
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if n < syscall.SizeofInotifyEvent {
|
if n < syscall.SizeofInotifyEvent {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for offset := 0; offset < len(buffer); offset += syscall.SizeofInotifyEvent {
|
for offset := 0; offset < len(buffer); offset += syscall.SizeofInotifyEvent {
|
||||||
event := (*syscall.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
|
event := (*syscall.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
|
||||||
if event.Mask&mask > 0 {
|
if event.Mask&mask > 0 {
|
||||||
fired <- event.Mask
|
fired <- event.Mask
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close removes all watchers, closes inotify descriptor and stops all
|
// Close removes all watchers, closes inotify descriptor and stops all
|
||||||
|
26
tools/gen-salt.sh
Executable file
26
tools/gen-salt.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/sh
|
||||||
|
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
echo "Must run as root!" >&2;
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# It will create a new encription key if it doesn't exists.
|
||||||
|
systemd-creds setup
|
||||||
|
|
||||||
|
if [[ $1 ]]; then
|
||||||
|
char_num=$1;
|
||||||
|
else
|
||||||
|
char_num=64;
|
||||||
|
fi
|
||||||
|
|
||||||
|
service_override=$(pkg-config systemd --variable=systemdsystemconfdir)/dwelling-upload.service.d/override.conf
|
||||||
|
|
||||||
|
if [ ! -f $service_override ]; then
|
||||||
|
echo "[Service]" > $service_override;
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat /dev/urandom | tr -dc 'a-zA-Z0-9!@#$%^&*' | fold -w $char_num | head -n 1 \
|
||||||
|
| systemd-creds encrypt -qp --name=salt - - 2> /dev/null >> $service_override;
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
@ -33,6 +33,8 @@
|
|||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
color: var(--background-color); }
|
color: var(--background-color); }
|
||||||
|
|
||||||
|
.center { text-align: center; }
|
||||||
|
|
||||||
a,
|
a,
|
||||||
button {
|
button {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@ -50,8 +52,8 @@ input[type="file"] {
|
|||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font: inherit; }
|
font: inherit; }
|
||||||
|
|
||||||
input[type="file"]::file-selector-button,
|
button,
|
||||||
button {
|
input[type="file"]::file-selector-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
@ -91,17 +93,6 @@ h2 {
|
|||||||
|
|
||||||
small { font-size: .8rem; }
|
small { font-size: .8rem; }
|
||||||
|
|
||||||
progress {
|
|
||||||
background-color: var(--secondary-color);
|
|
||||||
border: none;
|
|
||||||
color: var(--primary-color);
|
|
||||||
height: 1.1rem;
|
|
||||||
width: 30%; }
|
|
||||||
|
|
||||||
progress::-moz-progress-bar { background-color: var(--primary-color); }
|
|
||||||
|
|
||||||
.center { text-align: center; }
|
|
||||||
|
|
||||||
html { margin-left: calc(100vw - 100%); }
|
html { margin-left: calc(100vw - 100%); }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -118,24 +109,22 @@ header {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between; }
|
justify-content: space-between; }
|
||||||
|
|
||||||
#logo {
|
header svg { width: 360px; }
|
||||||
display: block;
|
|
||||||
width: 360px; }
|
|
||||||
|
|
||||||
#logo text { fill: var(--text-color); }
|
header svg text { fill: var(--text-color); }
|
||||||
|
|
||||||
#logo .logo {
|
header svg text:first-child {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-variant-caps: small-caps;
|
font-variant-caps: small-caps;
|
||||||
font-weight: bold; }
|
font-weight: bold; }
|
||||||
|
|
||||||
|
header svg text:last-child { font-size: .88rem; }
|
||||||
|
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
#logo .logo { font-size: 2.082rem; } }
|
header svg text:first-child { font-size: 2.082rem; } }
|
||||||
|
|
||||||
@-moz-document url-prefix() {
|
@-moz-document url-prefix() {
|
||||||
#logo .logo { font-size: 2rem; } }
|
header svg text:first-child { font-size: 2rem; } }
|
||||||
|
|
||||||
#logo .under { font-size: .88rem; }
|
|
||||||
|
|
||||||
nav { margin-top: .5rem; }
|
nav { margin-top: .5rem; }
|
||||||
|
|
||||||
@ -147,7 +136,13 @@ nav h1 {
|
|||||||
|
|
||||||
section { margin-top: 1rem; }
|
section { margin-top: 1rem; }
|
||||||
|
|
||||||
#used-space div span { margin: 0 .2rem; }
|
#error {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
line-height: 5rem;
|
||||||
|
text-align: center;
|
||||||
|
margin: 6rem 0; }
|
||||||
|
|
||||||
|
#error h1 { font-size: 8rem; }
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
@ -157,7 +152,7 @@ footer {
|
|||||||
@media screen and (max-width: 640px) {
|
@media screen and (max-width: 640px) {
|
||||||
header { display: block; }
|
header { display: block; }
|
||||||
|
|
||||||
#logo {
|
header svg {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 100%; }
|
width: 100%; }
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /assets/
|
Allow: /$
|
||||||
|
Disallow: /
|
141
web/jade.go
141
web/jade.go
@ -1,141 +0,0 @@
|
|||||||
// Code generated by "jade.go"; DO NOT EDIT.
|
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
escaped = []byte{'<', '>', '"', '\'', '&'}
|
|
||||||
replacing = []string{"<", ">", """, "'", "&"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func WriteEscString(st string, buffer *WriterAsBuffer) {
|
|
||||||
for i := 0; i < len(st); i++ {
|
|
||||||
if n := bytes.IndexByte(escaped, st[i]); n >= 0 {
|
|
||||||
buffer.WriteString(replacing[n])
|
|
||||||
} else {
|
|
||||||
buffer.WriteByte(st[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type WriterAsBuffer struct {
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WriterAsBuffer) WriteString(s string) (n int, err error) {
|
|
||||||
n, err = w.Write([]byte(s))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WriterAsBuffer) WriteByte(b byte) (err error) {
|
|
||||||
_, err = w.Write([]byte{b})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringer interface {
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
func WriteAll(a interface{}, escape bool, buffer *WriterAsBuffer) {
|
|
||||||
switch v := a.(type) {
|
|
||||||
case string:
|
|
||||||
if escape {
|
|
||||||
WriteEscString(v, buffer)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(v)
|
|
||||||
}
|
|
||||||
case int:
|
|
||||||
WriteInt(int64(v), buffer)
|
|
||||||
case int8:
|
|
||||||
WriteInt(int64(v), buffer)
|
|
||||||
case int16:
|
|
||||||
WriteInt(int64(v), buffer)
|
|
||||||
case int32:
|
|
||||||
WriteInt(int64(v), buffer)
|
|
||||||
case int64:
|
|
||||||
WriteInt(v, buffer)
|
|
||||||
case uint:
|
|
||||||
WriteUint(uint64(v), buffer)
|
|
||||||
case uint8:
|
|
||||||
WriteUint(uint64(v), buffer)
|
|
||||||
case uint16:
|
|
||||||
WriteUint(uint64(v), buffer)
|
|
||||||
case uint32:
|
|
||||||
WriteUint(uint64(v), buffer)
|
|
||||||
case uint64:
|
|
||||||
WriteUint(v, buffer)
|
|
||||||
case float32:
|
|
||||||
buffer.WriteString(strconv.FormatFloat(float64(v), 'f', -1, 64))
|
|
||||||
case float64:
|
|
||||||
buffer.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
|
||||||
case bool:
|
|
||||||
WriteBool(v, buffer)
|
|
||||||
case stringer:
|
|
||||||
if escape {
|
|
||||||
WriteEscString(v.String(), buffer)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(v.String())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
buffer.WriteString("\n<<< unprinted type, fmt.Stringer implementation needed >>>\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ternary(condition bool, iftrue, iffalse interface{}) interface{} {
|
|
||||||
if condition {
|
|
||||||
return iftrue
|
|
||||||
} else {
|
|
||||||
return iffalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used part of go source:
|
|
||||||
// https://github.com/golang/go/blob/master/src/strconv/itoa.go
|
|
||||||
func WriteUint(u uint64, buffer *WriterAsBuffer) {
|
|
||||||
var a [64 + 1]byte
|
|
||||||
i := len(a)
|
|
||||||
|
|
||||||
if ^uintptr(0)>>32 == 0 {
|
|
||||||
for u > uint64(^uintptr(0)) {
|
|
||||||
q := u / 1e9
|
|
||||||
us := uintptr(u - q*1e9)
|
|
||||||
for j := 9; j > 0; j-- {
|
|
||||||
i--
|
|
||||||
qs := us / 10
|
|
||||||
a[i] = byte(us - qs*10 + '0')
|
|
||||||
us = qs
|
|
||||||
}
|
|
||||||
u = q
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
us := uintptr(u)
|
|
||||||
for us >= 10 {
|
|
||||||
i--
|
|
||||||
q := us / 10
|
|
||||||
a[i] = byte(us - q*10 + '0')
|
|
||||||
us = q
|
|
||||||
}
|
|
||||||
|
|
||||||
i--
|
|
||||||
a[i] = byte(us + '0')
|
|
||||||
buffer.Write(a[i:])
|
|
||||||
}
|
|
||||||
func WriteInt(i int64, buffer *WriterAsBuffer) {
|
|
||||||
if i < 0 {
|
|
||||||
buffer.WriteByte('-')
|
|
||||||
i = -i
|
|
||||||
}
|
|
||||||
WriteUint(uint64(i), buffer)
|
|
||||||
}
|
|
||||||
func WriteBool(b bool, buffer *WriterAsBuffer) {
|
|
||||||
if b {
|
|
||||||
buffer.WriteString("true")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buffer.WriteString("false")
|
|
||||||
}
|
|
@ -12,12 +12,12 @@ html(lang="en")
|
|||||||
block head
|
block head
|
||||||
body
|
body
|
||||||
header
|
header
|
||||||
svg#logo(viewBox="0 -25 216 40")
|
svg(viewBox="0 -25 216 40")
|
||||||
text.logo Arav's dwelling
|
text Arav's dwelling
|
||||||
text.under(y="11") Welcome to my sacred place, wanderer
|
text(y="11") Welcome to my sacred place, wanderer
|
||||||
nav
|
nav
|
||||||
a(href=mainSite title="Arav's dwelling") Back to main website
|
a(href=mainSite title="Arav's dwelling") Back to main website
|
||||||
block header
|
block header
|
||||||
block body
|
block body
|
||||||
footer
|
footer
|
||||||
| 2022,2023 Alexander "Arav" Andreev <#[a(href="mailto:me@arav.su") me@arav.su]>
|
| 2022,2023 Alexander "Arav" Andreev <#[a(href="mailto:me@arav.su") me@arav.su]> #[a(href=mainSite+'/privacy') Privacy statements]
|
11
web/templates/deleted.jade
Normal file
11
web/templates/deleted.jade
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
extends base.jade
|
||||||
|
|
||||||
|
block header
|
||||||
|
h1 Deleted
|
||||||
|
|
||||||
|
block body
|
||||||
|
:go:func Deleted(mainSite string)
|
||||||
|
section
|
||||||
|
h2 File was successfully deleted.
|
||||||
|
center
|
||||||
|
a(href="/") Back to index page
|
@ -1,20 +0,0 @@
|
|||||||
extends base.jade
|
|
||||||
|
|
||||||
block head
|
|
||||||
:go:func Error404(mainSite string)
|
|
||||||
style(type="text/css").
|
|
||||||
#error {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
line-height: 5rem;
|
|
||||||
text-align: center;
|
|
||||||
margin: 6rem 0; }
|
|
||||||
|
|
||||||
#error h1 { font-size: 8rem; }
|
|
||||||
|
|
||||||
block header
|
|
||||||
h1 404
|
|
||||||
|
|
||||||
block body
|
|
||||||
section#error
|
|
||||||
h1 404
|
|
||||||
| Not Found
|
|
14
web/templates/errorXXX.jade
Normal file
14
web/templates/errorXXX.jade
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
extends base.jade
|
||||||
|
|
||||||
|
block header
|
||||||
|
h1 Еггог
|
||||||
|
|
||||||
|
block body
|
||||||
|
:go:func ErrorXXX(mainSite string, code int, errorMsg string)
|
||||||
|
section#error
|
||||||
|
h1 #{code}
|
||||||
|
| #{http.StatusText(code)}
|
||||||
|
if errorMsg != ""
|
||||||
|
p #{errorMsg}
|
||||||
|
center
|
||||||
|
a(href="/") Back to index page
|
@ -4,37 +4,25 @@ block header
|
|||||||
h1 Upload
|
h1 Upload
|
||||||
|
|
||||||
block body
|
block body
|
||||||
:go:func Index(mainSite string, storageCapacity, storageUsed int64, keepForHours int, fileMaxSize, storageUsedStr, storageCapacityStr, storageAvailableStr string)
|
:go:func Index(mainSite string, keepForHours int, fileMaxSize, storageAvailableStr string)
|
||||||
section#rules.center
|
section
|
||||||
h2 Rules
|
h2 Rules
|
||||||
p Maximum file size is #{fileMaxSize} and it will be kept for #{keepForHours} hours.
|
p Maximum file size is #[b #{fileMaxSize}] and it will be kept for #[b #{keepForHours}] hours.
|
||||||
p Content you upload should comply with with Russian Federation's law. Generally speaking, anything illegal, like CP, extremist literature, and so on is forbidden.
|
p Content you upload should comply with Russian Federation's law. Generally speaking, anything illegal, like CP, extremist literature, and so on is forbidden.
|
||||||
section#used-space.center
|
section.center
|
||||||
h2 Free space
|
|
||||||
div
|
|
||||||
span #{storageUsedStr}
|
|
||||||
progress(value=storageUsed max=storageCapacity)
|
|
||||||
span #{storageCapacityStr}
|
|
||||||
div
|
|
||||||
| #{storageAvailableStr}
|
|
||||||
section#upload.center
|
|
||||||
h2 Upload
|
h2 Upload
|
||||||
form(action="/" method="POST" enctype="multipart/form-data")
|
form(action="/" method="POST" enctype="multipart/form-data")
|
||||||
input(type="file" name="file" multiple=false)
|
input(type="file" name="file" multiple=false)
|
||||||
button(type="submit") Upload
|
button(type="submit") Upload
|
||||||
section.center
|
p.center #[b #{storageAvailableStr}] left.
|
||||||
p You can use cURL to upload a file: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
|
section
|
||||||
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -F 'file=@somefile.ext' http://upload.arav.i2p]
|
p Using cURL: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
|
||||||
p A resulted link looks like this: #[code /f/base64rawURL(salted SHA-256)/filename.ext].
|
p Also works under other networks (I2P, Tor, Yggdrasil). For Tor and I2P you'll need to add a #[code --proxy] option for cURL. Same for deletion.
|
||||||
|
p A resulted link has the following structure: #[code <site>/<hash>/<file>.<ext>].
|
||||||
section.center
|
section.center
|
||||||
h2 Delete
|
h2 Delete
|
||||||
form(action="/delete" method="POST")
|
form(action="/delete" method="POST")
|
||||||
input(type="text", name="hash" placeholder="File hash goes here" minlength="43" maxlength="43" size="43" required="")
|
input(type="text", name="hash" placeholder="File hash goes here" minlength="43" maxlength="43" size="43" required="")
|
||||||
button(type="submit") Delete
|
button(type="submit") Delete
|
||||||
section.center
|
|
||||||
p You can delete a file using cURL: #[code curl -XDELETE https://upload.arav.su/<hash>]
|
|
||||||
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -XDELETE http://upload.arav.i2p/<hash>]
|
|
||||||
section
|
section
|
||||||
h2 Privacy statements
|
p Using cURL: #[code curl -XDELETE https://upload.arav.su/<hash>]
|
||||||
p Any abuses should be sent to #[a(href="mailto:admin@arav.su") admin@arav.su]. I WILL cooperate with law enforcements and provide them with logs.
|
|
||||||
p Logs include: access time, IP-address, file name it was uploaded/downloaded with, a SHA-256 hash of it, size of it, and User-Agent.
|
|
@ -1,19 +0,0 @@
|
|||||||
extends base.jade
|
|
||||||
|
|
||||||
block head
|
|
||||||
:go:func ErrorNoSpace(mainSite string)
|
|
||||||
style(type="text/css").
|
|
||||||
#error {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
line-height: 5rem;
|
|
||||||
text-align: center;
|
|
||||||
margin: 6rem 0; }
|
|
||||||
|
|
||||||
#error h1 { font-size: 6rem; }
|
|
||||||
|
|
||||||
block header
|
|
||||||
h1 :(
|
|
||||||
|
|
||||||
block body
|
|
||||||
section#error
|
|
||||||
h1 Not enough space left
|
|
@ -5,7 +5,7 @@ block header
|
|||||||
|
|
||||||
block body
|
block body
|
||||||
:go:func Uploaded(mainSite, site, downloadLink string, keepForHours int)
|
:go:func Uploaded(mainSite, site, downloadLink string, keepForHours int)
|
||||||
section#file
|
section
|
||||||
h2 Your link
|
h2 Your link
|
||||||
center
|
center
|
||||||
a(href=downloadLink) #{site}#{downloadLink}
|
a(href=downloadLink) #{site}#{downloadLink}
|
||||||
|
@ -6,10 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/index.jade
|
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/index.jade
|
||||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/nospace.jade
|
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/deleted.jade
|
||||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/uploaded.jade
|
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/uploaded.jade
|
||||||
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/error404.jade
|
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/errorXXX.jade
|
||||||
|
|
||||||
//go:embed assets
|
//go:embed assets
|
||||||
var assetsDir embed.FS
|
var assetsDir embed.FS
|
||||||
|
Loading…
Reference in New Issue
Block a user