Made the backup mechanism actually work, made filenames carry across client-server, added extensive testing suite, made the configurations actually work, updated the README, added "Makefiles for dummies" (main.go).
This commit is contained in:
parent
84e3071a25
commit
16a4153a0c
2
Makefile
2
Makefile
|
@ -20,5 +20,7 @@ uninstall:
|
|||
rm -f $(DESTDIR)/bin/burgerbackup-client
|
||||
rm -f $(DESTDIR)/bin/burgerbackup-server
|
||||
|
||||
test:
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)
|
|
@ -0,0 +1,55 @@
|
|||
# Burgerbackup
|
||||
|
||||
## What is this?
|
||||
|
||||
It's a simple backup program that sends files to a server.
|
||||
It does this periodically and is designed to have one daemon attached to each file you want to back up.
|
||||
|
||||
No, you can't back up a directory. You have to back up each file individually. This is so I don't have to implement tarballs.
|
||||
|
||||
It uses AES-256 GCM encryption with a ARGON2ID key derivation function. The key is derived from a password you specify in the config file.
|
||||
|
||||
That's practically the most secure encryption you get without breaking some sort of NSA law. It may already be breaking some sort of NSA law. I don't know. I'm not a lawyer.
|
||||
|
||||
## How does it work?
|
||||
|
||||
1. You run the server on a machine you want to store your backups on.
|
||||
2. You run the client on a machine you want to back up files from.
|
||||
3. The client sends the server an authentication request, and a password hashed with a salt in SCRYPT.
|
||||
4. The client AES encrypts the file and sends it to the server in the same request
|
||||
5. The server verifies the hash with its own password, then decrypts the file it and stores it.
|
||||
6. The client repeats from step 3 after a specified interval.
|
||||
|
||||
## Installing
|
||||
First, have Go installed. Latest version. What are we? Debian?
|
||||
|
||||
Run as root
|
||||
```
|
||||
CDIR=$PWD
|
||||
cd /tmp
|
||||
git clone https://concord.hectabit.org/hectabit/burgerbackup.git
|
||||
cd burgerbackup
|
||||
make install
|
||||
cd $CDIR
|
||||
CDIR=
|
||||
```
|
||||
|
||||
This also puts your environment back to where it was before you started. How nice of me.
|
||||
|
||||
## Compiling
|
||||
Root not required. Just run
|
||||
```
|
||||
git clone https://concord.hectabit.org/hectabit/burgerbackup.git
|
||||
cd burgerbackup
|
||||
make
|
||||
```
|
||||
The binaries will be located in /dist/bin/
|
||||
|
||||
## Configuring
|
||||
|
||||
### Config file
|
||||
The default config file is located at /etc/burgerbackup/(client/server).ini. You can specify a different config file as a command line argument to either program.
|
||||
|
||||
A default config file is provided in (REPO ROOT)/bin/(client/server)/config.ini.example. You can copy this file and modify it to your needs.
|
||||
|
||||
Modifying the config is not optional, unless you want some idiot to send random files to your server.
|
|
@ -0,0 +1,25 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/tests"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestAES(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.TestBbk(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/tests"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestAES(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.TestBbk(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
[config]
|
||||
# Used for authenticating the backup requests. Change to a random password, and keep the same on both the client and server.
|
||||
BACKUP_KEY=supersecretkey
|
||||
BACKUP_KEY = supersecretkey
|
||||
# Used for encrypting the backup files during transfer. Change to a random password, and keep the same on both the client and server.
|
||||
CRYPTO_KEY=supersecretkey
|
||||
CRYPTO_KEY = supersecretkey
|
||||
# How often the client should backup in seconds. Default is 86400 seconds (24 hours).
|
||||
BACKUP_INTERVAL=86400
|
||||
BACKUP_INTERVAL = 86400
|
||||
# The URL of the server to send the backups to.
|
||||
REMOTE_URL=http://example.org:8080
|
||||
REMOTE_URL = http://example.org:8080/api/backup
|
||||
# The file to backup, relative to where the command is run.
|
||||
FILE_LOCATION=/path/to/file
|
||||
FILE_LOCATION = /path/to/file
|
|
@ -6,6 +6,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -37,8 +38,12 @@ func main() {
|
|||
log.Fatalln("[FATAL] File is in quantum uncertainty:", err)
|
||||
}
|
||||
|
||||
viper.SetConfigName(configPath)
|
||||
viper.AddConfigPath("./")
|
||||
viper.SetConfigType("ini")
|
||||
configPathSlice := strings.Split(configPath, "/")
|
||||
configPathNoIni := strings.Split(configPathSlice[len(configPathSlice)-1], ".")[0]
|
||||
viper.SetConfigName(configPathNoIni)
|
||||
configPathNoFile := strings.Join(configPathSlice[:len(configPathSlice)-1], "/") + "/"
|
||||
viper.AddConfigPath(configPathNoFile)
|
||||
viper.AutomaticEnv()
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
|
@ -46,32 +51,43 @@ func main() {
|
|||
log.Fatalln("[FATAL] Error in config file at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
}
|
||||
|
||||
backupKey := viper.GetString("BACKUP_KEY")
|
||||
cryptoKey := viper.GetString("CRYPTO_KEY")
|
||||
backupInterval := viper.GetInt("BACKUP_INTERVAL")
|
||||
fileLocation := viper.GetString("FILE_LOCATION")
|
||||
remoteURL := viper.GetString("REMOTE_URL")
|
||||
backupKey := viper.GetString("config.BACKUP_KEY")
|
||||
cryptoKey := viper.GetString("config.CRYPTO_KEY")
|
||||
backupInterval := viper.GetInt("config.BACKUP_INTERVAL")
|
||||
fileLocation := viper.GetString("config.FILE_LOCATION")
|
||||
remoteURL := viper.GetString("config.REMOTE_URL")
|
||||
|
||||
for {
|
||||
err, errCode := client.PerformBackup(fileLocation, backupKey, cryptoKey, remoteURL)
|
||||
if err != nil {
|
||||
if errCode == 0 {
|
||||
log.Println("[CRITICAL] Unknown in performBackup() file read:", err)
|
||||
} else if errCode == 1 {
|
||||
log.Println("[CRITICAL] Unknown in performBackup() content encryption:", err)
|
||||
} else if errCode == 2 {
|
||||
switch errCode {
|
||||
case 0:
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() file read:", err)
|
||||
case 1:
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() file stat:", err)
|
||||
case 2:
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() content marshal:", err)
|
||||
case 3:
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() content encryption:", err)
|
||||
case 4:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() form writer creation:", err)
|
||||
case 5:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() IO Copying:", err)
|
||||
case 6:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() writer closing:", err)
|
||||
case 7:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() request creation:", err)
|
||||
} else if errCode == 3 {
|
||||
case 8:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() hash creation:", err)
|
||||
} else if errCode == 4 {
|
||||
case 9:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() request execution:", err)
|
||||
} else if errCode == 5 {
|
||||
case 10:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() response read:", err)
|
||||
} else if errCode == 6 {
|
||||
case 11:
|
||||
log.Println("[CRITICAL] Unknown in SendFileToServer() response marshalling:", err)
|
||||
} else if errCode == 7 {
|
||||
case 12:
|
||||
log.Println("[CRITICAL] Server sent message in SendFileToServer():", err)
|
||||
} else {
|
||||
default:
|
||||
log.Println("[CRITICAL] Unknown error in main():", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/tests"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestAES(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.TestBbk(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
[config]
|
||||
# Used for authenticating the backup requests. Change to a random password, and keep the same on both the client and server.
|
||||
BACKUP_KEY=supersecretkey
|
||||
BACKUP_KEY = supersecretkey
|
||||
# Used for encrypting the backup files during transfer. Change to a random password, and keep the same on both the client and server.
|
||||
CRYPTO_KEY=supersecretkey
|
||||
CRYPTO_KEY = supersecretkey
|
||||
# The port burgerbackup runs on. Change to Port 80 if not using a reverse proxy.
|
||||
PORT=8080
|
||||
PORT = 8080
|
||||
# The host burgerbackup runs on. Change to 127.0.0.1 if using a reverse proxy.
|
||||
HOST=0.0.0.0
|
||||
HOST = 0.0.0.0
|
||||
# The folder where the backups are stored, relative to where the command is run.
|
||||
# It is recommended to use an absolute path
|
||||
BACKUP_FOLDER=/path/to/backup/folder
|
||||
BACKUP_FOLDER = /path/to/backup/folder
|
|
@ -2,8 +2,9 @@ package main
|
|||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/lib/common"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -44,8 +46,12 @@ func main() {
|
|||
log.Fatalln("[FATAL] File is in quantum uncertainty:", err)
|
||||
}
|
||||
|
||||
viper.SetConfigName(configPath)
|
||||
viper.AddConfigPath("./")
|
||||
viper.SetConfigType("ini")
|
||||
configPathSlice := strings.Split(configPath, "/")
|
||||
configPathNoIni := strings.Split(configPathSlice[len(configPathSlice)-1], ".")[0]
|
||||
viper.SetConfigName(configPathNoIni)
|
||||
configPathNoFile := strings.Join(configPathSlice[:len(configPathSlice)-1], "/") + "/"
|
||||
viper.AddConfigPath(configPathNoFile)
|
||||
viper.AutomaticEnv()
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
|
@ -53,11 +59,11 @@ func main() {
|
|||
log.Fatalln("[FATAL] Error in config file at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
}
|
||||
|
||||
backupKey := viper.GetString("BACKUP_KEY")
|
||||
cryptoKey := viper.GetString("CRYPTO_KEY")
|
||||
port := viper.GetInt("PORT")
|
||||
host := viper.GetString("HOST")
|
||||
backupFolder := viper.GetString("BACKUP_FOLDER")
|
||||
backupKey := viper.GetString("config.BACKUP_KEY")
|
||||
cryptoKey := viper.GetString("config.CRYPTO_KEY")
|
||||
port := viper.GetInt("config.PORT")
|
||||
host := viper.GetString("config.HOST")
|
||||
backupFolder := viper.GetString("config.BACKUP_FOLDER")
|
||||
|
||||
if host == "" {
|
||||
log.Fatalln("[FATAL] HOST is not set")
|
||||
|
@ -128,7 +134,6 @@ func main() {
|
|||
}
|
||||
return
|
||||
}
|
||||
fileName := "database_" + strconv.FormatInt(time.Now().Unix(), 10) + ".db"
|
||||
encryptedFile, err := file.Open()
|
||||
if err != nil {
|
||||
c.JSON(422, gin.H{"error": "Could not open file", "goErr": err.Error()})
|
||||
|
@ -157,14 +162,52 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
decryptedDataMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(decryptedData, &decryptedDataMap)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-JSONERR", "goErr": err.Error()})
|
||||
log.Println("[ERROR] Error in /api/backup file json:", err)
|
||||
return
|
||||
}
|
||||
|
||||
originalFileName, ok := decryptedDataMap["filename"].(string)
|
||||
if !ok {
|
||||
c.JSON(422, gin.H{"error": "Could not get filename", "goErr": "could not get filename"})
|
||||
return
|
||||
}
|
||||
|
||||
contentBase64, ok := decryptedDataMap["content"].(string)
|
||||
if !ok {
|
||||
c.JSON(422, gin.H{"error": "Could not get content", "goErr": "could not get content"})
|
||||
return
|
||||
}
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(contentBase64)
|
||||
if err != nil {
|
||||
c.JSON(422, gin.H{"error": "Could not decode base64 content", "goErr": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("[INFO] Received backup from", c.ClientIP(), "with filename", originalFileName)
|
||||
fileNameSlice := strings.Split(originalFileName, ".")
|
||||
|
||||
var fileName string
|
||||
if len(fileNameSlice) >= 2 {
|
||||
fileName = fileNameSlice[0] + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "." + fileNameSlice[1]
|
||||
} else if len(fileNameSlice) == 1 {
|
||||
fileName = fileNameSlice[0] + "_" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
} else {
|
||||
fileName = "unnamed-backup_" + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
}
|
||||
|
||||
filePath := backupFolder + "/" + fileName
|
||||
if err := os.WriteFile(filePath, decryptedData, 0644); err != nil {
|
||||
if err := os.WriteFile(filePath, content, 0644); err != nil {
|
||||
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgerbackup and refer to the documentation for more info. Your error code is: UNKNOWN-API-BACKUP-WRITEERR", "goErr": err.Error()})
|
||||
log.Println("[ERROR] Error in /api/backup file write:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("[INFO] Received and decrypted backup from", c.ClientIP())
|
||||
log.Println("[INFO] Successfully saved backup from", c.ClientIP())
|
||||
c.JSON(200, gin.H{"success": "true", "timeTaken": time.Now().Unix(), "goErr": ""})
|
||||
} else {
|
||||
c.JSON(401, gin.H{"error": "Incorrect backup key", "goErr": "incorrect backup key"})
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package client_test
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/tests"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestAES(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.TestBbk(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
|
@ -3,56 +3,96 @@ package client
|
|||
import (
|
||||
"bytes"
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/lib/common"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func PerformBackup(fileLocation string, backupKey string, cryptoKey string, remoteURL string) (error, int) {
|
||||
fileContent, err := os.ReadFile(fileLocation)
|
||||
if err != nil {
|
||||
log.Println("[CRITICAL] Unknown in performBackup() file read:", err)
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() file read:", err)
|
||||
return err, 0
|
||||
}
|
||||
|
||||
cryptoKeyHashed := argon2.IDKey([]byte(cryptoKey), []byte("burgerbackup"), 1, 64*1024, 4, 32)
|
||||
encryptedContent, err := common.EncryptAES(cryptoKeyHashed, fileContent)
|
||||
fileInfo, err := os.Stat(fileLocation)
|
||||
if err != nil {
|
||||
log.Println("[CRITICAL] Unknown in performBack() content encryption", err)
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() file stat:", err)
|
||||
return err, 1
|
||||
}
|
||||
filename := fileInfo.Name()
|
||||
|
||||
content := map[string]interface{}{
|
||||
"filename": filename,
|
||||
"content": base64.StdEncoding.EncodeToString(fileContent),
|
||||
}
|
||||
|
||||
marshaledContent, err := json.Marshal(content)
|
||||
if err != nil {
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() content marshal:", err)
|
||||
return err, 2
|
||||
}
|
||||
|
||||
cryptoKeyHashed := argon2.IDKey([]byte(cryptoKey), []byte("burgerbackup"), 1, 64*1024, 4, 32)
|
||||
encryptedContent, err := common.EncryptAES(cryptoKeyHashed, marshaledContent)
|
||||
if err != nil {
|
||||
log.Println("[CRITICAL] Unknown in PerformBackup() content encryption", err)
|
||||
return err, 3
|
||||
}
|
||||
|
||||
encryptedFile := io.NopCloser(bytes.NewReader(encryptedContent))
|
||||
err, errCode := SendFileToServer(encryptedFile, backupKey, remoteURL)
|
||||
err, errCode := SendFileToServer(encryptedFile, backupKey, remoteURL, filename)
|
||||
if err != nil {
|
||||
return err, errCode + 2
|
||||
return err, errCode + 4
|
||||
}
|
||||
|
||||
log.Println("[INFO] Backup completed at:")
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func SendFileToServer(file io.Reader, backupKey string, remoteURL string) (error, int) {
|
||||
req, err := http.NewRequest("POST", remoteURL, file)
|
||||
func SendFileToServer(file io.Reader, backupKey string, remoteURL string, filename string) (error, int) {
|
||||
sendBody := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(sendBody)
|
||||
part, err := writer.CreateFormFile("file", filename)
|
||||
if err != nil {
|
||||
return err, 0
|
||||
}
|
||||
|
||||
_, err = io.Copy(part, file)
|
||||
if err != nil {
|
||||
return err, 1
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return err, 3
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", remoteURL, sendBody)
|
||||
if err != nil {
|
||||
return err, 4
|
||||
}
|
||||
|
||||
salt, err := common.GenSalt(16)
|
||||
hashedBackupKey, err := common.Hash(backupKey, salt)
|
||||
if err != nil {
|
||||
return err, 1
|
||||
return err, 5
|
||||
}
|
||||
req.Header.Set("Authorization", hashedBackupKey)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("Content-Length", strconv.Itoa(sendBody.Len()))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err, 2
|
||||
return err, 6
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
|
@ -63,13 +103,13 @@ func SendFileToServer(file io.Reader, backupKey string, remoteURL string) (error
|
|||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err, 3
|
||||
return err, 7
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(body, &response)
|
||||
if err != nil {
|
||||
return err, 4
|
||||
return err, 8
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
|
@ -77,7 +117,7 @@ func SendFileToServer(file io.Reader, backupKey string, remoteURL string) (error
|
|||
if !ok {
|
||||
err = "error not sent by server"
|
||||
}
|
||||
return errors.New(err), 5
|
||||
return errors.New(err), 9
|
||||
}
|
||||
|
||||
return nil, -1
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package common_test
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/tests"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestAES(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
t.Logf("[INFO] Starting tests...\n")
|
||||
tests.TestBld(t)
|
||||
tests.TestBbk(t)
|
||||
tests.Cleanup(t)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Hello, fellow programmer! This is a warning script.
|
||||
// It is meant for idiots who don't read the README.md file.
|
||||
// If you are reading this, you are probably one of those idiots.
|
||||
// Please use the Makefile to build burgerbackup.
|
||||
|
||||
// If you are trying to contribute to burgerbackup, then start by reading the README.md file.
|
||||
// That might have a teensy weensy bit of useful information.
|
||||
// ;) Have a great day (unless you are using this script in production).
|
||||
|
||||
func main() {
|
||||
fmt.Println("[WARN] This isn't how you are meant to build burgerbackup. Please use the Makefile.")
|
||||
fmt.Println("[INFO] This script only exists as a warning and so go test has something to run.")
|
||||
fmt.Print("[PROMPT] Would you like me to run the makefile for you? (y/n): ")
|
||||
var input string
|
||||
_, err := fmt.Scanln(&input)
|
||||
if err != nil {
|
||||
fmt.Println("[FATAL] Error reading input: " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
if input == "y" {
|
||||
fmt.Println("[INFO] Running make... (you should use the Makefile, this isn't how you are meant to build burgerbackup)")
|
||||
err := exec.Command("make").Run()
|
||||
if err != nil {
|
||||
fmt.Println("[FATAL] Error running make: " + err.Error())
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Println("[INFO] Shame on you for not using the Makefile.")
|
||||
fmt.Println("[INFO] If you use this idiotic script in production, I will find you. I will capture you. And I will make you use the Makefile.")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[INFO] Shame on you for not using the Makefile.")
|
||||
fmt.Println("[INFO] If you use this idiotic script in production, I will find you. I will capture you. And I will make you use the Makefile.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package tests
|
||||
|
||||
import (
|
||||
"concord.hectabit.org/Hectabit/burgerbackup/lib/common"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAES(t *testing.T) {
|
||||
t.Logf("[INFO] Running test: encryption and decryption\n")
|
||||
|
||||
cryptoKeyHashed := argon2.IDKey([]byte("supersecretkey"), []byte("burgerbackup"), 1, 64*1024, 4, 32)
|
||||
encryptedContent, err := common.EncryptAES(cryptoKeyHashed, []byte("This is a test file for burgerbackup"))
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error encrypting content: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
decryptedContent, err := common.DecryptAES(cryptoKeyHashed, encryptedContent)
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error decrypting content: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
if string(decryptedContent) != "This is a test file for burgerbackup" {
|
||||
t.Logf(string(decryptedContent) + "\n")
|
||||
t.Fatalf("[FATAL] Decrypted content does not match original content\n")
|
||||
}
|
||||
|
||||
t.Logf("[INFO] Test encryption and decryption successful: " + string(decryptedContent) + "\n")
|
||||
}
|
||||
|
||||
func TestBld(t *testing.T) {
|
||||
t.Logf("[INFO] Running test: package build\n")
|
||||
|
||||
goList := exec.Command("go", "list", "-m", "-f", "\"{{.Dir}}\"")
|
||||
directoryBytes, err := goList.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error running go list: " + err.Error() + "\n")
|
||||
}
|
||||
directory := strings.TrimSuffix(strings.TrimPrefix(string(directoryBytes), "\""), "\"\n")
|
||||
t.Logf("[INFO] Located go.mod at: " + directory + "\n")
|
||||
err = os.RemoveAll("/tmp/burgerbackup")
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error removing old build directory: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
output, err := exec.Command("make", "-C", directory, "BUILDDIR=/tmp/burgerbackup").Output()
|
||||
if err != nil {
|
||||
t.Logf("[FATAL] Error building the package: " + err.Error() + "\n")
|
||||
t.Fatalf("[INFO] Make output: " + string(output) + "\n")
|
||||
}
|
||||
|
||||
t.Logf("[INFO] Test package build successful\n")
|
||||
}
|
||||
|
||||
func TestBbk(t *testing.T) {
|
||||
var processes []*exec.Cmd
|
||||
done := make(chan bool)
|
||||
t.Logf("[INFO] Starting requisites for test: server and client backup\n")
|
||||
|
||||
serverIni := "[config]\nPORT = 8080\nHOST = 127.0.0.1\nBACKUP_FOLDER = /tmp/burgerbackup/\nBACKUP_KEY = supersecretkey\nCRYPTO_KEY = supersecretkey"
|
||||
err := os.WriteFile("/tmp/burgerbackup/server.ini", []byte(serverIni), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error writing server.ini: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
clientIni := "[config]\nFILE_LOCATION = /tmp/burgerbackup/testfile.txt\nREMOTE_URL = http://127.0.0.1:8080/api/backup\nBACKUP_KEY = supersecretkey\nCRYPTO_KEY = supersecretkey\nBACKUP_INTERVAL = 1"
|
||||
err = os.WriteFile("/tmp/burgerbackup/client.ini", []byte(clientIni), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error writing client.ini: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
testFile := "This is a test file for burgerbackup"
|
||||
err = os.WriteFile("/tmp/burgerbackup/testfile.txt", []byte(testFile), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error writing testfile: " + err.Error() + "\n")
|
||||
}
|
||||
|
||||
t.Logf("[INFO] Requisites for test: server and client backup have finished\n")
|
||||
t.Logf("[INFO] Running test: server and client backup\n")
|
||||
|
||||
go func() {
|
||||
cmd := exec.Command("/tmp/burgerbackup/bin/server", "/tmp/burgerbackup/server.ini")
|
||||
processes = append(processes, cmd)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error running server: " + err.Error() + "\n")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
cmd := exec.Command("/tmp/burgerbackup/bin/client", "/tmp/burgerbackup/server.ini")
|
||||
processes = append(processes, cmd)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error running client: " + err.Error() + "\n")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(2 * 1000000000)
|
||||
for _, process := range processes {
|
||||
err := process.Process.Kill()
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error killing process: " + err.Error() + "\n")
|
||||
}
|
||||
}
|
||||
t.Logf("[INFO] Server and client killed\n")
|
||||
matches, err := filepath.Glob("/tmp/burgerbackup/testfile_*.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error globbing testfiles: " + err.Error() + "\n")
|
||||
}
|
||||
if len(matches) > 0 {
|
||||
matchesString := ""
|
||||
for _, match := range matches {
|
||||
matchesString += match + ","
|
||||
}
|
||||
t.Logf("[INFO] Testfiles found: " + matchesString + "\n")
|
||||
t.Logf("[INFO] Test server and client backup successful\n")
|
||||
done <- true
|
||||
} else {
|
||||
t.Fatalf("[FATAL] No testfiles found" + "\n")
|
||||
}
|
||||
}()
|
||||
|
||||
finished := <-done
|
||||
if finished {
|
||||
return
|
||||
} else {
|
||||
t.Fatalf("[FATAL] Bit flip detected, please do not expose to cosmic radiation (impossible condition detected)\n")
|
||||
}
|
||||
}
|
||||
|
||||
func Cleanup(t *testing.T) {
|
||||
t.Logf("[INFO] Tests completed successfully, cleaning up...\n")
|
||||
err := os.RemoveAll("/tmp/burgerbackup")
|
||||
if err != nil {
|
||||
t.Fatalf("[FATAL] Error cleaning up: " + err.Error() + "\n")
|
||||
}
|
||||
t.Logf("[INFO] Cleanup successful. Exiting...\n")
|
||||
}
|
Reference in New Issue