Automated and manual backup system based on Borg Backup with a terminal interface (TUI) and native systemd timers.
100% Bash. Zero runtime dependencies beyond the standard GNU/Linux ecosystem.
Developer: morphilab
- Manual and automatic backups — local or remote via SSH
- Client-side encryption with Borg (
repokey) - Deduplication — only changes between backups are stored
- Configurable retention per job (daily, weekly, monthly)
- TUI interface with
whiptailfor browsing and extracting backups - Native automation with
systemd --user timers(no custom daemon) - Detailed JSON logs with timestamps, exit codes, and executed commands
- No credentials in code — uses the system's
~/.ssh/config - File locking — prevents concurrent executions of the same job
- Signal trapping — automatic cleanup on Ctrl+C / SIGTERM
- Config validation — anti command-injection on
.confvalues - Dry-run mode —
./copycrow.sh dryrun <job>simulates without writing
English:
This project was developed with assistance from artificial intelligence tools. Given the automated nature of some components, users are advised to review and test the code independently before integrating it into their own systems.
Español:
Este proyecto fue desarrollado con asistencia de herramientas de inteligencia artificial. Dada la naturaleza automatizada de algunos componentes, se recomienda que los usuarios revisen y prueben el código independientemente antes de integrarlo en sus propios sistemas.
| Dependency | Installation |
|---|---|
borgbackup |
sudo apt install borgbackup |
whiptail |
sudo apt install whiptail |
bash 4+ |
Included in Ubuntu/Debian |
systemd |
Included in Ubuntu/Debian |
pass (recommended) |
sudo apt install pass |
git clone https://github.com/morphilab/copycrow.git
cd copycrow
./copycrow.sh initThis creates copycrow.conf (your configuration) from the example.
Create a dedicated key and configure two aliases:
ssh-keygen -t ed25519 -f ~/.ssh/copycrow_server -N "" -C "copycrow"
ssh-copy-id -i ~/.ssh/copycrow_server.pub user@1xx.1xx.1.1xxEdit ~/.ssh/config:
Host nas-backup
HostName 1xx.1xx.1.1xx
User backupuser
IdentityFile ~/.ssh/id_ed25519 # your daily key
Host nas-backup-borg
HostName 1xx.1xx.1.1xx
User backupuser
IdentityFile ~/.ssh/copycrow_server # dedicated key without passphrase
In copycrow.conf use host = nas-backup-borg. See the Security section.
sudo apt install pass
gpg --gen-key # generate GPG key
pass init "your-gpg-id" # initialize pass
pass insert copycrow/borg # store passphrase
export BORG_PASSCOMMAND="pass show copycrow/borg"Add to ~/.bashrc for persistence.
[global]
retention_default = --keep-daily 7 --keep-weekly 4 --keep-monthly 6
compression = lz4
mount_dir = .mnt
logs_dir = logs
[daily_job]
type = automatic
sources = /home /etc
host = nas-backup-borg
remote_path = /backups/copycrow/daily
schedule = daily
retention = --keep-daily 7 --keep-weekly 4Create a key without a passphrase for automation while keeping your daily key with a passphrase. Use two SSH aliases to the same server:
ssh-keygen -t ed25519 -f ~/.ssh/copycrow_server -N "" -C "copycrow"
ssh-copy-id -i ~/.ssh/copycrow_server.pub user@serverIn ~/.ssh/config:
Host my-server
HostName 1xx.1xx.1.1xx
User user
IdentityFile ~/.ssh/id_ed25519 # daily key with passphrase
Host my-server-borg
HostName 1xx.1xx.1.1xx
User user
IdentityFile ~/.ssh/copycrow_server # dedicated key without passphrase
In copycrow.conf use host = my-server-borg.
In the server's ~/.ssh/authorized_keys, restrict the key to only run borg:
command="borg serve --restrict-to-path /path/backups",no-port-forwarding,no-pty ssh-ed25519 AAA...
pass encrypts the passphrase with GPG. Avoids plaintext in ~/.bashrc:
sudo apt install pass
gpg --gen-key # generate GPG key
pass init "your-gpg-id" # initialize
pass insert copycrow/borg # store passphrase
export BORG_PASSCOMMAND="pass show copycrow/borg"Add to ~/.bashrc for persistence. Copycrow propagates BORG_PASSCOMMAND to timers automatically.
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519./copycrow.sh # Open interactive menu (TUI)
./copycrow.sh init # Initial setup
./copycrow.sh backup <job> # Manual backup of a job
./copycrow.sh auto <job> # Automatic backup (used by timers)
./copycrow.sh dryrun <job> # Simulate backup (writes nothing)
./copycrow.sh list [job] # List backups
./copycrow.sh open <host> <arch> # Extract and open container
./copycrow.sh migrate # Convert legacy config to English v1.0.0
./copycrow.sh install # Install systemd timers
./copycrow.sh uninstall # Remove timers
./copycrow.sh status # System status
./copycrow.sh help # Helpcopycrow/
├── copycrow.sh ← entry point
├── copycrow.conf ← your config (gitignored)
├── copycrow.conf.example ← example configuration
├── README.md
├── LICENSE
├── CHANGELOG.md
├── SECURITY.md
├── VERSION
├── .gitignore
├── .shellcheckrc
├── src/
│ ├── config-parser.sh ← INI parser + validation
│ ├── safety.sh ← traps, locks, cleanup
│ ├── backup-core.sh ← Borg wrapper + JSON logs
│ ├── timer-generator.sh ← systemd timers
│ └── tui.sh ← whiptail menus
├── tests/ ← bats-core tests
│ ├── config-parser.bats
│ └── safety.bats
├── .mnt/ ← temporary extraction
├── .locks/ ← mutual exclusion locks
└── logs/ ← daily JSON logs
# Requires: bats-core (https://github.com/bats-core/bats-core)
sudo apt install bats
bats tests/shellcheck -x copycrow.sh src/*.sh./copycrow.sh installThis creates systemd --user timers for all jobs with type=automatic.
Important: User timers only run when a login session is active. For always-on execution (even without login):
loginctl enable-linger"Permission denied (publickey)"
- Verify
~/.ssh/confighas the correctIdentityFilefor that host - If using a key with a passphrase, unlock it with
ssh-add - Test the key on the server:
ssh user@host "echo ok"
"Is borg working on the server?" Borg is not installed on the remote server:
ssh host "sudo apt install borgbackup""missing_passphrase" (in automatic timers)
You need BORG_PASSCOMMAND configured. See the Security section.
"BORG_PASSPHRASE is not set"
Configure the passphrase. Recommended: use pass + BORG_PASSCOMMAND (see Security).
"Repository does not exist" The repo is initialized automatically on the first backup. If it fails, check:
- SSH connection to the host:
ssh host "echo ok" - Borg installed on the remote server
- Write permissions on
remote_path
"copycrow.conf not found"
./copycrow.sh initTimers not running
systemctl --user list-timers 'copycrow-*'
loginctl enable-linger"Unknown config keys after upgrading?" If you upgraded from an older version with Spanish config keys:
./copycrow.sh migrate # Converts config to English v1.0.0 formatA backup is saved as copycrow.conf.bak.
MIT — See LICENSE file.