1
0
Fork 0

Compare commits

...

73 Commits

Author SHA1 Message Date
Alexander Andreev 7218fbc27f
Version set to 23.34.0. 2023-08-23 04:04:22 +04:00
Alexander Andreev 5148202a0f
A small reorganisation in main.css. 2023-08-23 03:48:52 +04:00
Alexander Andreev 1edba52d32
Disallow all pages but the index. 2023-08-23 03:43:35 +04:00
Alexander Andreev 9c42144b45
Updated index.jade. 2023-08-22 18:46:02 +04:00
Alexander Andreev 58197af85d
Added new options to .service files. Hope it won't break a program. xD 2023-08-22 18:24:02 +04:00
Alexander Andreev 4e098ec665
style-src unsafe-inline was removed because it doesn't needed anymore. 2023-08-22 18:21:25 +04:00
Alexander Andreev 5567de7de1
Error styling was moved out to main.css. 2023-08-22 18:20:13 +04:00
Alexander Andreev 63e03f6459
Removed unused ids. 2023-08-22 18:19:47 +04:00
Alexander Andreev 90837f1299
Changed logo. Removed id and classes. 2023-08-22 18:16:26 +04:00
Alexander Andreev b50a80a33d
Updated httpr package dependency to 0.3.1. 2023-08-13 04:19:14 +04:00
Alexander Andreev 92212c97eb
Add time for better randomisation for a hash. 2023-08-06 07:33:34 +04:00
Alexander Andreev 3f16c3e799
Removed sysusers.d/dwelling-upload.conf from Makefile. 2023-08-06 06:36:20 +04:00
Alexander Andreev 34c31448bb
Shortened two sentences on an index page. 2023-08-06 04:33:09 +04:00
Alexander Andreev 514c6208e2
Make available space bold. 2023-08-06 04:00:09 +04:00
Alexander Andreev 88b989bfbb
Fixed new code for HttpServer. Had to add net.Addr field to the struct to hold addr and network to use in Stop() method. 2023-08-06 03:06:24 +04:00
Alexander Andreev 7915091b96
Defer srv.Stop after successful call of srv.Start. 2023-08-06 03:00:31 +04:00
Alexander Andreev f22fa1ee1d
Do not create a system user, use a dynamic one instead. 2023-08-06 02:21:02 +04:00
Alexander Andreev 34890ff93f
Version was set to 23.31.1. 2023-08-06 02:13:56 +04:00
Alexander Andreev fea3e7104d
In nginx.conf added an onion domain to a server_name. And a space between it and access_log. 2023-08-06 02:12:15 +04:00
Alexander Andreev 2dc523271c
In dwelling-upload.service changed a formatting of one line to reduce its length. :) 2023-08-06 02:05:30 +04:00
Alexander Andreev 4048390bfb
Changed handlers.Error() signature to be like http.Error()'s one. 2023-08-06 02:02:58 +04:00
Alexander Andreev 6106e817cf
In utils.NetworkType() added Yggdrasil. 2023-08-06 01:59:25 +04:00
Alexander Andreev 30ffa6805b
Omit IP if a network type is not www or yggdrasil. 2023-08-06 01:54:01 +04:00
Alexander Andreev 17781c5445
Fixed copy-pasta. log.Fatalln() replaced with return err. 2023-08-06 01:42:56 +04:00
Alexander Andreev 3b15052330
Setup upload dir watcher at the last step. 2023-08-06 01:34:55 +04:00
Alexander Andreev 271c27f4ad
In dwelling-upload's main changed aliases for imports. 2023-08-06 01:28:55 +04:00
Alexander Andreev 532e8d9da6
Code for determining of network type was moved to HttpServer's Start() func. 2023-08-06 01:26:41 +04:00
Alexander Andreev 72d3104150
Explicit check for address type, now check if an addr passed is IPv6. Otherwise, network var will remain empty. 2023-08-06 01:21:14 +04:00
Alexander Andreev 5bd6120ae5
In var () section removed unnecessary type declarations. 2023-08-06 01:18:41 +04:00
Alexander Andreev d919d00cc0
In dwelling-upload-clean converted an expiry var to time.Duration. Added an 'h' unit in a .service to represent hours. 2023-08-05 19:28:05 +04:00
Alexander Andreev 1e30e5e8ff
Removed unnecessary .center class from sections. | replaced by p.center for space left. 2023-08-05 06:51:39 +04:00
Alexander Andreev c486acef79
Moved available space next to an Upload button. 2023-08-05 06:34:21 +04:00
Alexander Andreev aaa6aca743
Removing -buildmode=pie flag, it causes permission denied 203/EXEC failure when starting a service. 2023-08-05 06:27:48 +04:00
Alexander Andreev d251d174e3
Added a set of export CGO_*FLAGS statements recommended in https://wiki.archlinux.org/title/Go_package_guidelines 2023-08-05 06:02:54 +04:00
Alexander Andreev 82915fb300
Added some necessary flags for go build. 2023-08-05 06:01:10 +04:00
Alexander Andreev e7e45259ba
Oh, in os.Stat() path.Join() is needed, since DirEntry.Name() contains only name of a file itself, not a full path. 2023-08-05 05:07:59 +04:00
Alexander Andreev df8baf153b
Replaced deprecated ioutil.ReadDir() with os.ReadDir() + os.Stat() in filesize.go. 2023-08-05 05:03:34 +04:00
Alexander Andreev b513a5ff1d
Found that in an Error() func status code wasn't set. Fixed that. 2023-08-05 04:54:52 +04:00
Alexander Andreev c2278fdd2e
A little refactoring in handlers.go. 2023-08-05 04:46:25 +04:00
Alexander Andreev df3c5e0678
Oops, use Error() func instead of direct call of ErrorXXX(). 2023-08-05 04:26:11 +04:00
Alexander Andreev 266cf9dbb0
Removed nospace.jade. Replaced by errorXXX.jade. 2023-08-05 04:22:27 +04:00
Alexander Andreev 6b56037ded
Updated httpr dependency. 2023-08-05 04:17:17 +04:00
Alexander Andreev 9e20c424be
Version was set to 23.31.0. 2023-08-01 16:52:08 +04:00
Alexander Andreev d77552049a
In PKGBUILD build() removed spaces between lines. 2023-08-01 16:51:30 +04:00
Alexander Andreev 59e0629597
On index page only available storage is shown. 2023-08-01 16:50:39 +04:00
Alexander Andreev 6b8a7dffc7
In .service files flags was replaced by the new ones. 2023-08-01 16:46:50 +04:00
Alexander Andreev 084872be61
Returned Onion-Location header, and removed a separate I2P listen statement. 2023-08-01 16:44:30 +04:00
Alexander Andreev 5590b0ad71
In upload service flags was renamed. 2023-08-01 16:43:41 +04:00
Alexander Andreev e430f0bfc9
ioutil package is deprecated, so its ReadDir() was replaced by os. ReadDir() and os.Stat(). Also fixed existing error messages a little. 2023-08-01 16:42:39 +04:00
Alexander Andreev 1c7f03053f
In clean service: args are renamed: upload-dir -> dir, keep-for -> expiry. 2023-08-01 16:40:31 +04:00
Alexander Andreev 7e4af6b977
Version was changed to 23.24.0. 2023-06-12 23:10:14 +04:00
Alexander Andreev 5c6cf2586f
Removed Privacy statements section and added a link to it on a main site. 2023-06-12 23:09:03 +04:00
Alexander Andreev 9c72131dc8
Version patch part increased to 3. Nothing changed but a version of used httpr router. 2023-05-28 04:23:32 +04:00
Alexander Andreev d81a29bd5b
Removed no more needed func AssetsFS. 2023-05-28 04:18:50 +04:00
Alexander Andreev d5052994b8
Updated httpr to v0.2.0. 2023-05-28 04:14:48 +04:00
Alexander Andreev 2d4169245c
Reorginised the main()'s code a little. 2023-05-27 19:51:29 +04:00
Alexander Andreev 949c2e68c8
Patch part of version was incremented. 2023-05-27 19:39:18 +04:00
Alexander Andreev 3799c1d178
Fixed a comment in dwelling-upload.service. 2023-05-27 19:36:30 +04:00
Alexander Andreev 99adb7fbf8
Made max. size and hours bold. 2023-05-27 19:33:02 +04:00
Alexander Andreev aa3ca9a4ad
Free space section was renamed to more logical occupied space. 2023-05-27 19:32:44 +04:00
Alexander Andreev 96fd7e5883
Added links that lead to index page of a service in errorXXX and nospace templates. 2023-05-27 19:31:52 +04:00
Alexander Andreev 31f84721ad
Status 204 prevents a Deleted page rendering. 2023-05-27 19:31:11 +04:00
Alexander Andreev e695becbcd
Actually, ParseAddrPort is needed, since we give ip:port. 2023-05-27 19:30:28 +04:00
Alexander Andreev 114d3cc931
Oh, with ioutil.ReadDir subtracting of 4096 isn't needed. 2023-05-26 22:41:48 +04:00
Alexander Andreev 9a889e746a
Saved 2 lines by deferring each of two calls instead of wrapping in an anonymous func.
Also, removed old cURL handling in an Upload handler.
2023-05-26 22:17:03 +04:00
Alexander Andreev 345869608e
ParseAddrPort replaced with ParseAddr. Removed !ap.IsValid() since it is already done in ParseAddr. Also there is no need to additionally check if an address is IPv6. If address is neither v4 or v6, then ParseAddr will return an error. 2023-05-26 17:25:36 +04:00
Alexander Andreev d94030a4e8
In ConvertFileSize code was cosmetically changed a little. 2023-05-26 17:23:09 +04:00
Alexander Andreev 54f87951c1
In DirectorySize() filepath.Walk was replaced by ioutil.ReadDir to shorten the code. 2023-05-26 17:22:21 +04:00
Alexander Andreev da001dbe39
To decrease nesting go directive was removed. 2023-05-26 17:21:13 +04:00
Alexander Andreev 51494e87be
Patch version part was incremented. 2023-05-26 13:01:49 +04:00
Alexander Andreev 92710f7f5a
Cosmetic changes in flags. 2023-05-26 13:01:15 +04:00
Alexander Andreev 2abf5a9d7f
Fixed default upload dir path in progs as well. 2023-05-26 12:51:32 +04:00
Alexander Andreev 860d719cdd
Fixed a typo in upload dir path. 2023-05-26 12:49:18 +04:00
23 changed files with 261 additions and 273 deletions

