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:
Tracker-Friendly 2024-07-13 17:13:57 +01:00
parent 84e3071a25
commit 16a4153a0c
14 changed files with 521 additions and 52 deletions

View File

@ -20,5 +20,7 @@ uninstall:
rm -f $(DESTDIR)/bin/burgerbackup-client
rm -f $(DESTDIR)/bin/burgerbackup-server
test:
clean:
rm -rf $(BUILDDIR)

55
README.md Normal file
View File

@ -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.

25
all_test.go Normal file
View File

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

25
bin/client/all_test.go Normal file
View File

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

View File

@ -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

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

25
bin/server/all_test.go Normal file
View File

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

View File

@ -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

View File

@ -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"})

25
lib/client/all_test.go Normal file
View File

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

View File

@ -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

25
lib/common/all_test.go Normal file
View File

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

43
main.go Normal file
View File

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

145
tests/main.go Normal file
View File

@ -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")
}