Compare commits

..

No commits in common. "master" and "v0.6.0" have entirely different histories.

3 changed files with 17 additions and 96 deletions

82
btv
View file

@ -8,11 +8,8 @@ import datetime
import json import json
import os import os
import shlex import shlex
import shutil
import sys import sys
import time import time
import socket
import urllib.request
CONFIG = "/etc/btv/config.ini" CONFIG = "/etc/btv/config.ini"
LOCKFILE = "/run/lock/btv/serialization.lock" LOCKFILE = "/run/lock/btv/serialization.lock"
@ -199,9 +196,9 @@ def serialize(snap, outdir, key, snap_from=None):
## prepare directories ## prepare directories
## ##
if snap_from: if snap_from:
name = "%s to %s" %(snap_from.name, snap.name) name = "%s diff from %s" %(snap.name, snap_from.name)
else: else:
name = snap.name name = "%s full" %(snap.name)
directory = os.path.join(outdir, name) directory = os.path.join(outdir, name)
os.makedirs(directory) os.makedirs(directory)
@ -247,37 +244,21 @@ def serialize(snap, outdir, key, snap_from=None):
## final touches ## final touches
## ##
## add self-check and unpack executables ## add a self-check executable
with open(os.path.join(directory, "check-integrity.sh"), "w") as f: with open(os.path.join(directory, "check-integrity.sh"), "w") as f:
f.write("#! /bin/sh\n\nsha512sum --check manifest.sha512\n") f.write("#! /bin/sh\n\nsha512sum --check manifest.sha512\n")
os.chmod(f.name, 0o500) os.chmod(f.name, 0o555)
unpack_path = os.path.join(directory, "unpack.sh")
shutil.copy("/usr/share/btv/unpack.sh", unpack_path)
os.chmod(unpack_path, 0o500)
## fix permissions and ownership of created objects ## fix permissions and ownership of created objects
outdir_stat = os.stat(outdir) outdir_stat = os.stat(outdir)
os.chown(directory, outdir_stat.st_uid, outdir_stat.st_gid) os.chown(directory, outdir_stat.st_uid, outdir_stat.st_gid)
os.chmod(directory, 0o700)
for file in os.listdir(directory): for file in os.listdir(directory):
path = os.path.join(directory, file) path = os.path.join(directory, file)
os.chown(path, outdir_stat.st_uid, outdir_stat.st_gid) os.chown(path, outdir_stat.st_uid, outdir_stat.st_gid)
if path.endswith(".aes") or path.endswith(".sha512"):
os.chmod(path, 0o400)
return 0 return 0
def ping(url):
try:
urllib.request.urlopen(url, timeout=10)
except socket.error as e:
print("Ping failed: %s" %(e))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Verbs # Verbs
@ -325,10 +306,6 @@ def do_create(args):
elif snaps_since_rank_1 >= cfg.getint("snap", "rank_1_interval"): elif snaps_since_rank_1 >= cfg.getint("snap", "rank_1_interval"):
snapshot.rank = 1 snapshot.rank = 1
ping_url = cfg.get("monitoring", "rank2_start_url")
if snapshot.rank == 2 and ping_url:
ping(ping_url)
## create the snapshot itself ## create the snapshot itself
## ##
os.makedirs(snapshot.path) os.makedirs(snapshot.path)
@ -350,6 +327,7 @@ def do_create(args):
## do optional processing ## do optional processing
## ##
if snapshot.rank == 2: if snapshot.rank == 2:
## snapshot serialization ## snapshot serialization
# if there's a previous Rank 2 snapshot, compute a diff against it # if there's a previous Rank 2 snapshot, compute a diff against it
# if not or if the process fails, demote this snap to Rank 1 # if not or if the process fails, demote this snap to Rank 1
@ -382,10 +360,6 @@ def do_create(args):
## garbage collection ## garbage collection
do_gc() do_gc()
ping_url = cfg.get("monitoring", "rank2_end_url")
if snapshot.rank == 2 and ping_url:
ping(ping_url)
def do_list(args): def do_list(args):
""" """
Print a list of existing snapshots. Print a list of existing snapshots.
@ -519,42 +493,23 @@ def do_gc(args=None):
Drops old snapshots. Drops old snapshots.
If the only arg is "greedy", drops ALL snapshots except the youngest If the only arg is "greedy", drops ALL snapshots except the youngest
Rank 2. If it's a number, drops that many oldest snapshots. Rank 2.
""" """
if args: if args and "greedy" in args:
if args[0] == "greedy": newest = list_snapshots(2)[-1]
newest = list_snapshots(2)[-1]
if newest: if newest:
print(">>> Dropping all snapshots except the newest Rank 2 in 5 s...") print(">>> Dropping all snapshots except the newest Rank 2 in 5 s...")
time.sleep(5)
for snap in list_snapshots():
if snap.name != newest.name:
snap.drop()
else:
print("!!! no Rank 2 snapshot exists")
sys.exit(1)
else:
try:
count = int(args[0])
except ValueError:
print("!!! %s is not a count of snapshots to delete" %(args[0]))
sys.exit(1)
snaps = list_snapshots()[:count]
for snap in snaps:
print(" %d %s %s" %(snap.rank, snap.name, ", ".join(snap.notes)))
print(">>> These snapshots will be dropped in 5 s...")
time.sleep(5) time.sleep(5)
for snap in snaps: for snap in list_snapshots():
snap.drop() if snap.name != newest.name:
snap.drop()
else:
print("!!! no Rank 2 snapshot exists")
sys.exit(1)
else: else:
counts = [0, 0, 0] # Rank 0, 1, 2 counts = [0, 0, 0] # Rank 0, 1, 2
@ -610,9 +565,6 @@ Verbs:
gc greedy gc greedy
Drop ALL snapshots except the newest Rank 2. Drop ALL snapshots except the newest Rank 2.
gc COUNT
Drop COUNT oldest snapshots.
""".format(cmd=sys.argv[0])) """.format(cmd=sys.argv[0]))
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -35,10 +35,3 @@ dir = /mnt/pool/subvol/_backup
rank_0_count=25 rank_0_count=25
rank_1_count=36 rank_1_count=36
rank_2_count=42 rank_2_count=42
[monitoring]
# GETs this URL before starting a rank2 snapdhot
rank2_start_url =
# GETs this URL after successfully completing a rank2 snapshot
rank2_end_url =

View file

@ -1,24 +0,0 @@
#! /bin/zsh
TIMESTAMP=($(basename "$(pwd)"))
OUTDIR="$1"
KEYFILE="$2"
function die {
>&2 echo "$2"
exit $1
}
[[ "$0" != "./unpack.sh" ]] && die 1 "This can only be executed from the snapshot directory itself."
[[ ! -d "$OUTDIR" ]] && die 1 "The first argument must be a directory to unpack subvolumes into."
[[ ! -f "$KEYFILE" ]] && die 1 "The second argument must be a readable keyfile."
./check-integrity.sh || die 2 "This snapshot failed integrity checks."
### end of checks
for ARCHIVE in *btrfs.zst.aes
do
openssl enc -d -aes-256-cbc -pbkdf2 -salt -pass "file:$KEYFILE" < "$ARCHIVE" | zstd -d | btrfs receive "$OUTDIR" || die 3 "Failed to unpack subvolume."
SUBVOL_NAME=${ARCHIVE%%.btrfs.zst.aes}
mv "${OUTDIR}/${SUBVOL_NAME}" "${OUTDIR}/${SUBVOL_NAME}.${TIMESTAMP[1]}" || die 4 "Failed to rename subvolume."
done