A .NET 10 self-contained executable for backing up and restoring Ubuntu system configuration and files. Back up your packages and dotfiles on one machine, fresh-install Ubuntu, and restore everything as it was.
- Captures manually installed packages via
apt-mark showmanual - Recursively collects files from user-defined directories
- Skips sensitive files (
.env,.key,.pem,secrets.*) and directories (node_modules/,.cache/,.git/) based on configurable rules - Skips symlinks, broken symlinks, sockets, and pipes gracefully
- Records source directories in
manifest.txtfor accurate restore paths - Bundles everything into a timestamped
.ziparchive in./backups/ - Automatically includes
/etc/apt/sources.list.d/and/etc/apt/keyrings/so third-party repos (Docker, Tailscale, Azure CLI, etc.) can be restored on a fresh system - Prunes old backups beyond the
--keepcount (default: 5) - Warns if run as root without
sudo(home directory targets would resolve to/root)
- Requires
sudo— verifies root privileges before proceeding - Restores backed-up apt sources and keyrings, runs
apt update, then reinstalls packages frompackages.txtviaapt install -y - Package install failures are non-fatal — failed packages are logged to
missing-packages.txtand file restoration continues - Restores files to their original locations using
manifest.txt - Skips identical files automatically (SHA256 comparison)
- Presents an interactive backup selection menu if no file is specified
- Automatically remaps home directory paths when restoring on a different user (e.g. backup from
alice→ restore tobob) - Dry-run mode (
--dry-run) previews what would be restored without making changes; does not requiresudo
Conflict resolution details
When a file already exists on the system and differs from the backup, an interactive menu offers:
- Overwrite — Replace the system file with the backup version (requires confirmation)
- Skip — Keep the existing system file unchanged
- View Diff — Show an inline diff comparing the two versions (powered by DiffPlex), then re-prompt
- Abort Restore — Stop the restore process immediately
For files larger than 1 MB, diffs are truncated to the first 50 lines.
In non-interactive terminals (e.g. scripts, cron), conflicting files cause an automatic abort with a message to re-run interactively. To skip the interactive prompt and auto-overwrite all conflicting files, pass --yes:
sudo ./UbuntuSafeSnap restore --yes backups/backup.zip⚠
--yesshould be used sparingly — always prefer an interactive restore where you can review each conflict.
- Platform: Ubuntu/Debian (required for
apt-markandapt install) - Root access: Required for
restore(run withsudo). Thebackupcommand does not requiresudoand will warn if run as root. - SDK: .NET 10.0 (only required when building from source)
No .NET SDK required — just download and run.
Using curl:
mkdir -p ~/UbuntuSafeSnap
curl -sL -o ~/UbuntuSafeSnap/UbuntuSafeSnap https://github.com/Christian-Gennari/UbuntuSafeSnap/releases/latest/download/UbuntuSafeSnap
chmod +x ~/UbuntuSafeSnap/UbuntuSafeSnapAlternative download methods (wget / GitHub CLI / manual)
Using wget:
mkdir -p ~/UbuntuSafeSnap
wget -qO ~/UbuntuSafeSnap/UbuntuSafeSnap https://github.com/Christian-Gennari/UbuntuSafeSnap/releases/latest/download/UbuntuSafeSnap
chmod +x ~/UbuntuSafeSnap/UbuntuSafeSnapUsing GitHub CLI:
mkdir -p ~/UbuntuSafeSnap
gh release download --repo Christian-Gennari/UbuntuSafeSnap \
--pattern UbuntuSafeSnap --dir ~/UbuntuSafeSnap
chmod +x ~/UbuntuSafeSnap/UbuntuSafeSnapOr download the binary manually from GitHub Releases.
Requires .NET 10.0 SDK. Run the install script to clean, build, and deploy:
bash install.sh # installs to ~/UbuntuSafeSnap/
bash install.sh /custom/path # installs to a custom directorycd ~/UbuntuSafeSnap
./UbuntuSafeSnap init
# Edit targets.txt, exclusions.txt, and settings.txt to your needs./UbuntuSafeSnap backupCreates backups/ubuntusafesnap-YYYYMMdd-HHmmss.zip.
You can limit the number of backups kept by using the --keep option (defaults to 5):
./UbuntuSafeSnap backup --keep 3 # keep only the 3 most recent backupssudo ./UbuntuSafeSnap restore
# Interactive selection from ./backups/Or specify a file directly:
sudo ./UbuntuSafeSnap restore backups/ubuntusafesnap-20260509-123456.zipPreview what would be restored without making changes (no sudo needed):
./UbuntuSafeSnap restore --dry-run backups/ubuntusafesnap-20260509-123456.zipAutomatically overwrite all conflicting files without prompting (non-interactive):
sudo ./UbuntuSafeSnap restore --yes backups/ubuntusafesnap-20260509-123456.zip⚠ Use
--yeswith caution. It skips the interactive conflict resolver and overwrites any file that differs from the backup. Always run--dry-runfirst to preview what would change, and prefer running without--yesin an interactive terminal so you can review conflicts manually.
To run automatic weekly backups, add a cron entry:
crontab -eAdd the following line for a weekly backup every Sunday at 02:00, keeping the 5 most recent:
0 2 * * 0 ~/UbuntuSafeSnap/UbuntuSafeSnap backup --keep 5 >> ~/UbuntuSafeSnap/cron.log 2>&1
Cloud backup: See CLOUD_BACKUP.md for syncing backups to cloud storage.
⚠ Restoring is destructive. Always review what will be restored with
--dry-runfirst, and prefer runningrestorein an interactive terminal so you can handle file conflicts manually rather than using--yes.
List directories to include in the backup, one per line. Supports ~ expansion for home directories and # for comments. When running under sudo, ~ resolves to the real user's home directory (not /root).
The home directory (~ or /home/user) is blocked as a target to prevent accidentally backing up the entire home directory.
Default targets.txt
# Directories to back up, one per line. ~ expands to your home directory.
# Lines starting with # are comments and will be ignored.
~/.config
~/.bashrc
~/.profile
~/.ssh
/etc/NetworkManager
Configure which files, extensions, and directories to skip:
- Extension rules start with
.— match any file with that extension - Filename rules are just the name — match any file with that exact name
- Directory rules end with
/— skip entire directory trees by name
Default exclusions.txt
# Files matching these patterns will be excluded from backups.
# Extension rules start with . (e.g. .env, .key, .pem)
# Filename rules are just the name (e.g. secrets.json)
# Directory rules end with / (e.g. node_modules/) to skip entire trees
# Lines starting with # are comments and will be ignored.
.env
.key
.pem
.log
.lock
.pid
.db
.sqlite
secrets.json
secrets.lua
id_rsa
id_ed25519
id_ecdsa
node_modules/
.cache/
__pycache__/
.git/
Created by init alongside the other config files. Controls default behavior:
# Default settings for UbuntuSafeSnap
keep = 5
keep— Number of backups to retain (default: 5). Old backups beyond this count are pruned duringbackup.- Priority:
--keepCLI flag >settings.txtkeepvalue > default (5).
Auto-generated during backup. Records the mapping <source_directory>|<relative_path> for each file, enabling restore to place files back in their original locations. Do not edit or delete this file from the archive.
Every backup archive automatically contains these items alongside user-defined targets:
| File | Purpose |
|---|---|
packages.txt |
Output of apt-mark showmanual for package reinstallation |
manifest.txt |
Maps every file to its original source path |
apt-sources/sources.list.d/ |
Third-party apt repository definitions (e.g. Docker, Tailscale) |
apt-sources/keyrings/ |
GPG keys for authenticating third-party repos |
These are not user-configurable — they are always present in every archive.
For contribution guidelines, git workflow, and commit conventions, see WORKFLOW.md.
This project is licensed under the GNU General Public License v3.0 — see the LICENSE.md file for details.