• Updated input 'flake-parts':
'github:hercules-ci/flake-parts/49f0870db23e8c1ca0b5259734a02cd9e1e371a1?narHash=sha256-F82%2BgS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE%3D' (2025-06-01)
→ 'github:hercules-ci/flake-parts/af66ad14b28a127c5c0f3bbb298218fc63528a18?narHash=sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8%3D' (2025-08-06)
• Updated input 'flake-parts/nixpkgs-lib':
'github:nix-community/nixpkgs.lib/656a64127e9d791a334452c6b6606d17539476e2?narHash=sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc%3D' (2025-06-01)
→ 'github:nix-community/nixpkgs.lib/0f36c44e01a6129be94e3ade315a5883f0228a6e?narHash=sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA%3D' (2025-07-27)
• Updated input 'git-hooks-nix':
'github:cachix/git-hooks.nix/80479b6ec16fefd9c1db3ea13aeb038c60530f46?narHash=sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo%2BbnXU9D9k%3D' (2025-05-16)
→ 'github:cachix/git-hooks.nix/4b04db83821b819bbbe32ed0a025b31e7971f22e?narHash=sha256-I0Ok1OGDwc1jPd8cs2VvAYZsHriUVFGIUqW%2B7uSsOUM%3D' (2025-08-17)
• Updated input 'git-hooks-nix/flake-compat':
'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33?narHash=sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U%3D' (2023-10-04)
→ 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885?narHash=sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX%2BfjA8Xf8PUmqCY%3D' (2025-05-12)
• Updated input 'nixpkgs':
'github:nixos/nixpkgs/910796cabe436259a29a72e8d3f5e180fc6dfacc?narHash=sha256-StSrWhklmDuXT93yc3GrTlb0cKSS0agTAxMGjLKAsY8%3D' (2025-05-31)
→ 'github:nixos/nixpkgs/20075955deac2583bb12f07151c2df830ef346b4?narHash=sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs%2BStOp19xNsbqdOg%3D' (2025-08-19)
• Updated input 'pyproject-nix':
'github:pyproject-nix/pyproject.nix/e09c10c24ebb955125fda449939bfba664c467fd?narHash=sha256-QxdHGNpbicIrw5t6U3x%2BZxeY/7IEJ6lYbvsjXmcxFIM%3D' (2025-05-06)
→ 'github:pyproject-nix/pyproject.nix/023cd4be230eacae52635be09eef100c37ef78da?narHash=sha256-QSKpYg%2BTs9HYF155ltlj40iBex39c05cpOF8gjoE2EM%3D' (2025-08-11)
.git/hooks/pre-commit: line 13: /nix/store/1grwmkj3qf897wgxycyymip87i9cvk29-pre-commit-4.0.1/bin/pre-commit: No such file or directory
|
||
|---|---|---|
| modules | ||
| src/restricted_backup | ||
| .envrc | ||
| .gitignore | ||
| default.nix | ||
| derivation.nix | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
Restricted Backup
This is a script to send either btrfs snapshots or rsync backups to a remote server It uses rrsync to restrict the rsync connection to a certain directory and does something similar with btrfs to restrict those to a certain connection as well.
Installation
git clone https://git.phlipphlop.me/phlipphlop/restricted_backup.git
cd restricted_backup
python -m build
pip install dist/restricted_backup-0.0.1.tar.gz
Nixos
Include this repository as a flake and follow the instructions below
Usage
Server
When run in server mode all commands are run the from the host machine and ssh is used to interact with the script.
- Decrypt and mount
cat backup.key | ssh $user@$ip cryptsetup open - Unmount and encrypt
ssh $user@$ip cryptsetup close - rsync
rsync ... ssh $user@$ip:/use regular rsync command, the destination is restricted. Snapshots are automatically created - Receive a btrfs snapshot
btrfs send snapshot | ssh $user@$ip btrfs receive $subvolumewhere subvolume is a subvolume inside the restricted directory - Get parent snapshot for sending incremental snapshot
ssh $user@$ip btrfs parent
Client
Client mode is supposed to be used directly via the command line or automated to run periodically using your method of choice. Run restricted_backup.py client -h for more info.
Limitations
- The backup drive must be btrfs formatted
- The subvolumes that get sent cannot have the same basename
Setup
On backup drive
The backup drive must be formated with the btrfs filesystem. The layout of the subvolumes is unimportant however once mounted all the hostnames of the devices you wish to backup should be present on the root of the backup device. You must therefore create a subvolume for every host that will backup to this drive in the root of the drive as well as a snapshots subvolume to store snapshots created from rsync backups. The snapshots directory can be omitted if only btrfs snapshots are sent and rsync is never used.
The directory structure will be different for devices backed up with rsync or with btrfs. Both can be used in conjunction, however not for the same client.
btrfs
/path/to/mnt/client_hostname/root/ # contains snapshots of subvol named / root
/path/to/mnt/client_hostname/subvol/ # contains snapshots of subvol named subvol
rsync
/path/to/mnt/client_hostname/ # root of backup containing everything from rsync
/path/to/mnt/snapshots/client_hostname/root/ # contains snapshots of client_hostname/
/path/to/mnt/snapshots/client_hostname/home/ # contains snapshots of client_hostname/home
Btrfs subvolumes and their parent directories must be created, the rest are created automatically. It is up to you to decide what should be a directory and what should be a subvolume, snapshots are by definition subvolumes.
On server
The server is the machine that will store the backups on the external drive.
restricted_backup.py is the script that does most of the main work however there is some setup required.
- Create a user with a home directory optionally in /var.
- Give the user permission to run
restricted-backup.pyas sudo passwordless and enable editing the environment in sudo. Add to sudoers file:username ALL=(root) NOPASSWD:SETENV: /path/to/restricted_backup.pyreplace username with the user that you created in the first step. - Configure sshd for the user to force a command. Add the following to the sshd_config file again replacing Username with the user you created in the first step as well as the path where the drive will be mounted.
Match User Username
ForceCommand sudo -E /var/remote-backup/restricted_backup.py server --your --args --here
PasswordAuthentication no
- Add the ssh-keys of the machines that you want to backup to $HOME/.ssh/authorized_keys.
On Client
No setup on the client is required. Run the script with the argument client and other wanted arguments. Optionally run the script periodically using either cron or a systemd. I leave this setup as an exercise to the user.
Note: This script must be able to read all of the files that are going to be backed up. Easiest is to run as root
Nixos
Include this repository in your flake.nix file
inputs = {
restricted_backup = {
type = "git";
url = "https://git.phlipphlop.me/phlipphlop/restricted_backup.git";
inputs.nixpkgs.follows = "nixpkgs"; # optional
};
};
Then include it in your imports list
imports = [
inputs.restricted_backup.nixosModules.default
];
Options
There are a number of options defined for nixos.
Client options can be found in modules/backup-client.nix
Server options can be found in modules/backup-server.nix