1
0
Fork 0

Compare commits

...

141 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
Alexander Andreev 30513b3300
Removed go:generate for error404.jade. 2023-05-26 04:40:58 +04:00
Alexander Andreev b695d8128d
Removed disallow of /f/ from robots.txt. 2023-05-26 04:38:14 +04:00
Alexander Andreev 85f853f48e
Removed /f/ path part in upload handler. 2023-05-26 04:37:07 +04:00
Alexander Andreev dd22577521
Removed /f/ path part at index.jade. 2023-05-26 04:36:36 +04:00
Alexander Andreev 1e4836ca86
Made errorMsg into p, so it get to the next line. 2023-05-26 04:36:07 +04:00
Alexander Andreev e47e592b01
julienschmidt/httprouter was removed and my httpr added to go.mod and go.sum. 2023-05-26 04:31:05 +04:00
Alexander Andreev 38e6813d11
Made use of my own httpr router in main. 2023-05-26 04:30:29 +04:00
Alexander Andreev b18338e4e1
Made use of my own httpr router. Replace GetURLParam with httpr.Param. 2023-05-26 04:28:38 +04:00
Alexander Andreev 0677147b63
Removed httprouter dependency from HTTP server. 2023-05-26 04:27:21 +04:00
Alexander Andreev a9e230ad61
Removed unneeded error404.jade. 2023-05-26 04:26:27 +04:00
Alexander Andreev a7972b8e13
A custom logger was removed. 2023-05-25 02:56:57 +04:00
Alexander Andreev c5faef303b
In service replaced args for ExecStart= to the new ones. 2023-05-25 02:32:04 +04:00
Alexander Andreev 5101a892de
Made AssetsFS() a standalone func. 2023-05-25 02:31:21 +04:00
Alexander Andreev 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
Alexander Andreev 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
Alexander Andreev fccb81d3a5
Added | separator to the start of all log.Printf() to separate date and action. 2023-05-25 02:24:00 +04:00
Alexander Andreev 3327b30d12
Replaced logging.Logger with std log.Logger. 2023-05-25 02:22:56 +04:00
Alexander Andreev 59544da625
Removed configPath flag from main. 2023-05-25 02:17:27 +04:00
Alexander Andreev 85529890a5
Oh, code for deletion statistics also should be purged. 2023-05-25 02:13:33 +04:00
Alexander Andreev 9510027d05
Replace flags in a clean service with the new ones. 2023-05-25 02:11:20 +04:00
Alexander Andreev 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
Alexander Andreev 203c0158ce
Install gen-salt.sh as dwelling-upload-gen-salt. 2023-05-25 00:58:02 +04:00
Alexander Andreev 282a00c328
Found that daemon-reload needs to be called when manually edit such files. 2023-05-25 00:57:36 +04:00
Alexander Andreev 99eed674fc
Since we now run setup command from script, this caution doesn't needed. 2023-05-25 00:53:35 +04:00
Alexander Andreev 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
Alexander Andreev 705a4ede76
Updated comments in upload.service. 2023-05-25 00:49:30 +04:00
Alexander Andreev dab675474a
Set default NotFound handler. 2023-05-25 00:47:32 +04:00
Alexander Andreev 3f79eb5b08
Added SetCredentialEncrypted= and a comment. 2023-05-25 00:14:24 +04:00
Alexander Andreev f0fc34c8e7
Remove func NotFound. 2023-05-25 00:09:30 +04:00
Alexander Andreev 2ef85c6f29
Handle cURL and Wget clients. 2023-05-25 00:08:52 +04:00
Alexander Andreev b16ec84e86
Added missing return in free space check. 2023-05-24 23:57:02 +04:00
Alexander Andreev f8351b935d
Moved WriteHeader to nospace.jade. 2023-05-24 23:56:12 +04:00
Alexander Andreev 64132ec18f
Removed missed w.WriteHeader(). 2023-05-24 23:52:33 +04:00
Alexander Andreev fb9cee2c0a
A new Error() handler used. 2023-05-24 23:50:49 +04:00
Alexander Andreev 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
Alexander Andreev a04adf7fa1
Replaced http.Error with 50x errors with web.Error50x(). 2023-05-24 23:20:21 +04:00
Alexander Andreev 6e9ad9323c
Added error50x template. 2023-05-24 23:20:05 +04:00
Alexander Andreev 06dfcaac8e
Removed excessive wtih word in index.jade. 2023-05-24 23:09:47 +04:00
Alexander Andreev 35468af206
In web/*.jade.go target perform check for changes in templates. 2023-05-24 23:09:16 +04:00
Alexander Andreev 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
Alexander Andreev 14ec537d82
And, let's change line wrapping here. 2023-05-24 22:49:36 +04:00
Alexander Andreev 14efad4a4a
Rewrote gen-salt.sh in a right way. 2023-05-24 22:48:28 +04:00
Alexander Andreev 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
Alexander Andreev 21d05e7488
Remove *.jade.go as well on clean target in Makefile. 2023-05-24 22:29:40 +04:00
Alexander Andreev 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
Alexander Andreev b95052709a
Removed upload.yaml from Makefile and PKGBUILD. Removed run and run-clean targets. 2023-05-24 22:25:19 +04:00
Alexander Andreev af0492e5a0
Version's week part incremented. 2023-05-24 22:23:05 +04:00
Alexander Andreev 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
Alexander Andreev badeda87ad
No need in gopkg.in/yaml.v3 anymore. 2023-05-24 22:21:08 +04:00
Alexander Andreev cc1cbeffcd
Removed config.yaml. 2023-05-24 22:20:22 +04:00
Alexander Andreev 332e9a1bb3
Removed Configuration. 2023-05-24 22:20:02 +04:00
Alexander Andreev 931f98d12e
Made use of cmd arguments. 2023-05-24 22:19:41 +04:00
Alexander Andreev 11dfbfbc23
Added flags that replace config file. 2023-05-24 22:11:28 +04:00
Alexander Andreev 470bbb04ee
Get file.log path as join of its name and LOGS_DIRECTORY env var. 2023-05-24 22:10:57 +04:00
Alexander Andreev deed268b4f
Get config passed as cmd args instead. 2023-05-24 22:09:41 +04:00
Alexander Andreev 33f69eaf44
Changed error logging for upload-clean as well. 2023-05-24 22:09:04 +04:00
Alexander Andreev 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
Alexander Andreev c43c314a78
Pass listen address as a -listen argument. Use the new code to parse a parameter. 2023-05-24 21:26:48 +04:00
Alexander Andreev ce6d81ad55
Added /f/ to robots.txt. 2023-05-24 02:57:41 +04:00
Alexander Andreev 88ec5e5efe
Added missing io.Writer in a call of web.Deleted. 2023-05-21 22:24:36 +04:00
Alexander Andreev 33ec19647b
Reworked logging. Store all actions in one file.log. 2023-05-21 21:27:44 +04:00
Alexander Andreev 31d2c4fd21
Let's, actually, directly assign to uploadDirSize. 2023-05-21 21:12:23 +04:00
Alexander Andreev 7e1b56bbc5
Version's week part was incremented. 2023-05-21 20:53:24 +04:00
Alexander Andreev 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
Alexander Andreev c53dab9e6b
Oops, that site argument isn't needed. 2023-05-21 20:25:26 +04:00
Alexander Andreev 15dd184106
Added deleted page to show if file was successfully deleted. 2023-05-21 20:21:41 +04:00
Alexander Andreev cb1dd51750
Let's reorginise endpoints a little. 2023-05-21 20:14:01 +04:00
Alexander Andreev d7191d0fbd
Added /favicon.svg endpoint. 2023-05-21 20:13:15 +04:00
32 changed files with 412 additions and 748 deletions

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
bin/*
!bin/.keep
.vscode
*.jade.go
*.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
}
if time.Now().UTC().Sub(file.ModTime().UTC()) >= *expiry {
if err := os.Remove(path.Join(*uploadDir, entry.Name())); err != nil {
log.Printf("failed to remove a file %s: %s", entry.Name(), err)
}
}
}
_, _, cFSz := utils.ConvertFileSize(deletedSize)
if deletedCount > 0 {
logClean.Printf("%d %s", deletedCount, cFSz)
}
}

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{
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
Handler: r,
},
router: r,
func NewHttpServer(r http.Handler) *HttpServer {
return &HttpServer{s: http.Server{
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
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
}
if ap.Addr().Is4() {
network = "tcp4"
} else if ap.Addr().Is6() {
network = "tcp6"
}
}
}
func (s *HttpServer) GET(path string, handler http.HandlerFunc) {
s.router.Handler(http.MethodGet, path, handler)
}
func (s *HttpServer) POST(path string, handler http.HandlerFunc) {
s.router.Handler(http.MethodPost, path, handler)
}
func (s *HttpServer) DELETE(path string, handler http.HandlerFunc) {
s.router.Handler(http.MethodDelete, path, handler)
}
func (s *HttpServer) ServeStatic(path string, fsys http.FileSystem) {
s.router.ServeFiles(path, fsys)
}
func (s *HttpServer) SetNotFoundHandler(handler http.HandlerFunc) {
s.router.NotFound = handler
}
// GetURLParam wrapper around underlying router for getting URL parameters.
func GetURLParam(r *http.Request, param string) string {
return httprouter.ParamsFromContext(r.Context()).ByName(param)
}
func (s *HttpServer) Start(network, address string) error {
listener, err := net.Listen(network, address)
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 {
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

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