Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
proofrock committed Oct 13, 2021
2 parents 75d885d + ce96e27 commit 4ecf58f
Show file tree
Hide file tree
Showing 20 changed files with 1,313 additions and 2 deletions.
115 changes: 115 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
snapkup
bin/
test/

# Created by https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,macos,linux,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=go,visualstudiocode,macos,linux,windows

### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

### Go Patch ###
/vendor/
/Godeps/

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace

# Local History for Visual Studio Code
.history/

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode,macos,linux,windows
19 changes: 19 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.PHONY: list
list:
@LC_ALL=C $(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'

build-prepare:
make clean
mkdir bin

clean:
rm -rf bin

build:
make build-prepare
cd src; go build -o snapkup; strip snapkup
mv src/snapkup bin/

zbuild:
make build
upx --lzma bin/snapkup
69 changes: 67 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,67 @@
# snapkup
A snapshot-based backup tool, easy to understand and use
# 🗃️ snapkup v0.1.0

Snapkup is a simple backup tool that takes snapshots of your filesystem (or the parts that you'll decide), storing them efficiently and conveniently.

## Basic workflow

Snapkup's goal is to store efficiently one or more filesystem's situation at given points in time, in a manner that is friendly to e.g. Dropbox sync or removable storage.

- You initialize an empty directory that will store the backups
- You register one or more backup roots, directory or files that will be snapshotted
- You take one or more snapshots. Snapkup lists all the tree for those roots, taking a snapshot of the contents
- All the files in the roots are deduplicated, and only the files that are different are stored
- All files that can be compressed are stored as such, using `zstd -9`
- Files are stored in an efficient manner, with a shallow directory structure.
- You can restore the situation of the roots at a given snapshot, later on
- Files' and dirs' mode and modification time are preserved
- If you choose to delete any snapshot, dangling backup files are removed.
- Of course, it's possible to list roots and snapshots and delete any of them.

All paths are converted to absolute paths, for consistency.

## Mini-tutorial

We will backup the contents of the `C:\MyImportantDir`, using the `C:\MySnapkupDir` folder as the container of the backup structures. This example is styled after windows, but it's completely similar under UNIXes.

### Initialize the backup directory

`snapkup.exe -d C:\MySnapkupDir init`

### Register the directory to backup as a root

`snapkup.exe -d C:\MySnapkupDir add-root C:\MyImportantDir`

### Take your first snapshot

`snapkup.exe -d C:\MySnapkupDir snap`

*add `-z` if you want to compress the files being backed up*.

### Delete it, because... just because.

`snapkup.exe -d C:\MySnapkupDir del-snap 0`

### Or restore it!

`snapkup.exe -d C:\MySnapkupDir restore 0 C:\MyRestoreDir`

*the destination directory should be empty.*

## Status

Everything described above should work. **It's still at an early stage of development, so don't trust it with any critical data, yet**.

Next steps:

- Proper testing framework, for reliability
- Improved documentation
- Mounting a snapshot as a FUSE filesystem
- Proper cross-compiling
- Better error handling
- Better recovery of the data structures from errors

## Build

`cd` to the `src/` dir and `go build`. On UNIX systems you can also use `make build` from the root.

It uses `CGO`, so cross-compilation comes with the usual caveats, and a proper build stack should be installed.
29 changes: 29 additions & 0 deletions docs/db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
CREATE TABLE SNAPS (
ID INTEGER NOT NULL,
TIMESTAMP INTEGER NOT NULL,
PRIMARY KEY(ID)
);

CREATE TABLE ITEMS (
PATH TEXT NOT NULL,
SNAP INTEGER NOT NULL,
HASH TEXT NOT NULL,
IS_DIR INTEGER NOT NULL,
MODE INTEGER NOT NULL,
MOD_TIME INTEGER NOT NULL,
PRIMARY KEY(PATH, SNAP)
);

CREATE TABLE BLOBS (
HASH TEXT NOT NULL,
SIZE INTEGER NOT NULL,
BLOB_SIZE INTEGER NOT NULL,
IS_COMPRESSED INTEGER NOT NULL,
IS_ENCRYPTED INTEGER NOT NULL,
PRIMARY KEY(HASH)
);

CREATE TABLE ROOTS (
PATH TEXT NOT NULL,
PRIMARY KEY(PATH)
);
50 changes: 50 additions & 0 deletions src/commands/add_root/add_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package addroot

import (
"database/sql"
"fmt"

"github.com/proofrock/snapkup/util"
)

func AddRoot(bkpDir string, toAdd string) error {
dbPath, errComposingDbPath := util.DbFile(bkpDir)
if errComposingDbPath != nil {
return errComposingDbPath
}

db, errOpeningDb := sql.Open("sqlite3", dbPath)
if errOpeningDb != nil {
return errOpeningDb
}
defer db.Close()

tx, errBeginning := db.Begin()
if errBeginning != nil {
return errBeginning
}

// TODO QueryOnce
throwaway := 1
row := db.QueryRow("SELECT 1 FROM ROOTS WHERE PATH = ?", toAdd)
if errQuerying := row.Scan(&throwaway); errQuerying == nil {
tx.Rollback()
return fmt.Errorf("Root already present (%s)", toAdd)
} else if errQuerying != sql.ErrNoRows {
tx.Rollback()
return errQuerying
}

if _, errExecing := tx.Exec("INSERT INTO ROOTS (PATH) VALUES (?)", toAdd); errExecing != nil {
tx.Rollback() // error is not managed
return errExecing
}

if errCommitting := tx.Commit(); errCommitting != nil {
return errCommitting
}

println("Root correctly added (", toAdd, ")")

return nil
}
33 changes: 33 additions & 0 deletions src/commands/del_root/del_root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package delroot

import (
"database/sql"
"fmt"

"github.com/proofrock/snapkup/util"
)

func DelRoot(bkpDir string, toDel string) error {
dbPath, errComposingDbPath := util.DbFile(bkpDir)
if errComposingDbPath != nil {
return errComposingDbPath
}

db, errOpeningDb := sql.Open("sqlite3", dbPath)
if errOpeningDb != nil {
return errOpeningDb
}
defer db.Close()

if res, errExecing := db.Exec("DELETE FROM ROOTS WHERE PATH = ?", toDel); errExecing != nil {
return errExecing
} else if numAffected, errCalcRowsAffected := res.RowsAffected(); errCalcRowsAffected != nil {
return errExecing
} else if numAffected == 0 {
return fmt.Errorf("Root not found in pool (%s)", toDel)
}

println("Root correctly deleted (", toDel, ")")

return nil
}
Loading

0 comments on commit 4ecf58f

Please sign in to comment.