1
0

Compare commits

...

141 Commits

Author SHA1 Message Date
7218fbc27f
Version set to 23.34.0. 2023-08-23 04:04:22 +04:00
5148202a0f
A small reorganisation in main.css. 2023-08-23 03:48:52 +04:00
1edba52d32
Disallow all pages but the index. 2023-08-23 03:43:35 +04:00
9c42144b45
Updated index.jade. 2023-08-22 18:46:02 +04:00
58197af85d
Added new options to .service files. Hope it won't break a program. xD 2023-08-22 18:24:02 +04:00
4e098ec665
style-src unsafe-inline was removed because it doesn't needed anymore. 2023-08-22 18:21:25 +04:00
5567de7de1
Error styling was moved out to main.css. 2023-08-22 18:20:13 +04:00
63e03f6459
Removed unused ids. 2023-08-22 18:19:47 +04:00
90837f1299
Changed logo. Removed id and classes. 2023-08-22 18:16:26 +04:00
b50a80a33d
Updated httpr package dependency to 0.3.1. 2023-08-13 04:19:14 +04:00
92212c97eb
Add time for better randomisation for a hash. 2023-08-06 07:33:34 +04:00
3f16c3e799
Removed sysusers.d/dwelling-upload.conf from Makefile. 2023-08-06 06:36:20 +04:00
34c31448bb
Shortened two sentences on an index page. 2023-08-06 04:33:09 +04:00
514c6208e2
Make available space bold. 2023-08-06 04:00:09 +04:00
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
7915091b96
Defer srv.Stop after successful call of srv.Start. 2023-08-06 03:00:31 +04:00
f22fa1ee1d
Do not create a system user, use a dynamic one instead. 2023-08-06 02:21:02 +04:00
34890ff93f
Version was set to 23.31.1. 2023-08-06 02:13:56 +04:00
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
2dc523271c
In dwelling-upload.service changed a formatting of one line to reduce its length. :) 2023-08-06 02:05:30 +04:00
4048390bfb
Changed handlers.Error() signature to be like http.Error()'s one. 2023-08-06 02:02:58 +04:00
6106e817cf
In utils.NetworkType() added Yggdrasil. 2023-08-06 01:59:25 +04:00
30ffa6805b
Omit IP if a network type is not www or yggdrasil. 2023-08-06 01:54:01 +04:00
17781c5445
Fixed copy-pasta. log.Fatalln() replaced with return err. 2023-08-06 01:42:56 +04:00
3b15052330
Setup upload dir watcher at the last step. 2023-08-06 01:34:55 +04:00
271c27f4ad
In dwelling-upload's main changed aliases for imports. 2023-08-06 01:28:55 +04:00
532e8d9da6
Code for determining of network type was moved to HttpServer's Start() func. 2023-08-06 01:26:41 +04:00
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
5bd6120ae5
In var () section removed unnecessary type declarations. 2023-08-06 01:18:41 +04:00
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
1e30e5e8ff
Removed unnecessary .center class from sections. | replaced by p.center for space left. 2023-08-05 06:51:39 +04:00
c486acef79
Moved available space next to an Upload button. 2023-08-05 06:34:21 +04:00
aaa6aca743
Removing -buildmode=pie flag, it causes permission denied 203/EXEC failure when starting a service. 2023-08-05 06:27:48 +04:00
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
82915fb300
Added some necessary flags for go build. 2023-08-05 06:01:10 +04:00
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
df8baf153b
Replaced deprecated ioutil.ReadDir() with os.ReadDir() + os.Stat() in filesize.go. 2023-08-05 05:03:34 +04:00
b513a5ff1d
Found that in an Error() func status code wasn't set. Fixed that. 2023-08-05 04:54:52 +04:00
c2278fdd2e
A little refactoring in handlers.go. 2023-08-05 04:46:25 +04:00
df3c5e0678
Oops, use Error() func instead of direct call of ErrorXXX(). 2023-08-05 04:26:11 +04:00
266cf9dbb0
Removed nospace.jade. Replaced by errorXXX.jade. 2023-08-05 04:22:27 +04:00
6b56037ded
Updated httpr dependency. 2023-08-05 04:17:17 +04:00
9e20c424be
Version was set to 23.31.0. 2023-08-01 16:52:08 +04:00
d77552049a
In PKGBUILD build() removed spaces between lines. 2023-08-01 16:51:30 +04:00
59e0629597
On index page only available storage is shown. 2023-08-01 16:50:39 +04:00
6b8a7dffc7
In .service files flags was replaced by the new ones. 2023-08-01 16:46:50 +04:00
084872be61
Returned Onion-Location header, and removed a separate I2P listen statement. 2023-08-01 16:44:30 +04:00
5590b0ad71
In upload service flags was renamed. 2023-08-01 16:43:41 +04:00
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
1c7f03053f
In clean service: args are renamed: upload-dir -> dir, keep-for -> expiry. 2023-08-01 16:40:31 +04:00
7e4af6b977
Version was changed to 23.24.0. 2023-06-12 23:10:14 +04:00
5c6cf2586f
Removed Privacy statements section and added a link to it on a main site. 2023-06-12 23:09:03 +04:00
9c72131dc8
Version patch part increased to 3. Nothing changed but a version of used httpr router. 2023-05-28 04:23:32 +04:00
d81a29bd5b
Removed no more needed func AssetsFS. 2023-05-28 04:18:50 +04:00
d5052994b8
Updated httpr to v0.2.0. 2023-05-28 04:14:48 +04:00
2d4169245c
Reorginised the main()'s code a little. 2023-05-27 19:51:29 +04:00
949c2e68c8
Patch part of version was incremented. 2023-05-27 19:39:18 +04:00
3799c1d178
Fixed a comment in dwelling-upload.service. 2023-05-27 19:36:30 +04:00
99adb7fbf8
Made max. size and hours bold. 2023-05-27 19:33:02 +04:00
aa3ca9a4ad
Free space section was renamed to more logical occupied space. 2023-05-27 19:32:44 +04:00
96fd7e5883
Added links that lead to index page of a service in errorXXX and nospace templates. 2023-05-27 19:31:52 +04:00
31f84721ad
Status 204 prevents a Deleted page rendering. 2023-05-27 19:31:11 +04:00
e695becbcd
Actually, ParseAddrPort is needed, since we give ip:port. 2023-05-27 19:30:28 +04:00
114d3cc931
Oh, with ioutil.ReadDir subtracting of 4096 isn't needed. 2023-05-26 22:41:48 +04:00
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
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
d94030a4e8
In ConvertFileSize code was cosmetically changed a little. 2023-05-26 17:23:09 +04:00
54f87951c1
In DirectorySize() filepath.Walk was replaced by ioutil.ReadDir to shorten the code. 2023-05-26 17:22:21 +04:00
da001dbe39
To decrease nesting go directive was removed. 2023-05-26 17:21:13 +04:00
51494e87be
Patch version part was incremented. 2023-05-26 13:01:49 +04:00
92710f7f5a
Cosmetic changes in flags. 2023-05-26 13:01:15 +04:00
2abf5a9d7f
Fixed default upload dir path in progs as well. 2023-05-26 12:51:32 +04:00
860d719cdd
Fixed a typo in upload dir path. 2023-05-26 12:49:18 +04:00
30513b3300
Removed go:generate for error404.jade. 2023-05-26 04:40:58 +04:00
b695d8128d
Removed disallow of /f/ from robots.txt. 2023-05-26 04:38:14 +04:00
85f853f48e
Removed /f/ path part in upload handler. 2023-05-26 04:37:07 +04:00
dd22577521
Removed /f/ path part at index.jade. 2023-05-26 04:36:36 +04:00
1e4836ca86
Made errorMsg into p, so it get to the next line. 2023-05-26 04:36:07 +04:00
e47e592b01
julienschmidt/httprouter was removed and my httpr added to go.mod and go.sum. 2023-05-26 04:31:05 +04:00
38e6813d11
Made use of my own httpr router in main. 2023-05-26 04:30:29 +04:00
b18338e4e1
Made use of my own httpr router. Replace GetURLParam with httpr.Param. 2023-05-26 04:28:38 +04:00
0677147b63
Removed httprouter dependency from HTTP server. 2023-05-26 04:27:21 +04:00
a9e230ad61
Removed unneeded error404.jade. 2023-05-26 04:26:27 +04:00
a7972b8e13
A custom logger was removed. 2023-05-25 02:56:57 +04:00
c5faef303b
In service replaced args for ExecStart= to the new ones. 2023-05-25 02:32:04 +04:00
5101a892de
Made AssetsFS() a standalone func. 2023-05-25 02:31:21 +04:00
8eb8a24a23
Added copytruncate to truncate a file instead of recreating. Removed script to send SIGHUP since it is not used anymore. 2023-05-25 02:27:03 +04:00
afbaad971a
Instantiate log file handle and an instance of log.Logger. Also remove reopen SIGHUP handler. Log file will be truncated with logrotate instead of recreating. 2023-05-25 02:25:43 +04:00
fccb81d3a5
Added | separator to the start of all log.Printf() to separate date and action. 2023-05-25 02:24:00 +04:00
3327b30d12
Replaced logging.Logger with std log.Logger. 2023-05-25 02:22:56 +04:00
59544da625
Removed configPath flag from main. 2023-05-25 02:17:27 +04:00
85529890a5
Oh, code for deletion statistics also should be purged. 2023-05-25 02:13:33 +04:00
9510027d05
Replace flags in a clean service with the new ones. 2023-05-25 02:11:20 +04:00
55ba1fd67d
Do not log how much files was deleted. Useless info, keep only error logs.
And removed configPath flag as well.
2023-05-25 02:10:35 +04:00
203c0158ce
Install gen-salt.sh as dwelling-upload-gen-salt. 2023-05-25 00:58:02 +04:00
282a00c328
Found that daemon-reload needs to be called when manually edit such files. 2023-05-25 00:57:36 +04:00
99eed674fc
Since we now run setup command from script, this caution doesn't needed. 2023-05-25 00:53:35 +04:00
cf7e240e8a
Let's add the creds to override.conf, actually. And it is safe to run systemd-creds setup, since it doesn't rewrite an existing key. 2023-05-25 00:52:51 +04:00
705a4ede76
Updated comments in upload.service. 2023-05-25 00:49:30 +04:00
dab675474a
Set default NotFound handler. 2023-05-25 00:47:32 +04:00
3f79eb5b08
Added SetCredentialEncrypted= and a comment. 2023-05-25 00:14:24 +04:00
f0fc34c8e7
Remove func NotFound. 2023-05-25 00:09:30 +04:00
2ef85c6f29
Handle cURL and Wget clients. 2023-05-25 00:08:52 +04:00
b16ec84e86
Added missing return in free space check. 2023-05-24 23:57:02 +04:00
f8351b935d
Moved WriteHeader to nospace.jade. 2023-05-24 23:56:12 +04:00
64132ec18f
Removed missed w.WriteHeader(). 2023-05-24 23:52:33 +04:00
fb9cee2c0a
A new Error() handler used. 2023-05-24 23:50:49 +04:00
185bd80750
error50x made into universal error page. Also Error() handler was made that checks for cURL and Wget, and if find, then use built-in plaintext http.Error, and web.errorXXX otherwise. 2023-05-24 23:50:10 +04:00
a04adf7fa1
Replaced http.Error with 50x errors with web.Error50x(). 2023-05-24 23:20:21 +04:00
6e9ad9323c
Added error50x template. 2023-05-24 23:20:05 +04:00
06dfcaac8e
Removed excessive wtih word in index.jade. 2023-05-24 23:09:47 +04:00
35468af206
In web/*.jade.go target perform check for changes in templates. 2023-05-24 23:09:16 +04:00
3696e1dfe1
Also, gen-salt demands to be runned under root, so let's check for it and fail if it doesn't. 2023-05-24 22:58:32 +04:00
14ec537d82
And, let's change line wrapping here. 2023-05-24 22:49:36 +04:00
14efad4a4a
Rewrote gen-salt.sh in a right way. 2023-05-24 22:48:28 +04:00
9c5bad04d4
Made a new target to generate templates only when they are missing. Also put go install jade in it. Hence, no need in check in the PKGBUILD. 2023-05-24 22:40:06 +04:00
21d05e7488
Remove *.jade.go as well on clean target in Makefile. 2023-05-24 22:29:40 +04:00
ec2656c719
jade.go is being automatically generated, so no need to keep it. Also removed unneeded flags from go:generate in web.go. 2023-05-24 22:28:36 +04:00
b95052709a
Removed upload.yaml from Makefile and PKGBUILD. Removed run and run-clean targets. 2023-05-24 22:25:19 +04:00
af0492e5a0
Version's week part incremented. 2023-05-24 22:23:05 +04:00
57aea7ef77
Added gen-salt.sh to generate and get a SetCredentialEncrypted= to put in a systemd service. 2023-05-24 22:22:11 +04:00
badeda87ad
No need in gopkg.in/yaml.v3 anymore. 2023-05-24 22:21:08 +04:00
cc1cbeffcd
Removed config.yaml. 2023-05-24 22:20:22 +04:00
332e9a1bb3
Removed Configuration. 2023-05-24 22:20:02 +04:00
931f98d12e
Made use of cmd arguments. 2023-05-24 22:19:41 +04:00
11dfbfbc23
Added flags that replace config file. 2023-05-24 22:11:28 +04:00
470bbb04ee
Get file.log path as join of its name and LOGS_DIRECTORY env var. 2023-05-24 22:10:57 +04:00
deed268b4f
Get config passed as cmd args instead. 2023-05-24 22:09:41 +04:00
33f69eaf44
Changed error logging for upload-clean as well. 2023-05-24 22:09:04 +04:00
c85c6555e2
Log errors to stdout (systemd-journal) instead of a separate file. Also updated error messages. And since journal already puts a date, use only a Llongfile flag to print a place where an error occured. 2023-05-24 21:45:46 +04:00
c43c314a78
Pass listen address as a -listen argument. Use the new code to parse a parameter. 2023-05-24 21:26:48 +04:00
ce6d81ad55
Added /f/ to robots.txt. 2023-05-24 02:57:41 +04:00
88ec5e5efe
Added missing io.Writer in a call of web.Deleted. 2023-05-21 22:24:36 +04:00
33ec19647b
Reworked logging. Store all actions in one file.log. 2023-05-21 21:27:44 +04:00
31d2c4fd21
Let's, actually, directly assign to uploadDirSize. 2023-05-21 21:12:23 +04:00
7e1b56bbc5
Version's week part was incremented. 2023-05-21 20:53:24 +04:00
50ca035a1f
Uhm... Don't recall why, but nonetheless, this is why size wasn't being updated when there were no files left. 2023-05-21 20:53:03 +04:00
c53dab9e6b
Oops, that site argument isn't needed. 2023-05-21 20:25:26 +04:00
15dd184106
Added deleted page to show if file was successfully deleted. 2023-05-21 20:21:41 +04:00
cb1dd51750
Let's reorginise endpoints a little. 2023-05-21 20:14:01 +04:00
d7191d0fbd
Added /favicon.svg endpoint. 2023-05-21 20:13:15 +04:00
32 changed files with 412 additions and 748 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ bin/*
!bin/.keep
.vscode
*.jade.go
jade.go

View File

@ -5,50 +5,44 @@ SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
SYSDDIR=${SYSDDIR_:/%=%}
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
all: ${TARGET}
all: web/*.jade.go ${TARGET}
.PHONY: ${TARGET}
${TARGET}:
go generate web/web.go
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
install-jade:
web/*.jade.go: web/templates/*.jade
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 -Dm 0755 bin/${TARGET} ${DESTDIR}usr/bin/${TARGET}
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 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.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
rm ${DESTDIR}usr/bin/${TARGET}-gen-salt
rm ${DESTDIR}etc/logrotate.d/${TARGET}
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
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.19.0
pkgver=23.34.0
pkgrel=1
pkgdesc="Arav's Dwelling / Upload"
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
@ -9,19 +9,16 @@ license=('MIT')
makedepends=('go>=1.17')
provides=('dwelling-upload')
conflicts=('dwelling-upload')
backup=('etc/dwelling/upload.yaml')
source=("https://git.arav.su/Arav/dwelling-upload/archive/v${pkgver}.tar.gz")
md5sums=('SKIP')
build() {
cd "$srcdir/$pkgname"
export GOPATH="$srcdir"/gopath
if [ ! -f "$(go env GOPATH)/bin/jade" ]; then
make DESTDIR="$pkgdir/" install-jade
fi
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

@ -1,70 +1,48 @@
package main
import (
"dwelling-upload/internal/configuration"
"dwelling-upload/pkg/logging"
"dwelling-upload/pkg/utils"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"time"
)
var configPath *string = flag.String("conf", "config.yaml", "path to configuration file")
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
func main() {
flag.Parse()
log.SetFlags(log.Llongfile)
if *showVersion {
fmt.Println("dwelling-upload-clean Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
return
}
config, err := configuration.LoadConfiguration(*configPath)
uploadsDir, err := os.ReadDir(*uploadDir)
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 {
if time.Now().UTC().Sub(entry.ModTime().UTC()) >= time.Duration(config.Uploads.Limits.KeepForHours)*time.Hour {
if err := os.Remove(path.Join(config.Uploads.Directory, entry.Name())); err != nil {
logErr.Println("failed to remove file ", entry.Name(), ": ", err)
} else {
deletedSize += entry.Size()
deletedCount++
}
}
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
}
_, _, cFSz := utils.ConvertFileSize(deletedSize)
if deletedCount > 0 {
logClean.Printf("%d %s", deletedCount, cFSz)
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)
}
}
}
}

View File

@ -1,129 +1,120 @@
package main
import (
"dwelling-upload/internal/configuration"
"dwelling-upload/internal/http"
"dwelling-upload/pkg/logging"
duihttp "dwelling-upload/internal/http"
"dwelling-upload/pkg/utils"
"dwelling-upload/pkg/watcher"
"dwelling-upload/web"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"path"
"syscall"
"git.arav.su/Arav/httpr"
)
var configPath *string = flag.String("conf", "config.yaml", "path to configuration file")
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
func main() {
flag.Parse()
log.SetFlags(log.Llongfile)
if *showVersion {
fmt.Println("dwelling-upload Ver. ", version, "\nCopyright (c) 2022,2023 Alexander \"Arav\" Andreev <me@arav.su>")
return
}
config, err := configuration.LoadConfiguration(*configPath)
hashSalt, err := os.ReadFile(path.Join(os.Getenv("CREDENTIALS_DIRECTORY"), "salt"))
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() {
if typ, addr := config.SplitNetworkAddress(); typ == "unix" {
os.Remove(addr)
if err := srv.Stop(); err != nil {
log.Fatalln("failed to properly shutdown a server:", err)
}
}()
logErr, err := logging.New(config.Log.Error)
if err != nil {
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()
doneSignal := make(chan os.Signal, 1)
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
watcha, err := watcher.NewInotifyWatcher()
if err != nil {
logErr.Fatalln(err)
log.Fatalln(err)
}
defer watcha.Close()
if err := watcha.AddWatch(config.Uploads.Directory, watcher.CrDelMask); err != nil {
logErr.Fatalln(err)
if err := watcha.AddWatch(*uploadDir, watcher.CrDelMask); err != nil {
log.Fatalln(err)
}
uploadDirNotify := make(chan uint32)
uploadDirSize, err := utils.DirectorySize(config.Uploads.Directory)
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 watcha.WatchForMask(uploadDirNotify, watcher.CrDelMask)
go func() {
for {
select {
case <-logReopenSignal:
logErr.Reopen(config.Log.Error)
logUpload.Reopen(config.Log.Upload)
logDownload.Reopen(config.Log.Download)
case <-uploadDirNotify:
sz, err := utils.DirectorySize(config.Uploads.Directory)
uploadDirSize, err = utils.DirectorySize(*uploadDir)
if err != nil {
logErr.Println("failed to get uploads directory size:", err)
}
if sz > 0 {
uploadDirSize = sz
log.Println("failed to get uploads directory size:", err)
}
}
}
}()
<-doneSignal
if err := srv.Stop(); err != nil {
logErr.Fatalln("failed to properly shutdown a server:", err)
}
}

View File

@ -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

View File

@ -1,15 +1,12 @@
/var/log/dwelling-upload/*log {
nocreate
copytruncate
size 1M
compress
compresscmd /usr/bin/zstd
compressext .zst
compressoptions -T0 --long -15
uncompresscmd /usr/bin/unzstd
sharedscripts
missingok
notifempty
postrotate
/bin/pkill -HUP dwelling-upload
endscript
}

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 / {

8
go.mod
View File

@ -2,8 +2,6 @@ module dwelling-upload
go 1.17
require (
github.com/julienschmidt/httprouter v1.3.0
github.com/pkg/errors v0.9.1
gopkg.in/yaml.v3 v3.0.1
)
require github.com/pkg/errors v0.9.1
require git.arav.su/Arav/httpr v0.3.1

8
go.sum
View File

@ -1,8 +1,4 @@
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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=
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=

View File

@ -3,9 +3,8 @@ Description=dwelling-upload-clean
[Service]
Type=oneshot
User=dwupload
Group=dwupload
ExecStart=/usr/bin/dwelling-upload-clean -conf /etc/dwelling/upload.yaml
DynamicUser=yes
ExecStart=/usr/bin/dwelling-upload-clean -dir /srv/upload -expiry 36h
ReadOnlyPaths=/
# Set here path to directory where uploads are stored.
@ -13,8 +12,6 @@ ReadWritePaths=/srv/upload
NoExecPaths=/
ExecPaths=/usr/bin/dwelling-upload-clean
LogsDirectory=dwelling-upload
AmbientCapabilities=
CapabilityBoundingSet=
@ -22,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,9 +5,9 @@ After=network.target
[Service]
Type=simple
Restart=on-failure
User=dwupload
Group=dwupload
ExecStart=/usr/bin/dwelling-upload -conf /etc/dwelling/upload.yaml
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.
@ -18,6 +18,9 @@ ExecPaths=/usr/bin/dwelling-upload
RuntimeDirectory=dwelling-upload
LogsDirectory=dwelling-upload
# Use gen-salt.sh to generate salt! It will create / append to an override.conf.
SetCredentialEncrypted=
AmbientCapabilities=
CapabilityBoundingSet=
@ -25,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

@ -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]
}

View File

@ -2,111 +2,109 @@ package http
import (
"crypto/sha256"
"dwelling-upload/internal/configuration"
"dwelling-upload/pkg/logging"
"dwelling-upload/pkg/utils"
"dwelling-upload/web"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"
"git.arav.su/Arav/httpr"
)
type UploadHandlers struct {
conf *configuration.Configuration
logErr *logging.Logger
logUpload *logging.Logger
logDownload *logging.Logger
logDelete *logging.Logger
logFile *log.Logger
uploadDir string
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{
conf: conf,
logErr: lErr,
logUpload: lUp,
logDownload: lDown,
logDelete: lDel,
uploadDirSize: uploadDirSize}
}
func (*UploadHandlers) AssetsFS() http.FileSystem {
return web.Assets()
logFile: lFile,
uploadDir: uploadDir,
uploadDirSize: uploadDirSize,
hashSalt: hashSalt,
keepForHours: keepForHours,
limitStorage: limStorage,
limitFileSize: limFileSz}
}
func (h *UploadHandlers) Index(w http.ResponseWriter, r *http.Request) {
var storCapacity int64 = h.conf.Uploads.Limits.Storage << 20
var fMaxSize int64 = h.conf.Uploads.Limits.FileSize << 20
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.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) {
var fMaxSizeBytes int64 = h.conf.Uploads.Limits.FileSize << 20
var storCapacity int64 = h.conf.Uploads.Limits.Storage << 20
var fMaxSizeBytes int64 = h.limitFileSize << 20
var storCapacity int64 = h.limitStorage << 20
r.Body = http.MaxBytesReader(w, r.Body, fMaxSizeBytes)
if err := r.ParseMultipartForm(fMaxSizeBytes); err != nil {
h.logErr.Println("failed to parse form:", err)
http.Error(w, err.Error(), http.StatusExpectationFailed)
log.Println("failed to parse upload form:", err)
Error(w, r, "Failed to parse upload form.", http.StatusExpectationFailed)
return
}
f, fHandler, err := r.FormFile("file")
if err != nil {
h.logErr.Println("failed to open incoming file:", err)
w.WriteHeader(http.StatusInternalServerError)
http.Error(w, "cannot read incoming file", http.StatusInternalServerError)
log.Println("failed to open incoming file:", err)
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 {
h.logErr.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
w.WriteHeader(http.StatusInternalServerError)
web.ErrorNoSpace(utils.MainSite(r.Host), w)
log.Println("not enough space left in storage, only", leftSpace>>20, "MiB left")
Error(w, r, "Not enough space left, sorry.", http.StatusInternalServerError)
return
}
s256 := sha256.New()
if _, err := io.Copy(s256, f); err != nil {
h.logErr.Println("failed to compute SHA-256 hash:", err)
http.Error(w, "cannot compute hash for a file", http.StatusInternalServerError)
log.Println("failed to compute a SHA-256 hash:", err)
Error(w, r, "A hash for the file cannot be computed.", http.StatusInternalServerError)
return
}
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))
f.Seek(0, io.SeekStart)
fPath := path.Join(h.conf.Uploads.Directory, fSaltedHash)
fPath := path.Join(h.uploadDir, fSaltedHash)
_, err = os.Stat(fPath)
if os.IsNotExist(err) {
fDst, err := os.Create(fPath)
if err != nil {
h.logErr.Println("failed to open file for writing", err)
http.Error(w, "cannot create your file", http.StatusInternalServerError)
log.Println("failed to open file for writing", err)
Error(w, r, "File cannot be written.", http.StatusInternalServerError)
return
}
defer fDst.Close()
@ -119,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 {
h.logErr.Println("failed to copy uploaded file to destination:", err)
http.Error(w, "cannot copy file's content", http.StatusInternalServerError)
if _, err = io.Copy(fDst, f); err != nil {
log.Println("failed to copy uploaded file to destination:", err)
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.logUpload.Printf("| %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 {
@ -137,46 +138,50 @@ func (h *UploadHandlers) Upload(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusFound)
}
downloadURL := path.Join("/f", fSaltedHash, fHandler.Filename)
downloadURL := path.Join("/", fSaltedHash, fHandler.Filename)
downloadURLParsed, _ := url.Parse(downloadURL)
_, 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, site+downloadURLParsed.String(), "will be kept for", h.keepForHours)
} 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) {
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)
if os.IsNotExist(err) {
NotFound(w, r)
Error(w, r, "", http.StatusNotFound)
return
}
name := GetURLParam(r, "name")
name := httpr.Param(r, "name")
w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", name))
fd, err := os.Open(path)
if err != nil {
h.logErr.Println("failed to open file to read:", err)
w.WriteHeader(http.StatusInternalServerError)
log.Println("failed to open file to read:", err)
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.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)
}
@ -184,44 +189,57 @@ func (h *UploadHandlers) Download(w http.ResponseWriter, r *http.Request) {
func (h *UploadHandlers) Delete(w http.ResponseWriter, r *http.Request) {
var saltedHash string
if r.Method == "DELETE" {
saltedHash = GetURLParam(r, "hash")
saltedHash = httpr.Param(r, "hash")
} else {
r.ParseForm()
saltedHash = r.FormValue("hash")
}
path := path.Join(h.conf.Uploads.Directory, saltedHash)
path := path.Join(h.uploadDir, saltedHash)
_, err := os.Stat(path)
if os.IsNotExist(err) {
NotFound(w, r)
if _, err := os.Stat(path); os.IsNotExist(err) {
Error(w, r, "", http.StatusNotFound)
return
}
err = os.Remove(path)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, err)
if err := os.Remove(path); err != nil {
log.Println("failed to remove a file:", err)
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.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) {
w.WriteHeader(http.StatusNotFound)
if !strings.Contains(r.UserAgent(), "curl") {
web.Error404(utils.MainSite(r.Host), w)
} else {
fmt.Fprintln(w, "file not found")
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 RobotsTxt(w http.ResponseWriter, r *http.Request) {
data, _ := web.AssetsGetFile("robots.txt")
w.Write(data)
}
func Favicon(w http.ResponseWriter, r *http.Request) {
data, _ := web.AssetsGetFile("img/favicon.svg")
w.Write(data)
}

View File

@ -5,55 +5,41 @@ import (
"log"
"net"
"net/http"
"net/netip"
"os"
"strings"
"time"
"github.com/julienschmidt/httprouter"
)
type HttpServer struct {
server *http.Server
router *httprouter.Router
s http.Server
addr net.Addr
}
func NewHttpServer() *HttpServer {
r := httprouter.New()
return &HttpServer{
server: &http.Server{
func NewHttpServer(r http.Handler) *HttpServer {
return &HttpServer{s: http.Server{
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
Handler: r,
},
router: r,
Handler: r}}
}
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
}
}
func (s *HttpServer) GET(path string, handler http.HandlerFunc) {
s.router.Handler(http.MethodGet, path, handler)
}
if ap.Addr().Is4() {
network = "tcp4"
} else if ap.Addr().Is6() {
network = "tcp6"
}
}
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)
if err != nil {
return err
@ -63,8 +49,10 @@ func (s *HttpServer) Start(network, address string) error {
os.Chmod(address, 0777)
}
s.addr = listener.Addr()
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)
}
}()
@ -76,7 +64,11 @@ func (s *HttpServer) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
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
}

View File

@ -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()
}

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 {
func DirectorySize(dirPath string) (dirSz int64, err error) {
dir, err := os.ReadDir(dirPath)
if err != nil {
return errors.Wrapf(err, "failed to compute %s directory size", path)
return 0, errors.Wrapf(err, "failed to compute %s directory size", dirPath)
}
dirSz += info.Size()
return nil
})
for _, entry := range dir {
file, err := os.Stat(path.Join(dirPath, entry.Name()))
if err != nil {
return 0, err
return 0, errors.Wrapf(err, "failed to stat a file %s", entry.Name())
}
dirSz += file.Size()
}
return dirSz - 4096, nil
return dirSz, nil
}

View File

@ -54,7 +54,6 @@ 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)
@ -73,7 +72,6 @@ func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) {
}
}
}
}()
}
// Close removes all watchers, closes inotify descriptor and stops all

26
tools/gen-salt.sh Executable file
View 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

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

@ -1,141 +0,0 @@
// Code generated by "jade.go"; DO NOT EDIT.
package web
import (
"bytes"
"io"
"strconv"
)
var (
escaped = []byte{'<', '>', '"', '\'', '&'}
replacing = []string{"&lt;", "&gt;", "&#34;", "&#39;", "&amp;"}
)
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")
}

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

@ -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

View File

@ -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

View 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

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 Content you upload should comply with 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
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.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 /f/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,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

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

@ -6,10 +6,10 @@ import (
"net/http"
)
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/index.jade
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -writer templates/nospace.jade
//go:generate $GOPATH/bin/jade -pkg=web -stdbuf -stdlib -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/index.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
//go:embed assets
var assetsDir embed.FS