mirror of
https://github.com/edu4rdshl/rusnapshot.git
synced 2026-07-18 07:34:48 +00:00
Compare commits
33 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1351a6bd9b | |||
| a2de007c87 | |||
| 2f47feccd7 | |||
| daca0998fa | |||
| 2c8c16ad4e | |||
| 0285857b91 | |||
| c2d1d12abf | |||
| d57c34e9d1 | |||
| bfca23d1c7 | |||
| ae403db784 | |||
| f23e74c610 | |||
| 9c7f10c186 | |||
| b47ebd7616 | |||
| 97fa393825 | |||
| 8f67b4d044 | |||
| 7b363e0207 | |||
| 2a2058f781 | |||
| bd0887f416 | |||
| 5e1a3e7115 | |||
| 0b117ac07c | |||
| b8b2c6f0f8 | |||
| 844b6543b4 | |||
| e421db4013 | |||
| 573c50d356 | |||
| 41951edb8b | |||
| 235945c8e8 | |||
| 01e518d3d3 | |||
| e24d29c048 | |||
| 230b2babbe | |||
| 14c0f371d2 | |||
| 53ca8d4773 | |||
| 4648c12379 | |||
| a9aa9d9eae |
25 changed files with 574 additions and 360 deletions
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
github: [edu4rdshl]
|
||||||
22
.github/workflows/rust.yml
vendored
Normal file
22
.github/workflows/rust.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose
|
||||||
625
Cargo.lock
generated
625
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
|
@ -1,26 +1,25 @@
|
||||||
[package]
|
[package]
|
||||||
name = "rusnapshot"
|
name = "rusnapshot"
|
||||||
version = "0.4.0"
|
version = "0.5.3"
|
||||||
authors = ["edu4rdshl"]
|
authors = ["edu4rdshl"]
|
||||||
edition = "2018"
|
edition = "2024"
|
||||||
description = "Simple and handy btrfs snapshoting tool."
|
description = "Simple and handy btrfs snapshoting tool."
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.18", features = ["derive", "env"] }
|
clap = { version = "4.5.47", features = ["derive", "env"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
sqlite = "0.33.0"
|
sqlite = "0.37.0"
|
||||||
md5 = "0.7.0"
|
md5 = "0.8.0"
|
||||||
chrono = "0.4.33"
|
chrono = "0.4.42"
|
||||||
prettytable-rs = "0.10.0"
|
prettytable-rs = "0.10.0"
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.99"
|
||||||
toml = "0.8.9"
|
toml = "0.9.5"
|
||||||
|
hostname = "0.4.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = 'abort'
|
panic = 'abort'
|
||||||
incremental = false
|
strip = true
|
||||||
opt-level = "s"
|
|
||||||
|
|
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Eduard Tolosa
|
||||||
|
|
||||||
|
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 and this permission notice 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.
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Description
|
# Description
|
||||||
Simple and handy btrfs snapshoting tool. Supports unattended snapshots, tracking, restoring, automatic cleanup and more. Backed with SQLite.
|
Simple and handy definitions-based snapshoting tool for BTRFS. Supports unattended snapshots, tracking, restoring, automatic cleanup and more. Backed with SQLite.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
|
|
@ -7,6 +7,10 @@ See the [docs folder](https://github.com/Edu4rdSHL/rusnapshot/blob/master/docs/)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
**The main feature of Rustnapshot is the ability to create snapshots of BTRFS subvolumes using simple TOML definitions. It is designed to be simple and easy to use, while still providing powerful features for managing snapshots. See [the examples folder](https://github.com/Edu4rdSHL/rusnapshot/tree/master/examples) for more information.**
|
||||||
|
|
||||||
|
Among the features of Rustnapshot are:
|
||||||
|
|
||||||
- Allows you to specify the origin and destination of snapshots at will of the user.
|
- Allows you to specify the origin and destination of snapshots at will of the user.
|
||||||
- Track snapshots using SQLite as backend database.
|
- Track snapshots using SQLite as backend database.
|
||||||
- Easy setup using small templates instead of confusing long files.
|
- Easy setup using small templates instead of confusing long files.
|
||||||
|
|
@ -15,8 +19,9 @@ See the [docs folder](https://github.com/Edu4rdSHL/rusnapshot/blob/master/docs/)
|
||||||
- Ability to use the same SQLite database for everything.
|
- Ability to use the same SQLite database for everything.
|
||||||
- Ability to specify the prefix of the name for the snapshots for better identification.
|
- Ability to specify the prefix of the name for the snapshots for better identification.
|
||||||
- Ability to specify a `kind` identifier to differentiate them in the database. Useful if you plan to have hourly, weekly, monthly or more "kind" of snapshots of the same subvolume(s).
|
- Ability to specify a `kind` identifier to differentiate them in the database. Useful if you plan to have hourly, weekly, monthly or more "kind" of snapshots of the same subvolume(s).
|
||||||
|
- Ability to specify the maximum number of snapshots to keep for automatic cleanup.
|
||||||
- Supports restoration of snapshots in the original directory or a specific one.
|
- Supports restoration of snapshots in the original directory or a specific one.
|
||||||
- Automatic snapshots cleanup.
|
- Supports machine name identification for better tracking when using the same database in multiple machines.
|
||||||
- Nice CLI output to see the status and details of snapshots.
|
- Nice CLI output to see the status and details of snapshots.
|
||||||
|
|
||||||
# Known limitations
|
# Known limitations
|
||||||
|
|
|
||||||
51
builder.sh
51
builder.sh
|
|
@ -3,23 +3,22 @@ NAME="rusnapshot"
|
||||||
|
|
||||||
LINUX_TARGET="x86_64-unknown-linux-musl"
|
LINUX_TARGET="x86_64-unknown-linux-musl"
|
||||||
LINUX_X86_TARGET="i686-unknown-linux-musl"
|
LINUX_X86_TARGET="i686-unknown-linux-musl"
|
||||||
|
ARMV7_TARGET="armv7-unknown-linux-gnueabihf"
|
||||||
|
AARCH_TARGET="aarch64-unknown-linux-gnu"
|
||||||
MANPAGE_DIR="./$NAME.1"
|
MANPAGE_DIR="./$NAME.1"
|
||||||
|
BIN_OUTPUT_DIR="./ghbinaries"
|
||||||
|
|
||||||
if ! systemctl is-active docker >/dev/null 2>&1; then
|
rm -rf "$BIN_OUTPUT_DIR"
|
||||||
echo "Docker is not running. Starting docker."
|
mkdir -p "$BIN_OUTPUT_DIR"
|
||||||
if ! sudo systemctl start docker; then
|
|
||||||
echo "Failed to start docker."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Linux build
|
# Linux build
|
||||||
echo "Building Linux artifact."
|
echo "Building Linux artifact."
|
||||||
if cargo build -q --release --target="$LINUX_TARGET"; then
|
if cross build -q --release --target="$LINUX_TARGET"; then
|
||||||
echo "Linux artifact build: SUCCESS"
|
echo "Linux artifact build: SUCCESS"
|
||||||
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
|
cp "target/$LINUX_TARGET/release/$NAME" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||||
strip "target/$LINUX_TARGET/release/$NAME-linux"
|
strip "target/$LINUX_TARGET/release/$NAME-linux"
|
||||||
sha512sum "target/$LINUX_TARGET/release/$NAME-linux" >"target/$LINUX_TARGET/release/$NAME-linux.sha512"
|
sha512sum "target/$LINUX_TARGET/release/$NAME-linux" >"$BIN_OUTPUT_DIR/$NAME-linux.sha512"
|
||||||
|
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-x64.zip" "target/$LINUX_TARGET/release/$NAME-linux"
|
||||||
else
|
else
|
||||||
echo "Linux artifact build: FAILED"
|
echo "Linux artifact build: FAILED"
|
||||||
fi
|
fi
|
||||||
|
|
@ -30,11 +29,36 @@ if cross build -q --release --target="$LINUX_X86_TARGET"; then
|
||||||
echo "Linux x86 artifact build: SUCCESS"
|
echo "Linux x86 artifact build: SUCCESS"
|
||||||
cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
cp "target/$LINUX_X86_TARGET/release/$NAME" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||||
strip "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
strip "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||||
sha512sum "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" >"target/$LINUX_X86_TARGET/release/$NAME-linux-i386.sha512"
|
sha512sum "target/$LINUX_X86_TARGET/release/$NAME-linux-i386" >"$BIN_OUTPUT_DIR/$NAME-linux-i386.sha512"
|
||||||
|
zip -q -j "$BIN_OUTPUT_DIR/$NAME-linux-i386.zip" "target/$LINUX_X86_TARGET/release/$NAME-linux-i386"
|
||||||
else
|
else
|
||||||
echo "Linux x86 artifact build: FAILED"
|
echo "Linux x86 artifact build: FAILED"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ARMV7 build
|
||||||
|
echo "Building ARMv7 artifact."
|
||||||
|
if cross build -q --release --target="$ARMV7_TARGET"; then
|
||||||
|
echo "ARMv7 artifact build: SUCCESS"
|
||||||
|
cp "target/$ARMV7_TARGET/release/$NAME" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||||
|
strip "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||||
|
sha512sum "target/$ARMV7_TARGET/release/$NAME-armv7" >"$BIN_OUTPUT_DIR/$NAME-armv7.sha512"
|
||||||
|
zip -q -j "$BIN_OUTPUT_DIR/$NAME-armv7.zip" "target/$ARMV7_TARGET/release/$NAME-armv7"
|
||||||
|
else
|
||||||
|
echo "ARMv7 artifact build: FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Aarch64 build
|
||||||
|
echo "Building Aarch64 artifact."
|
||||||
|
if cross build -q --release --target="$AARCH_TARGET"; then
|
||||||
|
echo "Aarch64 artifact build: SUCCESS"
|
||||||
|
cp "target/$AARCH_TARGET/release/$NAME" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||||
|
strip "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||||
|
sha512sum "target/$AARCH_TARGET/release/$NAME-aarch64" >"$BIN_OUTPUT_DIR/$NAME-aarch64.sha512"
|
||||||
|
zip -q -j "$BIN_OUTPUT_DIR/$NAME-aarch64.zip" "target/$AARCH_TARGET/release/$NAME-aarch64"
|
||||||
|
else
|
||||||
|
echo "Aarch64 artifact build: FAILED"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Creating manpage..."
|
echo "Creating manpage..."
|
||||||
if command -v help2man >/dev/null; then
|
if command -v help2man >/dev/null; then
|
||||||
if help2man -o "$MANPAGE_DIR" "target/$LINUX_TARGET/release/$NAME"; then
|
if help2man -o "$MANPAGE_DIR" "target/$LINUX_TARGET/release/$NAME"; then
|
||||||
|
|
@ -45,10 +69,3 @@ if command -v help2man >/dev/null; then
|
||||||
else
|
else
|
||||||
echo "Please install the help2man package."
|
echo "Please install the help2man package."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop docker
|
|
||||||
echo "Stopping docker."
|
|
||||||
if ! sudo systemctl stop docker; then
|
|
||||||
echo "Failed to stop docker."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
- Create a snapshot using a [config file](https://github.com/Edu4rdSHL/rusnapshot/tree/master/examples/config-templates):
|
- Create a snapshot using a [config file](https://github.com/Edu4rdSHL/rusnapshot/tree/master/examples/config-templates):
|
||||||
|
|
||||||
`sudo rusnapshot --config {{path/to/config.toml}} --cr`
|
`sudo rusnapshot --config {{path/to/config.toml}} --create`
|
||||||
|
|
||||||
- List created snapshots:
|
- List created snapshots:
|
||||||
|
|
||||||
|
|
@ -30,11 +30,11 @@
|
||||||
|
|
||||||
- Create a read-write snapshot:
|
- Create a read-write snapshot:
|
||||||
|
|
||||||
`sudo rusnapshot -c {{path/to/config.toml}} --cr --rw`
|
`sudo rusnapshot -c {{path/to/config.toml}} --create --rw`
|
||||||
|
|
||||||
- Restore a snapshot:
|
- Restore a snapshot:
|
||||||
|
|
||||||
`sudo rusnapshot -c {{path/to/config.toml}} --id {{snapshot_id}} --restore`
|
`sudo rusnapshot --id {{snapshot_id}} --restore`
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
|
|
||||||
|
|
@ -2,14 +2,16 @@
|
||||||
# These are all the current available options
|
# These are all the current available options
|
||||||
|
|
||||||
# Snapshots directory
|
# Snapshots directory
|
||||||
dest_dir = "/.rusnapshot"
|
dest_dir = "/.snapshots"
|
||||||
# Directory to be snapshot-ed
|
# Directory to be snapshot-ed
|
||||||
source_dir = "/"
|
source_dir = "/"
|
||||||
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
||||||
database_file = "/var/lib/rusnapshot/rusnapshot.db"
|
database_file = "/.snapshots/rusnapshot.db"
|
||||||
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
||||||
snapshot_prefix = "root"
|
snapshot_prefix = "root"
|
||||||
# Snapshots identifier
|
# Snapshots identifier
|
||||||
snapshot_kind = "weekly"
|
snapshot_kind = "weekly"
|
||||||
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
||||||
keep_only = 2
|
keep_only = 3
|
||||||
|
# Machine name to be used in the snapshots metadata
|
||||||
|
machine = "Oribos"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
# Snapshots directory
|
# Snapshots directory
|
||||||
dest_dir = "/.rusnapshot"
|
dest_dir = "/.snapshots"
|
||||||
# Directory to be snapshot-ed
|
# Directory to be snapshot-ed
|
||||||
source_dir = "/home"
|
source_dir = "/home"
|
||||||
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
||||||
database_file = "/var/lib/rusnapshot/rusnapshot.db"
|
database_file = "/.snapshots/rusnapshot.db"
|
||||||
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
||||||
snapshot_prefix = "home"
|
snapshot_prefix = "home"
|
||||||
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
||||||
keep_only = 2
|
keep_only = 3
|
||||||
|
# Machine name to be used in the snapshots metadata
|
||||||
|
machine = "Oribos"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
# Snapshots directory
|
# Snapshots directory
|
||||||
dest_dir = "/.rusnapshot"
|
dest_dir = "/.snapshots"
|
||||||
# Directory to be snapshot-ed
|
# Directory to be snapshot-ed
|
||||||
source_dir = "/"
|
source_dir = "/"
|
||||||
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
# SQLite database file PATH where snapshots metadata will be stored. Created if not exists
|
||||||
database_file = "/var/lib/rusnapshot/rusnapshot.db"
|
database_file = "/.snapshots/rusnapshot.db"
|
||||||
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
# Prefix for the snapshots, they will be saved in the format prefix-$current_date
|
||||||
snapshot_prefix = "root"
|
snapshot_prefix = "root"
|
||||||
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
# Last snapshots to keep. If prefix is specified then only the snapshots with that prefix will be deleted
|
||||||
keep_only = 2
|
keep_only = 3
|
||||||
|
# Machine name to be used in the snapshots metadata
|
||||||
|
machine = "Oribos"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Snapshot root directory.
|
Description=Daily snapshots for home and root subvolumes
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind three-hours
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --create --kind daily
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind three-hours --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --clean --kind daily
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind three-hours
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --create --kind daily
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind three-hours --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --clean --kind daily
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
9
examples/services/rusnapshot-daily.timer
Normal file
9
examples/services/rusnapshot-daily.timer
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Take daily snapshots with rusnapshot
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=daily
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Take system snapshots monthly.
|
Description=Monthly snapshots for home and root subvolumes
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind monthly
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --create --kind monthly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind monthly --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --clean --kind monthly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind monthly
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --create --kind monthly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind monthly --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --clean --kind monthly
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Take system snapshots monthly.
|
Description=Take monthly snapshots with rusnapshot
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=monthly
|
OnCalendar=monthly
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=Take snapshots every 3 hours.
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=1h
|
|
||||||
OnUnitActiveSec=3h
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Take system snapshots weekly.
|
Description=Weekly snapshots for home and root subvolumes
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind weekly
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --create --kind weekly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --cr --clean --kind weekly --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-root.toml --clean --kind weekly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind weekly
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --create --kind weekly
|
||||||
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --cr --clean --kind weekly --rw
|
ExecStart=/usr/bin/rusnapshot -c /etc/rusnapshot/config-home.toml --clean --kind weekly
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Take system snapshots weekly.
|
Description=Take weekly snapshots with rusnapshot
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=weekly
|
OnCalendar=weekly
|
||||||
|
|
|
||||||
19
src/args.rs
19
src/args.rs
|
|
@ -23,12 +23,12 @@ pub struct Args {
|
||||||
/// Snapshot id or name to work with.
|
/// Snapshot id or name to work with.
|
||||||
#[clap(long = "id", default_value = "")]
|
#[clap(long = "id", default_value = "")]
|
||||||
pub snapshot_id: String,
|
pub snapshot_id: String,
|
||||||
/// Path to the SQLite database file.
|
/// Path to the `SQLite` database file.
|
||||||
#[clap(
|
#[clap(
|
||||||
short = 'd',
|
short = 'd',
|
||||||
long = "dfile",
|
long = "dfile",
|
||||||
env = "RUSNAPSHOT_DB_FILE",
|
env = "RUSNAPSHOT_DB_FILE",
|
||||||
default_value = "/var/lib/rusnapshot/rusnapshot.db"
|
default_value = "/.snapshots/rustnapshot.sqlite"
|
||||||
)]
|
)]
|
||||||
pub database_file: String,
|
pub database_file: String,
|
||||||
/// Prefix for the snapshot name.
|
/// Prefix for the snapshot name.
|
||||||
|
|
@ -38,10 +38,10 @@ pub struct Args {
|
||||||
#[clap(long = "kind", default_value = "rusnapshot")]
|
#[clap(long = "kind", default_value = "rusnapshot")]
|
||||||
pub snapshot_kind: String,
|
pub snapshot_kind: String,
|
||||||
/// Keep only the last X items.
|
/// Keep only the last X items.
|
||||||
#[clap(short = 'k', long = "keep", default_value = "10")]
|
#[clap(short = 'k', long = "keep", default_value = "3")]
|
||||||
pub keep_only: usize,
|
pub keep_only: usize,
|
||||||
/// Time in milliseconds until SQLite can return a timeout. Do not touch if you don't know what you are doing.
|
/// Time in milliseconds until `SQLite` can return a timeout. Do not touch if you don't know what you are doing.
|
||||||
#[clap(long = "timeout", default_value = "5000")]
|
#[clap(long = "timeout", default_value = "10000")]
|
||||||
pub timeout: usize,
|
pub timeout: usize,
|
||||||
/// Create a read-only/ro snapshot.
|
/// Create a read-only/ro snapshot.
|
||||||
#[clap(long = "create", conflicts_with_all = &["restore_snapshot", "delete_snapshot", "list_snapshots", "clean_snapshots"])]
|
#[clap(long = "create", conflicts_with_all = &["restore_snapshot", "delete_snapshot", "list_snapshots", "clean_snapshots"])]
|
||||||
|
|
@ -62,6 +62,9 @@ pub struct Args {
|
||||||
/// Create read-write/rw snapshots.
|
/// Create read-write/rw snapshots.
|
||||||
#[clap(short = 'w', long = "rw")]
|
#[clap(short = 'w', long = "rw")]
|
||||||
pub read_write: bool,
|
pub read_write: bool,
|
||||||
|
/// Machine name to be used in the metadata.
|
||||||
|
#[clap(short, long, default_value = "")]
|
||||||
|
pub machine: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
|
|
@ -148,6 +151,12 @@ impl Args {
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Failed to parse timeout, make sure it's a number");
|
.expect("Failed to parse timeout, make sure it's a number");
|
||||||
}
|
}
|
||||||
|
if let Some(machine) = config.get("machine") {
|
||||||
|
self.machine = machine
|
||||||
|
.to_string()
|
||||||
|
.parse()
|
||||||
|
.expect("Failed to parse machine");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use {
|
||||||
crate::{args::Args, database, operations, structs::ExtraArgs},
|
crate::{args::Args, database, operations, structs::ExtraArgs},
|
||||||
anyhow::Result,
|
anyhow::Result,
|
||||||
md5,
|
md5,
|
||||||
prettytable::{row, Table},
|
prettytable::{Table, row},
|
||||||
sqlite::Connection,
|
sqlite::Connection,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -79,7 +79,8 @@ pub fn manage_listing(database_connection: &Connection) -> Result<()> {
|
||||||
"KIND",
|
"KIND",
|
||||||
"SOURCE DIR",
|
"SOURCE DIR",
|
||||||
"DESTINATION DIR",
|
"DESTINATION DIR",
|
||||||
"RO/RW",
|
"MACHINE",
|
||||||
|
"RW",
|
||||||
"DATE"
|
"DATE"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -90,6 +91,7 @@ pub fn manage_listing(database_connection: &Connection) -> Result<()> {
|
||||||
data.kind,
|
data.kind,
|
||||||
data.source,
|
data.source,
|
||||||
data.destination,
|
data.destination,
|
||||||
|
data.machine,
|
||||||
data.ro_rw,
|
data.ro_rw,
|
||||||
data.date,
|
data.date,
|
||||||
]);
|
]);
|
||||||
|
|
@ -104,7 +106,7 @@ pub fn keep_only_x(args: &mut Args, extra_args: &mut ExtraArgs) -> Result<()> {
|
||||||
|
|
||||||
for data in &snaps_data {
|
for data in &snaps_data {
|
||||||
extra_args.snapshot_name = data.destination.clone() + &data.name;
|
extra_args.snapshot_name = data.destination.clone() + &data.name;
|
||||||
args.snapshot_id = data.snap_id.clone();
|
args.snapshot_id.clone_from(&data.snap_id);
|
||||||
|
|
||||||
manage_deletion(args, extra_args)?;
|
manage_deletion(args, extra_args)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn setup_initial_database(connection: &Connection) -> Result<()> {
|
pub fn setup_initial_database(connection: &Connection) -> Result<()> {
|
||||||
connection.execute("CREATE TABLE IF NOT EXISTS snapshots (name TEXT NOT NULL, snap_id TEXT NOT NULL, kind TEXT NOT NULL, source TEXT NOT NULL, destination TEXT NOT NULL, ro_rw TEXT NOT NULL, date TEXT DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(name, snap_id))")?;
|
connection.execute("CREATE TABLE IF NOT EXISTS snapshots (name TEXT NOT NULL, snap_id TEXT NOT NULL, kind TEXT NOT NULL, source TEXT NOT NULL, destination TEXT NOT NULL, machine TEXT NOT NULL, ro_rw TEXT NOT NULL, date TEXT DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(name, snap_id))")?;
|
||||||
connection.execute("PRAGMA journal_mode=WAL")?;
|
connection.execute("PRAGMA journal_mode=WAL")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -16,8 +16,14 @@ pub fn setup_initial_database(connection: &Connection) -> Result<()> {
|
||||||
|
|
||||||
pub fn commit_to_database(args: &Args, extra_args: &ExtraArgs) -> Result<()> {
|
pub fn commit_to_database(args: &Args, extra_args: &ExtraArgs) -> Result<()> {
|
||||||
let statement = format!(
|
let statement = format!(
|
||||||
"INSERT INTO snapshots (name, snap_id, kind, source, destination, ro_rw) VALUES ('{}', '{}', '{}', '{}', '{}', '{}')",
|
"INSERT INTO snapshots (name, snap_id, kind, source, destination, machine, ro_rw) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}')",
|
||||||
&extra_args.snapshot_name, args.snapshot_id, args.snapshot_kind, args.source_dir, args.dest_dir, args.read_write
|
&extra_args.snapshot_name,
|
||||||
|
args.snapshot_id,
|
||||||
|
args.snapshot_kind,
|
||||||
|
args.source_dir,
|
||||||
|
args.dest_dir,
|
||||||
|
args.machine,
|
||||||
|
args.read_write
|
||||||
);
|
);
|
||||||
extra_args.database_connection.execute(statement)?;
|
extra_args.database_connection.execute(statement)?;
|
||||||
|
|
||||||
|
|
@ -42,7 +48,7 @@ pub fn return_snapshot_data(connection: &Connection, args: &Args) -> Result<Data
|
||||||
let mut snap_data = Database::default();
|
let mut snap_data = Database::default();
|
||||||
|
|
||||||
while statement.next()? == State::Row {
|
while statement.next()? == State::Row {
|
||||||
snap_data = populate_db_struct(&statement, snap_data)?;
|
snap_data = populate_db_struct(&statement)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(snap_data)
|
Ok(snap_data)
|
||||||
|
|
@ -52,11 +58,10 @@ pub fn return_all_data(connection: &Connection) -> Result<Vec<Database>> {
|
||||||
let mut snapshots_data: Vec<Database> = Vec::new();
|
let mut snapshots_data: Vec<Database> = Vec::new();
|
||||||
|
|
||||||
let mut statement = connection
|
let mut statement = connection
|
||||||
.prepare("SELECT name,snap_id,kind,source,destination,ro_rw,datetime(date, 'localtime') FROM snapshots ORDER BY date DESC")?;
|
.prepare("SELECT name,snap_id,kind,source,destination,machine,ro_rw,datetime(date, 'localtime') FROM snapshots ORDER BY date DESC")?;
|
||||||
|
|
||||||
while statement.next()? == State::Row {
|
while statement.next()? == State::Row {
|
||||||
let db_struct = Database::default();
|
snapshots_data.push(populate_db_struct(&statement)?);
|
||||||
snapshots_data.push(populate_db_struct(&statement, db_struct)?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(snapshots_data)
|
Ok(snapshots_data)
|
||||||
|
|
@ -65,26 +70,24 @@ pub fn return_all_data(connection: &Connection) -> Result<Vec<Database>> {
|
||||||
pub fn return_only_x_items(connection: &Connection, args: &Args) -> Result<Vec<Database>> {
|
pub fn return_only_x_items(connection: &Connection, args: &Args) -> Result<Vec<Database>> {
|
||||||
let mut snapshots_data: Vec<Database> = Vec::new();
|
let mut snapshots_data: Vec<Database> = Vec::new();
|
||||||
|
|
||||||
let mut statement = connection.prepare(&format!("SELECT name,snap_id,kind,source,destination,ro_rw,date FROM (SELECT row_number() over(ORDER BY date DESC) n,* from snapshots WHERE name like '{}%' AND kind = '{}' AND ro_rw = '{}') WHERE n > {}", args.snapshot_prefix, args.snapshot_kind, args.read_write, args.keep_only))?;
|
let mut statement = connection.prepare(format!("SELECT name,snap_id,kind,source,destination,machine,ro_rw,date FROM (SELECT row_number() over(ORDER BY date DESC) n,* from snapshots WHERE name like '{}%' AND kind = '{}' AND ro_rw = '{}') WHERE n > {}", args.snapshot_prefix, args.snapshot_kind, args.read_write, args.keep_only))?;
|
||||||
|
|
||||||
while statement.next()? == State::Row {
|
while statement.next()? == State::Row {
|
||||||
let db_struct = Database::default();
|
snapshots_data.push(populate_db_struct(&statement)?);
|
||||||
snapshots_data.push(populate_db_struct(&statement, db_struct)?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(snapshots_data)
|
Ok(snapshots_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_db_struct(row: &Statement, mut db_struct: Database) -> Result<Database> {
|
fn populate_db_struct(stmt: &Statement) -> Result<Database> {
|
||||||
// I'll improve this later.
|
Ok(Database {
|
||||||
|
name: stmt.read(0)?,
|
||||||
db_struct.name = row.read(0)?;
|
snap_id: stmt.read(1)?,
|
||||||
db_struct.snap_id = row.read(1)?;
|
kind: stmt.read(2)?,
|
||||||
db_struct.kind = row.read(2)?;
|
source: stmt.read(3)?,
|
||||||
db_struct.source = row.read(3)?;
|
destination: stmt.read(4)?,
|
||||||
db_struct.destination = row.read(4)?;
|
machine: stmt.read(5)?,
|
||||||
db_struct.ro_rw = row.read(5)?;
|
ro_rw: stmt.read(6)?,
|
||||||
db_struct.date = row.read(6)?;
|
date: stmt.read(7)?,
|
||||||
|
})
|
||||||
Ok(db_struct)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/main.rs
10
src/main.rs
|
|
@ -1,7 +1,9 @@
|
||||||
use anyhow::Result;
|
use {
|
||||||
use chrono::Utc;
|
anyhow::Result,
|
||||||
use clap::Parser;
|
chrono::Utc,
|
||||||
use rusnapshot::{args, controller, structs::ExtraArgs, utils};
|
clap::Parser,
|
||||||
|
rusnapshot::{args, controller, structs::ExtraArgs, utils},
|
||||||
|
};
|
||||||
|
|
||||||
fn try_run() -> Result<()> {
|
fn try_run() -> Result<()> {
|
||||||
let mut arguments = args::Args::parse();
|
let mut arguments = args::Args::parse();
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ pub fn del_snapshot(snapshot_name: &str) -> bool {
|
||||||
pub fn restore_snapshot(args: &Args, snapshot_name: &str) -> bool {
|
pub fn restore_snapshot(args: &Args, snapshot_name: &str) -> bool {
|
||||||
!Path::new(&args.dest_dir).exists()
|
!Path::new(&args.dest_dir).exists()
|
||||||
&& Command::new("btrfs")
|
&& Command::new("btrfs")
|
||||||
.args(["subvolume", "snapshot", &snapshot_name, &args.dest_dir])
|
.args(["subvolume", "snapshot", snapshot_name, &args.dest_dir])
|
||||||
.status()
|
.status()
|
||||||
.expect("Error restoring the snapshot.")
|
.expect("Error restoring the snapshot.")
|
||||||
.success()
|
.success()
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub struct Database {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub destination: String,
|
pub destination: String,
|
||||||
pub ro_rw: String,
|
pub ro_rw: String,
|
||||||
|
pub machine: String,
|
||||||
pub date: String,
|
pub date: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,5 +42,9 @@ pub fn check_creation_requirements(arguments: &mut Args, extra_args: &ExtraArgs)
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if arguments.machine.is_empty() {
|
||||||
|
arguments.machine = hostname::get()?.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue