Initial commit.
This commit is contained in:
commit
d4852ee6dd
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2021,2022 Alexander "Arav" Andreev <me@arav.top>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice, this permission notice and the word "NIGGER" shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
46
Makefile
Executable file
46
Makefile
Executable file
@ -0,0 +1,46 @@
|
||||
TARGET=dwelling-upload
|
||||
|
||||
SYSCTL=${shell which systemctl}
|
||||
SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir}
|
||||
SYSDDIR=${SYSDDIR_:/%=%}
|
||||
DESTDIR=/
|
||||
|
||||
LDFLAGS=-ldflags "-s -w"
|
||||
|
||||
SOURCES := ${wildcard *.go}
|
||||
|
||||
all: ${TARGET}
|
||||
|
||||
.PHONY: ${TARGET}
|
||||
|
||||
${TARGET}:
|
||||
go build -o bin/$@ ${LDFLAGS} cmd/$@/main.go
|
||||
go build -o bin/$@-clear ${LDFLAGS} cmd/$@-clear/main.go
|
||||
|
||||
run:
|
||||
bin/${TARGET} -config configs/config.yaml
|
||||
|
||||
install:
|
||||
install -Dm 0755 ${TARGET} ${DESTDIR}usr/bin/${TARGET}
|
||||
install -Dm 0755 ${TARGET}-clean ${DESTDIR}usr/bin/${TARGET}-clean
|
||||
install -Dm 0644 LICENSE ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE
|
||||
|
||||
install -Dm 0644 init/systemd/${TARGET}.service ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
install -Dm 0644 init/systemd/${TARGET}-clear.timer ${DESTDIR}${SYSDDIR}/${TARGET}-clear.timer
|
||||
install -Dm 0644 init/systemd/${TARGET}-clear.service ${DESTDIR}${SYSDDIR}/${TARGET}-clear.service
|
||||
|
||||
uninstall:
|
||||
rm ${DESTDIR}usr/bin/${TARGET}
|
||||
rm ${DESTDIR}usr/bin/${TARGET}-clear
|
||||
rm ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE
|
||||
|
||||
${SYSCTL} stop ${TARGET}.service
|
||||
${SYSCTL} stop ${TARGET}-clear.timer
|
||||
${SYSCTL} disable ${TARGET}.service
|
||||
${SYSCTL} disable ${TARGET}.timer
|
||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}.service
|
||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clear.timer
|
||||
rm ${DESTDIR}${SYSDDIR}/${TARGET}-clear.service
|
||||
|
||||
clean:
|
||||
go clean
|
41
cmd/dwelling-upload-clear/main.go
Normal file
41
cmd/dwelling-upload-clear/main.go
Normal file
@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dwelling-upload/internal/configuration"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var configPath string = *flag.String("conf", "config.yaml", "path to configuration file")
|
||||
|
||||
func main() {
|
||||
config, err := configuration.LoadConfiguration(configPath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
_ = config
|
||||
|
||||
uploads_dir, err := ioutil.ReadDir(config.Uploads.Directory)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open directory %s: %s\n", config.Uploads.Directory, err)
|
||||
}
|
||||
|
||||
var deleted_count int64 = 0
|
||||
var deteted_size int64 = 0
|
||||
|
||||
for _, entry := range uploads_dir {
|
||||
if time.Duration(entry.ModTime().UTC().Sub(time.Now().UTC()).Hours()) >= 24*time.Hour {
|
||||
if err := os.Remove(path.Join(config.Uploads.Directory, entry.Name())); err != nil {
|
||||
log.Fatalln("failed to remove file ", entry.Name(), ": ", err)
|
||||
}
|
||||
deteted_size += entry.Size()
|
||||
deleted_count++
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(deleted_count, " file(s) in total of ", deteted_size/1024/1024, " MiB was removed during this run.")
|
||||
}
|
35
cmd/dwelling-upload/main.go
Normal file
35
cmd/dwelling-upload/main.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"dwelling-upload/internal/configuration"
|
||||
"dwelling-upload/pkg/server"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var configPath string = *flag.String("conf", "config.yaml", "path to configuration file")
|
||||
|
||||
func main() {
|
||||
config, err := configuration.LoadConfiguration(configPath)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
srv := server.NewHttpServer()
|
||||
|
||||
if err := srv.Start(config.SplitNetworkAddress()); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
doneSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(doneSignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
<-doneSignal
|
||||
|
||||
if err := srv.Stop(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
7
configs/config.yaml
Normal file
7
configs/config.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
listen_on: "tcp :29101"
|
||||
uploads:
|
||||
directory: "/srv/uploads"
|
||||
limits:
|
||||
file_size: "128M"
|
||||
keep_for_hours: 48
|
||||
storage: "200G"
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
||||
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.0-20210107192922-496545a6307b
|
||||
)
|
9
init/systemd/dwelling-upload-clear.service
Executable file
9
init/systemd/dwelling-upload-clear.service
Executable file
@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=dwelling-upload-clear
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/dwelling-upload-clear -config /etc/dwelling-upload/config.yaml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
10
init/systemd/dwelling-upload-clear.timer
Executable file
10
init/systemd/dwelling-upload-clear.timer
Executable file
@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=dwelling-upload-clear
|
||||
|
||||
[Timer]
|
||||
OnCalendar=hourly
|
||||
AccuracySec=1h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
11
init/systemd/dwelling-upload.service
Executable file
11
init/systemd/dwelling-upload.service
Executable file
@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=dwelling-upload
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
ExecStart=/usr/bin/dwelling-upload -config /etc/dwelling-upload/config.yaml
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
43
internal/configuration/configuration.go
Normal file
43
internal/configuration/configuration.go
Normal file
@ -0,0 +1,43 @@
|
||||
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"`
|
||||
Uploads struct {
|
||||
Directory string `yaml:"directory"`
|
||||
Limits struct {
|
||||
FileSize string `yaml:"file_size"`
|
||||
KeepForHours int `yaml:"keep_for_hours"`
|
||||
Storage string `yaml:"storage"`
|
||||
} `yaml:"limits"`
|
||||
} `yaml:"uploads"`
|
||||
}
|
||||
|
||||
func LoadConfiguration(path string) (*Configuration, error) {
|
||||
config_file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open configuration file")
|
||||
}
|
||||
|
||||
config := &Configuration{}
|
||||
|
||||
if err := yaml.NewDecoder(config_file).Decode(config); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode configuration file")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Configuration) SplitNetworkAddress() (n string, a string) {
|
||||
s := strings.Split(c.ListenOn, " ")
|
||||
n, a = s[0], s[1]
|
||||
return n, a
|
||||
}
|
97
pkg/server/http.go
Normal file
97
pkg/server/http.go
Normal file
@ -0,0 +1,97 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type HttpServer struct {
|
||||
server *http.Server
|
||||
router *httprouter.Router
|
||||
}
|
||||
|
||||
func NewHttpServer() *HttpServer {
|
||||
r := httprouter.New()
|
||||
return &HttpServer{
|
||||
server: &http.Server{
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
Handler: r,
|
||||
},
|
||||
router: r,
|
||||
}
|
||||
}
|
||||
|
||||
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) PATCH(path string, handler http.HandlerFunc) {
|
||||
s.router.Handler(http.MethodPatch, path, handler)
|
||||
}
|
||||
|
||||
func (s *HttpServer) PUT(path string, handler http.HandlerFunc) {
|
||||
s.router.Handler(http.MethodPut, path, handler)
|
||||
}
|
||||
|
||||
func (s *HttpServer) DELETE(path string, handler http.HandlerFunc) {
|
||||
s.router.Handler(http.MethodDelete, path, handler)
|
||||
}
|
||||
|
||||
// GetURLParams wrapper around underlying router for getting URL parameters.
|
||||
func GetURLParams(r *http.Request) httprouter.Params {
|
||||
return httprouter.ParamsFromContext(r.Context())
|
||||
}
|
||||
|
||||
func (s *HttpServer) Start(network, address string) error {
|
||||
listener, err := net.Listen(network, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err = s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HttpServer) Stop() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := s.server.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthWithToken middleware that authenticates user by token (that is effectively just a password)
|
||||
// supplied in an 'Authentication-Token' HTTP header.
|
||||
func AuthWithToken(handler http.HandlerFunc, token string) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authentication-Token")), []byte(token)) == 1 {
|
||||
handler(rw, r)
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
rw.Header().Add("Content-Type", "application/json")
|
||||
response := make(map[string]string)
|
||||
response["message"] = "Unauthorized"
|
||||
json.NewEncoder(rw).Encode(response)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user