View File

@ -5,8 +5,9 @@ SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
SYSDDIR=${SYSDDIR_:/%=%}
DESTDIR=/
VERSION=23.21.0
VERSION=23.34.0
FLAGS=-trimpath -mod=readonly -modcacherw
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
all: web/*.jade.go ${TARGET}
@ -14,8 +15,8 @@ all: web/*.jade.go ${TARGET}
.PHONY: ${TARGET}
${TARGET}:
go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go
go build -o bin/$@-clean ${LDFLAGS} cmd/$@-clean/main.go
go build -o bin/$@ ${FLAGS} ${LDFLAGS} cmd/$@/main.go
go build -o bin/$@-clean ${FLAGS} ${LDFLAGS} cmd/$@-clean/main.go
web/*.jade.go: web/templates/*.jade
go install github.com/Joker/jade/cmd/jade@latest
@ -32,8 +33,6 @@ install:
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 build/dwelling-upload.conf ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
uninstall:
rm ${DESTDIR}usr/bin/${TARGET}
rm ${DESTDIR}usr/bin/${TARGET}-clean
@ -44,8 +43,6 @@ uninstall:
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.timer
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clean.service
rm ${DESTDIR}usr/lib/sysusers.d/dwelling-upload.conf
clean:
rm -f web/*.jade.go
go clean

View File

@ -1,6 +1,6 @@
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
pkgname=dwelling-upload
pkgver=23.21.0
pkgver=23.34.0
pkgrel=1
pkgdesc="Arav's Dwelling / Upload"
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
@ -14,9 +14,11 @@ md5sums=('SKIP')
build() {
cd "$srcdir/$pkgname"
export GOPATH="$srcdir"/gopath
export CGO_CPPFLAGS="${CPPFLAGS}"
export CGO_CFLAGS="${CFLAGS}"
export CGO_CXXFLAGS="${CXXFLAGS}"
export CGO_LDFLAGS="${LDFLAGS}"
make VERSION=$pkgver DESTDIR="$pkgdir/"
}

View File

@ -1,4 +0,0 @@
# sysusers.d
g dwupload - -
u dwupload - -
m dwupload dwupload

View File

@ -3,16 +3,18 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"time"
)
var uploadDir *string = flag.String("upload-dir", "/srv/uploads", "path to a directory where uploaded files are stored")
var keepFileForHours *int64 = flag.Int64("keep-for", 36, "keep files for this much hours")
var showVersion *bool = flag.Bool("v", false, "show version")
var (
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
@ -25,15 +27,21 @@ func main() {
return
}
uploadsDir, err := ioutil.ReadDir(*uploadDir)
uploadsDir, err := os.ReadDir(*uploadDir)
if err != nil {
log.Fatalf("failed to open directory %s: %s\n", *uploadDir, err)
log.Fatalf("failed to open a directory %s: %s\n", *uploadDir, err)
}
for _, entry := range uploadsDir {
if time.Now().UTC().Sub(entry.ModTime().UTC()) >= time.Duration(*keepFileForHours)*time.Hour {
file, err := os.Stat(path.Join(*uploadDir, entry.Name()))
if err != nil {
log.Printf("failed to stat a file %s: %s", entry.Name(), err)
continue
}
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 file %s: %s", entry.Name(), err)
log.Printf("failed to remove a file %s: %s", entry.Name(), err)
}
}
}

View File

@ -1,30 +1,31 @@
package main
import (
"dwelling-upload/internal/http"
duihttp "dwelling-upload/internal/http"
"dwelling-upload/pkg/utils"
"dwelling-upload/pkg/watcher"
"dwelling-upload/web"
"flag"
"fmt"
"log"
nethttp "net/http"
"net/netip"
"net/http"
"os"
"os/signal"
"path"
"strings"
"syscall"
"git.arav.su/Arav/httpr"
)
var listenAddress *string = flag.String("listen", "/var/run/dwelling-upload/sock", "listen address (ip:port|unix_path)")
var uploadDir *string = flag.String("upload-dir", "/srv/uploads", "path to a directory where uploaded files are stored")
var keepFileForHours *int = flag.Int("keep-for", 36, "keep files for this much hours")
var limitStorage *int64 = flag.Int64("storage", 102400, "storage size in MiB for uploads")
var limitFileSize *int64 = flag.Int64("file-size", 128, "max. size in MiB for uploaded files")
var showVersion *bool = flag.Bool("v", false, "show version")
var (
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
@ -37,25 +38,6 @@ func main() {
return
}
var network string
if !strings.ContainsRune(*listenAddress, ':') {
network = "unix"
defer os.Remove(*listenAddress)
} else {
ap, err := netip.ParseAddrPort(*listenAddress)
if err != nil {
log.Fatalln(err)
} else if !ap.IsValid() {
log.Fatalln(*listenAddress, "is not valid")
}
if ap.Addr().Is4() {
network = "tcp4"
} else if ap.Addr().Is6() {
network = "tcp6"
}
}
hashSalt, err := os.ReadFile(path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "salt"))
if err != nil {
log.Fatalln("failed to read hash salt file:", err)
@ -71,6 +53,44 @@ func main() {
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() {
if err := srv.Stop(); err != nil {
log.Fatalln("failed to properly shutdown a server:", err)
}
}()
doneSignal := make(chan os.Signal, 1)
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
watcha, err := watcher.NewInotifyWatcher()
if err != nil {
log.Fatalln(err)
@ -82,40 +102,7 @@ func main() {
}
uploadDirNotify := make(chan uint32)
uploadDirSize, err := utils.DirectorySize(*uploadDir)
if err != nil {
log.Fatalf("failed to get initial size of %s: %s", *uploadDir, err)
}
watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
hand := http.NewUploadHandlers(logFile, *uploadDir, &uploadDirSize, string(hashSalt),
*keepFileForHours, *limitStorage, *limitFileSize)
r := httpr.New()
r.NotFoundHandler = func(w nethttp.ResponseWriter, r *nethttp.Request) {
http.Error(w, r, nethttp.StatusNotFound, "")
}
r.Handler(nethttp.MethodGet, "/", hand.Index)
r.Handler(nethttp.MethodPost, "/", hand.Upload)
r.Handler(nethttp.MethodGet, "/:hash/:name", hand.Download)
r.Handler(nethttp.MethodPost, "/delete", hand.Delete)
r.Handler(nethttp.MethodDelete, "/:hash", hand.Delete)
r.ServeStatic("/assets/*filepath", web.Assets())
r.Handler(nethttp.MethodGet, "/robots.txt", http.RobotsTxt)
r.Handler(nethttp.MethodGet, "/favicon.svg", http.Favicon)
srv := http.NewHttpServer(r)
if err := srv.Start(network, *listenAddress); err != nil {
log.Fatalln("failed to start a server:", err)
}
doneSignal := make(chan os.Signal, 1)
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
go watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
go func() {
for {
@ -130,8 +117,4 @@ func main() {
}()
<-doneSignal
if err := srv.Stop(); err != nil {
log.Fatalln("failed to properly shutdown a server:", err)
}
}

View File

@ -1,22 +1,22 @@
server {
listen 443 ssl http2;
# listen 8094; # Tor
listen 127.0.0.1:8114; # I2P
listen 8094; # Tor I2P
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;
ssl_certificate /etc/letsencrypt/live/arav.su/fullchain.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-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
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 / {

2
go.mod
View File

@ -4,4 +4,4 @@ go 1.17
require github.com/pkg/errors v0.9.1
require git.arav.su/Arav/httpr v0.1.0
require git.arav.su/Arav/httpr v0.3.1

4
go.sum
View File

@ -1,4 +1,4 @@
git.arav.su/Arav/httpr v0.1.0 h1:jp1BDGioYUV/egITFSGHbCRvIORjjnCvv42DZFJ7uWg=
git.arav.su/Arav/httpr v0.1.0/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
git.arav.su/Arav/httpr v0.3.1 h1:8ba90SJ4XYUWfIlC3V0Zuw3+CcOb9IYVkOZ/2mB9JO0=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@ -3,9 +3,8 @@ Description=dwelling-upload-clean
[Service]
Type=oneshot
User=dwupload
Group=dwupload
ExecStart=/usr/bin/dwelling-upload-clean -upload-dir /srv/uploads -keep-for 36
DynamicUser=yes
ExecStart=/usr/bin/dwelling-upload-clean -dir /srv/upload -expiry 36h
ReadOnlyPaths=/
# Set here path to directory where uploads are stored.
@ -20,18 +19,33 @@ LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
PrivateUsers=true
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=noaccess
ProtectSystem=strict
RestrictAddressFamilies=
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
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]
WantedBy=multi-user.target

View File

@ -5,10 +5,9 @@ After=network.target
[Service]
Type=simple
Restart=on-failure
User=dwupload
Group=dwupload
ExecStart=/usr/bin/dwelling-upload -listen /var/run/dwelling-upload/sock -upload-dir /srv/uploads \
-keep-for 36 -storage 102400 -file-size 128
DynamicUser=yes
ExecStart=/usr/bin/dwelling-upload -listen /var/run/dwelling-upload/sock \
-dir /srv/upload -expiry 36 -storage 102400 -file 128
ReadOnlyPaths=/
# Set here path to directory where uploads are stored.
@ -19,8 +18,7 @@ ExecPaths=/usr/bin/dwelling-upload
RuntimeDirectory=dwelling-upload
LogsDirectory=dwelling-upload
# Use gen-salt.sh to generate salt! It will create override.conf or add to the
# end of existing one.
# Use gen-salt.sh to generate salt! It will create / append to an override.conf.
SetCredentialEncrypted=
AmbientCapabilities=
@ -30,18 +28,33 @@ LockPersonality=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
PrivateUsers=true
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=noaccess
ProtectSystem=strict
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
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]
WantedBy=multi-user.target

View File

@ -48,12 +48,10 @@ func (h *UploadHandlers) Index(w http.ResponseWriter, r *http.Request) {
var storCapacity int64 = h.limitStorage << 20
var fMaxSize int64 = h.limitFileSize << 20
_, _, capStr := utils.ConvertFileSize(storCapacity)
_, _, usedStr := utils.ConvertFileSize(*h.uploadDirSize)
_, _, availStr := utils.ConvertFileSize(storCapacity - *h.uploadDirSize)
_, _, fMaxSzStr := utils.ConvertFileSize(fMaxSize)
web.Index(utils.MainSite(r.Host), storCapacity, *h.uploadDirSize, h.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) {
@ -64,42 +62,37 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
log.Println("failed to parse upload form:", err)
Error(w, r, http.StatusExpectationFailed, "Failed to parse upload form.")
Error(w, r, "Failed to parse upload form.", http.StatusExpectationFailed)
return
}
f, fHandler, err := r.FormFile("file")
if err != nil {
log.Println("failed to open incoming file:", err)
Error(w, r, http.StatusInternalServerError, "Error reading an incoming file.")
Error(w, r, "Error reading an incoming file.", http.StatusInternalServerError)
return
}
defer func() {
os.Remove(fHandler.Filename)
f.Close()
}()
defer os.Remove(fHandler.Filename)
defer f.Close()
var leftSpace int64 = storCapacity - *h.uploadDirSize
if leftSpace < fHandler.Size {
log.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
http.Error(w, "Not enough space left, sorry", http.StatusInternalServerError)
} else {
web.ErrorNoSpace(utils.MainSite(r.Host), w)
}
Error(w, r, "Not enough space left, sorry.", http.StatusInternalServerError)
return
}
s256 := sha256.New()
if _, err := io.Copy(s256, f); err != nil {
log.Println("failed to compute a SHA-256 hash:", err)
Error(w, r, http.StatusInternalServerError, "A hash for the file cannot be computed.")
Error(w, r, "A hash for the file cannot be computed.", http.StatusInternalServerError)
return
}
fHash := hex.EncodeToString(s256.Sum(nil))
s256.Write([]byte(h.hashSalt))
s256.Write([]byte(time.Now().String()))
fSaltedHash := base64.RawURLEncoding.EncodeToString(s256.Sum(nil))
f.Seek(0, io.SeekStart)
@ -111,7 +104,7 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
fDst, err := os.Create(fPath)
if err != nil {
log.Println("failed to open file for writing", err)
Error(w, r, http.StatusInternalServerError, "File cannot be written.")
Error(w, r, "File cannot be written.", http.StatusInternalServerError)
return
}
defer fDst.Close()
@ -124,17 +117,20 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
fDst.Write([]byte{0})
fDst.Seek(0, io.SeekStart)
_, err = io.Copy(fDst, f)
if err != nil {
if _, err = io.Copy(fDst, f); err != nil {
log.Println("failed to copy uploaded file to destination:", err)
Error(w, r, http.StatusInternalServerError, "Failed to copy uploaded file to the storage.")
Error(w, r, "Failed to copy uploaded file to the storage.", http.StatusInternalServerError)
return
}
typ, _ := utils.NetworkType(r.Host)
ip := r.Header.Get("X-Real-IP")
if typ != "www" && typ != "ygg" {
ip = ""
}
h.logFile.Printf("| up | %s | %s | %s | SHA256 %s | %s | %d | %s", r.Header.Get("X-Real-IP"), typ,
fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
h.logFile.Printf("| up | %s | %s | %s | SHA256 %s | %s | %d | %s",
ip, typ, fHandler.Filename, fHash, fSaltedHash, fHandler.Size, r.UserAgent())
w.WriteHeader(http.StatusCreated)
} else {
@ -148,13 +144,8 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
_, scheme := utils.NetworkType(r.Host)
site := scheme + "://" + r.Host
if strings.Contains(r.UserAgent(), "curl") {
w.Write([]byte(site + downloadURLParsed.String() + "\r\n"))
return
}
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
fmt.Fprintln(w, downloadURLParsed.String(), "will be kept for", h.keepForHours)
fmt.Fprintln(w, site+downloadURLParsed.String(), "will be kept for", h.keepForHours)
} else {
web.Uploaded(utils.MainSite(r.Host), site, downloadURLParsed.String(), h.keepForHours, w)
}
@ -167,7 +158,7 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
stat, err := os.Stat(path)
if os.IsNotExist(err) {
Error(w, r, http.StatusNotFound, "")
Error(w, r, "", http.StatusNotFound)
return
}
@ -178,14 +169,19 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
fd, err := os.Open(path)
if err != nil {
log.Println("failed to open file to read:", err)
Error(w, r, http.StatusInternalServerError, "Failed to open file to read.")
Error(w, r, "Failed to open file to read.", http.StatusInternalServerError)
return
}
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.logFile.Printf("| dw | %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)
}
@ -201,24 +197,26 @@ func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
path := path.Join(h.uploadDir, saltedHash)
_, err := os.Stat(path)
if os.IsNotExist(err) {
Error(w, r, http.StatusNotFound, "")
if _, err := os.Stat(path); os.IsNotExist(err) {
Error(w, r, "", http.StatusNotFound)
return
}
err = os.Remove(path)
if err != nil {
if err := os.Remove(path); err != nil {
log.Println("failed to remove a file:", err)
Error(w, r, http.StatusInternalServerError, "Failed to remove a file.")
Error(w, r, "Failed to remove a file.", http.StatusInternalServerError)
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.logFile.Printf("| dt | %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 {
@ -226,19 +224,16 @@ func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
}
}
func Error(w http.ResponseWriter, r *http.Request, code int, reason string) {
func Error(w http.ResponseWriter, r *http.Request, reason string, code int) {
if strings.Contains(r.UserAgent(), "curl") || strings.Contains(r.UserAgent(), "Wget") {
http.Error(w, reason, code)
return
}
w.WriteHeader(code)
web.ErrorXXX(utils.MainSite(r.Host), code, reason, w)
}
func AssetsFS() http.FileSystem {
return web.Assets()
}
func RobotsTxt(w http.ResponseWriter, r *http.Request) {
data, _ := web.AssetsGetFile("robots.txt")
w.Write(data)

View File

@ -5,12 +5,15 @@ import (
"log"
"net"
"net/http"
"net/netip"
"os"
"strings"
"time"
)
type HttpServer struct {
s http.Server
s http.Server
addr net.Addr
}
func NewHttpServer(r http.Handler) *HttpServer {
@ -20,7 +23,23 @@ func NewHttpServer(r http.Handler) *HttpServer {
Handler: r}}
}
func (s *HttpServer) Start(network, address string) error {
func (s *HttpServer) Start(address string) error {
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"
}
}
listener, err := net.Listen(network, address)
if err != nil {
return err
@ -30,6 +49,8 @@ func (s *HttpServer) Start(network, address string) error {
os.Chmod(address, 0777)
}
s.addr = listener.Addr()
go func() {
if err = s.s.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Fatalln(err)
@ -43,6 +64,10 @@ func (s *HttpServer) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if s.addr.Network() == "unix" {
defer os.Remove(s.addr.String())
}
if err := s.s.Shutdown(ctx); err != nil {
return err
}

View File

@ -25,6 +25,8 @@ func NetworkType(host string) (string, string) {
return "i2p", "http"
} else if strings.Contains(host, "onion") {
return "tor", "http"
} else if strings.Contains(host, "[300:") {
return "ygg", "http"
} else {
return "www", "https"
}

View File

@ -1,8 +1,8 @@
package utils
import (
"io/fs"
"path/filepath"
"os"
"path"
"strconv"
"strings"
@ -14,10 +14,10 @@ var sizeSuffixes = [...]string{"B", "KiB", "MiB", "GiB", "TiB"}
// ConvertFileSize converts size in bytes down to biggest units it represents.
// Returns converted size, unit and, a concatenation of size and unit
func ConvertFileSize(size int64) (float64, string, string) {
var idx int
var fSize float64 = float64(size)
idx := 0
fSize := float64(size)
for idx = 0; fSize >= 1024; fSize /= 1024 {
for ; fSize >= 1024; fSize /= 1024 {
idx++
}
@ -25,23 +25,23 @@ func ConvertFileSize(size int64) (float64, string, string) {
fSizeStr = strings.TrimRight(fSizeStr, "0")
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) {
err = filepath.Walk(path, func(_ string, info fs.FileInfo, err error) error {
if err != nil {
return errors.Wrapf(err, "failed to compute %s directory size", path)
}
dirSz += info.Size()
return nil
})
func DirectorySize(dirPath string) (dirSz int64, err error) {
dir, err := os.ReadDir(dirPath)
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
}

View File

@ -54,26 +54,24 @@ func (w *InotifyWatcher) AddWatch(path string, mask uint32) error {
// WatchForMask checks for events specified in `mask` and sends them
// to channel `fired`. See `man inotify` for events.
func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) {
go func() {
for !w.closed {
buffer := make([]byte, syscall.SizeofInotifyEvent*inotifyCount)
n, err := syscall.Read(w.fd, buffer)
if err != nil {
break
}
for !w.closed {
buffer := make([]byte, syscall.SizeofInotifyEvent*inotifyCount)
n, err := syscall.Read(w.fd, buffer)
if err != nil {
break
}
if n < syscall.SizeofInotifyEvent {
continue
}
if n < syscall.SizeofInotifyEvent {
continue
}
for offset := 0; offset < len(buffer); offset += syscall.SizeofInotifyEvent {
event := (*syscall.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
if event.Mask&mask > 0 {
fired <- event.Mask
}
for offset := 0; offset < len(buffer); offset += syscall.SizeofInotifyEvent {
event := (*syscall.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
if event.Mask&mask > 0 {
fired <- event.Mask
}
}
}()
}
}
// Close removes all watchers, closes inotify descriptor and stops all

View File

@ -33,6 +33,8 @@
background-color: var(--secondary-color);
color: var(--background-color); }
.center { text-align: center; }
a,
button {
color: var(--primary-color);
@ -50,8 +52,8 @@ input[type="file"] {
color: var(--text-color);
font: inherit; }
input[type="file"]::file-selector-button,
button {
button,
input[type="file"]::file-selector-button {
background: none;
border: none;
color: var(--primary-color);
@ -91,17 +93,6 @@ h2 {
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%); }
body {
@ -118,24 +109,22 @@ header {
flex-wrap: wrap;
justify-content: space-between; }
#logo {
display: block;
width: 360px; }
header svg { 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-variant-caps: small-caps;
font-weight: bold; }
header svg text:last-child { font-size: .88rem; }
@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() {
#logo .logo { font-size: 2rem; } }
#logo .under { font-size: .88rem; }
header svg text:first-child { font-size: 2rem; } }
nav { margin-top: .5rem; }
@ -147,7 +136,13 @@ nav h1 {
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 {
font-size: .8rem;
@ -157,7 +152,7 @@ footer {
@media screen and (max-width: 640px) {
header { display: block; }
#logo {
header svg {
margin: 0 auto;
width: 100%; }

View File

@ -1,2 +1,3 @@
User-agent: *
Disallow: /assets/
Allow: /$
Disallow: /

View File

@ -12,12 +12,12 @@ html(lang="en")
block head
body
header
svg#logo(viewBox="0 -25 216 40")
text.logo Arav's dwelling
text.under(y="11") Welcome to my sacred place, wanderer
svg(viewBox="0 -25 216 40")
text Arav's dwelling
text(y="11") Welcome to my sacred place, wanderer
nav
a(href=mainSite title="Arav's dwelling") Back to main website
block header
block body
footer
| 2022,2023 Alexander "Arav" Andreev &lt;#[a(href="mailto:me@arav.su") me@arav.su]&gt;
| 2022,2023 Alexander "Arav" Andreev &lt;#[a(href="mailto:me@arav.su") me@arav.su]&gt; #[a(href=mainSite+'/privacy') Privacy statements]

View File

@ -1,22 +1,14 @@
extends base.jade
block head
:go:func ErrorXXX(mainSite string, code int, errorMsg 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 Еггог
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

View File

@ -4,37 +4,25 @@ block header
h1 Upload
block body
:go:func Index(mainSite string, storageCapacity, storageUsed int64, keepForHours int, fileMaxSize, storageUsedStr, storageCapacityStr, storageAvailableStr string)
section#rules.center
:go:func Index(mainSite string, keepForHours int, fileMaxSize, storageAvailableStr string)
section
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 Russian Federation's law. Generally speaking, anything illegal, like CP, extremist literature, and so on is forbidden.
section#used-space.center
h2 Free space
div
span #{storageUsedStr}
progress(value=storageUsed max=storageCapacity)
span #{storageCapacityStr}
div
| #{storageAvailableStr}
section#upload.center
section.center
h2 Upload
form(action="/" method="POST" enctype="multipart/form-data")
input(type="file" name="file" multiple=false)
button(type="submit") Upload
section.center
p You can use cURL to upload a file: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -F 'file=@somefile.ext' http://upload.arav.i2p]
p A resulted link looks like this: #[code /base64rawURL(salted SHA-256)/filename.ext].
p.center #[b #{storageAvailableStr}] left.
section
p Using cURL: #[code curl -F 'file=@somefile.ext' https://upload.arav.su]
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 &lt;site&gt;/&lt;hash&gt;/&lt;file&gt;.&lt;ext&gt;].
section.center
h2 Delete
form(action="/delete" method="POST")
input(type="text", name="hash" placeholder="File hash goes here" minlength="43" maxlength="43" size="43" required="")
button(type="submit") Delete
section.center
p You can delete a file using cURL: #[code curl -XDELETE https://upload.arav.su/&lt;hash&gt;]
p Over I2P: #[code curl --proxy 127.0.0.1:4444 -XDELETE http://upload.arav.i2p/&lt;hash&gt;]
section
h2 Privacy statements
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.
p Using cURL: #[code curl -XDELETE https://upload.arav.su/&lt;hash&gt;]

View File

@ -1,20 +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
- wr.(http.ResponseWriter).WriteHeader(http.StatusInternalServerError)
section#error
h1 Not enough space left

View File

@ -5,7 +5,7 @@ block header
block body
:go:func Uploaded(mainSite, site, downloadLink string, keepForHours int)
section#file
section
h2 Your link
center
a(href=downloadLink) #{site}#{downloadLink}

View File

@ -7,7 +7,6 @@ import (
)
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/index.jade
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/nospace.jade
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/deleted.jade
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/uploaded.jade
//go:generate $GOPATH/bin/jade -pkg=web -writer templates/errorXXX.jade