1
0

Initial commit.

This commit is contained in:
Alexander Andreev 2022-02-06 02:22:23 +04:00
commit d4852ee6dd
Signed by: Arav
GPG Key ID: 1327FE8A374CC86F
11 changed files with 315 additions and 0 deletions

7
LICENSE Normal file
View 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
View 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

View 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.")
}

View 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
View 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
View 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
)

View 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

View File

@ -0,0 +1,10 @@
[Unit]
Description=dwelling-upload-clear
[Timer]
OnCalendar=hourly
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target

View 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

View 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
View 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)
}
}
}