first commit

This commit is contained in:
2025-08-26 21:54:30 +02:00
commit 4478d70d15
21 changed files with 1617 additions and 0 deletions

97
config/certs.go Normal file
View File

@@ -0,0 +1,97 @@
package config
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"os"
"time"
)
var selfSignedTLSCertificate []byte = []byte{}
var selfSignedTLSKey []byte = []byte{}
func GetTLSCertificate() []byte {
return selfSignedTLSCertificate
}
func GetTLSKey() []byte {
return selfSignedTLSKey
}
func generateSelfSignedTLSCertificate() error {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return err
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "ablage",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return err
}
cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
key, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: key})
selfSignedTLSCertificate = cert
selfSignedTLSKey = keyPEM
return nil
}
func loadOrGenerateTLSCertificate() error {
if GetHttpMode() {
return nil
}
if pathTLSCertFile == "" || pathTLSKeyFile == "" {
return generateSelfSignedTLSCertificate()
}
_, err := tls.LoadX509KeyPair(pathTLSCertFile, pathTLSKeyFile)
if err != nil {
return fmt.Errorf("Error: Failed to load TLS certificate or key: %w", err)
}
certData, err := os.ReadFile(pathTLSCertFile)
if err != nil {
return fmt.Errorf("Error: Failed to read TLS certificate file: %w", err)
}
keyData, err := os.ReadFile(pathTLSKeyFile)
if err != nil {
return fmt.Errorf("Error: Failed to read TLS key file: %w", err)
}
selfSignedTLSCertificate = certData
selfSignedTLSKey = keyData
return nil
}

68
config/config.go Normal file
View File

@@ -0,0 +1,68 @@
package config
import (
"fmt"
"os"
)
const DefaultBasicAuthUsername string = "ablage"
const DefaultNameDataFolder string = "data"
const DefaultNameUploadFolder string = "upload"
const DefaultPortToListenOn int = 13692
const LengthOfRandomBasicAuthPassword int = 16
const VersionString string = "1.0"
var randomBasicAuthPassword string = generateRandomPassword()
func Init() {
err := gatherDefaultPaths()
if err != nil {
panic(err)
}
parseFlags()
if GetReadonlyMode() && GetSinkholeMode() {
fmt.Println("Error: Cannot enable both readonly and sinkhole modes at the same time.")
os.Exit(1)
}
err = loadOrGenerateTLSCertificate()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
func PrintStartupBanner() {
fmt.Println("****************************************")
fmt.Println("* Ablage *")
fmt.Println("****************************************")
fmt.Printf("Version : %s\n", VersionString)
fmt.Printf("Basic Auth mode: %v\n", GetBasicAuthMode())
fmt.Printf("HTTP mode : %v\n", GetHttpMode())
fmt.Printf("Readonly mode : %v\n", GetReadonlyMode())
fmt.Printf("Sinkhole mode : %v\n", GetSinkholeMode())
fmt.Printf("Path : %s\n", GetPathDataFolder())
if GetBasicAuthMode() {
fmt.Printf("Username : %s\n", GetBasicAuthUsername())
fmt.Printf("Password : %s\n", GetBasicAuthPassword())
}
if GetHttpMode() {
fmt.Printf("Listening on : http://0.0.0.0:%d\n", GetPortToListenOn())
} else {
if pathTLSCertFile == "" || pathTLSKeyFile == "" {
fmt.Printf("TLS cert : self-signed\n")
fmt.Printf("TLS key : self-signed\n")
} else {
fmt.Printf("TLS cert : %s\n", pathTLSCertFile)
fmt.Printf("TLS key : %s\n", pathTLSKeyFile)
}
fmt.Printf("Listening on : https://0.0.0.0:%d\n", GetPortToListenOn())
}
fmt.Println("")
}

22
config/filesystem.go Normal file
View File

@@ -0,0 +1,22 @@
package config
import (
"fmt"
"os"
"path/filepath"
)
var defaultPathDataFolder string = ""
var defaultPathUploadFolder string = ""
func gatherDefaultPaths() error {
execPath, err := os.Executable()
if err != nil {
return fmt.Errorf("[Error] Could not determine binary path: %v", err)
}
defaultPathDataFolder = filepath.Join(filepath.Dir(execPath), DefaultNameDataFolder)
defaultPathUploadFolder = filepath.Join(defaultPathDataFolder, DefaultNameUploadFolder)
return nil
}

165
config/flags.go Normal file
View File

@@ -0,0 +1,165 @@
package config
import (
"crypto/rand"
"encoding/base64"
"flag"
"fmt"
"os"
"path/filepath"
)
var basicAuthMode bool = false
var basicAuthPassword string = ""
var httpMode bool = false
var pathDataFolder string = ""
var pathTLSCertFile string = ""
var pathTLSKeyFile string = ""
var pathUploadFolder string = ""
var portToListenOn int = DefaultPortToListenOn
var readonlyMode bool = false
var sinkholeMode bool = false
func GetBasicAuthMode() bool {
return basicAuthMode
}
func GetBasicAuthPassword() string {
return basicAuthPassword
}
func GetBasicAuthUsername() string {
return DefaultBasicAuthUsername
}
func GetHttpMode() bool {
return httpMode
}
func GetPathDataFolder() string {
return pathDataFolder
}
func GetPathTLSCertFile() string {
return pathTLSCertFile
}
func GetPathTLSKeyFile() string {
return pathTLSKeyFile
}
func GetPathUploadFolder() string {
return pathUploadFolder
}
func GetPortToListenOn() int {
return portToListenOn
}
func GetReadonlyMode() bool {
return readonlyMode
}
func GetSinkholeMode() bool {
return sinkholeMode
}
func generateRandomPassword() string {
b := make([]byte, LengthOfRandomBasicAuthPassword)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b)[:LengthOfRandomBasicAuthPassword]
}
func parseFlags() {
flag.BoolVar(&basicAuthMode, "auth", false, "Enable basic authentication.")
flag.BoolVar(&httpMode, "http", false, "Enable http mode. Nothing will be encrypted.")
flag.BoolVar(&readonlyMode, "readonly", false, "Enable readonly mode. No files can be uploaded or deleted.")
flag.BoolVar(&sinkholeMode, "sinkhole", false, "Enable sinkhole mode. Existing files won't be visible.")
flag.IntVar(&portToListenOn, "port", DefaultPortToListenOn, "Set Port to listen on.")
flag.StringVar(&basicAuthPassword, "password", "", "Set password for basic authentication (or let ablage generate a random one).")
flag.StringVar(&pathDataFolder, "path", "", "Set path to data folder (default is 'data' in the same directory as ablage).")
flag.StringVar(&pathTLSCertFile, "cert", "", "TLS cert file")
flag.StringVar(&pathTLSKeyFile, "key", "", "TLS key file")
flag.Parse()
parseFlagValueBasicAuthPassword()
parseFlagValuePortToListenOn()
parseFlagValuePathDataFolder()
parseFlagValuePathTLSCertFile()
parseFlagValuePathTLSKeyFile()
}
func parseFlagValueBasicAuthPassword() {
if len(basicAuthPassword) < 1 || len(basicAuthPassword) > 128 {
basicAuthPassword = generateRandomPassword()
}
}
func parseFlagValuePathDataFolder() {
if pathDataFolder == "" {
pathDataFolder = defaultPathDataFolder
pathUploadFolder = defaultPathUploadFolder
return
}
info, err := os.Stat(pathDataFolder)
if err != nil {
pathDataFolder = defaultPathDataFolder
pathUploadFolder = defaultPathUploadFolder
return
}
if !info.IsDir() {
pathDataFolder = defaultPathDataFolder
pathUploadFolder = defaultPathUploadFolder
return
}
pathUploadFolder = filepath.Join(pathDataFolder, DefaultNameUploadFolder)
}
func parseFlagValuePortToListenOn() {
if portToListenOn < 1 || portToListenOn > 65535 {
portToListenOn = DefaultPortToListenOn
}
}
func parseFlagValuePathTLSCertFile() {
if pathTLSCertFile == "" {
pathTLSKeyFile = ""
return
}
info, err := os.Stat(pathTLSCertFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Failed to read cert: %v\n", err)
os.Exit(1)
}
if info.IsDir() {
fmt.Fprintf(os.Stderr, "Error: Cert must be a file\n")
os.Exit(1)
}
}
func parseFlagValuePathTLSKeyFile() {
if pathTLSKeyFile == "" {
pathTLSCertFile = ""
return
}
info, err := os.Stat(pathTLSKeyFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: Failed to read key: %v\n", err)
os.Exit(1)
}
if info.IsDir() {
fmt.Fprintf(os.Stderr, "Error: Key must be a file\n")
os.Exit(1)
}
}