This guide covers installing Zelta and configuring it for your environment. Whether you're setting up a single-user backup system or managing enterprise-scale replication across multiple teams, Zelta's flexible installation model adapts to your needs.
System Requirements
Installation Methods
Installation Types
Post-Installation Setup
Multiple User Configurations
Environment Variables
Updating Zelta
Uninstalling Zelta
Convenience Aliases
Next Steps
Zelta runs on any UNIX or UNIX-like system with:
/bin/sh)Tested platforms:
No package dependencies. No daemons. No configuration databases.
Note: ZFS is not required on a remote Zelta bastion (also known as an orchestrator or initiator). You can manage replication between ZFS systems from any UNIX-like host with SSH access.
The web installer downloads a GitHub branch archive and runs the normal install.sh installer. No git required:
# Latest (may include beta features)
curl -fsSL https://zelta.space/web-install.sh | sh
# Latest release branch
curl -fsSL https://zelta.space/web-install.sh | sh -s -- --branch=release/1.2
Installing from GitHub gives you the latest features and bug fixes:
git clone https://github.com/bell-tower/zelta.git
cd zelta
sudo ./install.sh
The installer detects whether you're running as root and adjusts paths accordingly.
Zelta is available in the FreeBSD Ports Collection:
pkg install zelta
Ports may lag the GitHub release. Use the web installer for current 1.2 features.
The installer supports two installation modes: system-wide (root) and user-specific (non-root). Both are fully functional—the choice depends on your environment and security requirements.
Run the installer as root for a traditional system-wide installation:
sudo ./install.sh
Default paths:
/usr/local/bin/zelta/usr/local/share/zelta//usr/local/etc/zelta//usr/local/share/man/man8/ (or /usr/share/man/man8/)Files created:
/usr/local/etc/zelta/zelta.env - Environment variable defaults/usr/local/etc/zelta/zelta.env.example - Reference copy/usr/local/etc/zelta/zelta.conf - Policy configuration/usr/local/etc/zelta/zelta.conf.example - Reference copyRun the installer as a regular user for a personal installation:
./install.sh
Default paths:
$HOME/bin/zelta$HOME/.local/share/zelta/$HOME/.config/zelta/$HOME/.local/share/zelta/doc/These defaults are enough for most user installs. If you need custom locations, export any of these variables before running the installer:
export ZELTA_BIN="$HOME/bin"
export ZELTA_SHARE="$HOME/.local/share/zelta"
export ZELTA_ETC="$HOME/.config/zelta"
export ZELTA_DOC="$ZELTA_SHARE/doc"
Important: Ensure $HOME/bin, or whichever bin directory you choose, is in your PATH. The installer warns when another zelta appears first in PATH, then runs the installed zelta version command directly so you can verify the installation immediately.
Check that Zelta is installed and accessible:
zelta usage
You should see Zelta's command reference.
For remote replication, set up SSH keys for passwordless authentication. See SSH Configuration for detailed instructions.
Quick setup:
# Generate SSH key (if you don't have one)
ssh-keygen -t ed25519
# Copy key to remote systems
ssh-copy-id backupuser@remote-host
Grant non-root users the minimum permissions needed for replication. See ZFS Allow Delegation for comprehensive examples.
Quick setup:
# On source systems (as root)
zfs allow -u backupuser send:raw,snapshot,hold,bookmark tank/data
# On target systems (as root)
zfs allow -u backupuser receive:append,create,mount,readonly,clone,rename,volmode,compression,recordsize tank/backups
Zelta can run multiple independent configurations on the same system using different user accounts. This is useful for separating concerns, implementing defense-in-depth, and managing different workflows: conventional backups, failover, recovery, pruning, and development.
Different replication workflows have different requirements:
By using separate user accounts, you get:
Here are three examples inspired by real-world Zelta accounts at Bell Tower:
space (Primary Backup)Purpose: System-to-system backups, comprehensive and reliable
Configuration:
# In space's ~/.bashrc or ~/.zshrc
export ZELTA_SHARE="$HOME/.local/share/zelta"
export ZELTA_ETC="$HOME/.config/zelta"
# 'space' uses the system-wide PATH
Policy file (~/.config/zelta/zelta.conf):
SNAP_MODE: 0 # We're managing snapshots elsewhere
BACKUP_ROOT: backupserver:tank/Backups
JOBS: 2
Production:
app-server-01:
- sink01/www
- sink01/database
app-server-02:
- sink02/www
- sink02/cache
Cron schedule:
# Every 6 hours
0 */6 * * * /usr/local/bin/zelta policy
twin (Failover Replication)Purpose: Fast bidirectional replication for high-availability failover
This setup creates a Zelta Twin: an active-passive asynchronous cluster pattern where the active, read-write dataset receives application writes. Zelta automatically snapshots it and replicates those changes to the read-only side. Both replication directions are defined in a single policy file, and Zelta determines which direction needs a backup on each run.
The model: Whichever side is read-write is live; the read-only side is standby. Failover is locking the primary, verifying the final backup, syncing local properties, and unlocking the secondary. No Ceph, no daemons, no shared storage. Zelta 1.2 provides zelta failover to automate that workflow.
See Zelta Twin for the full guide.
Configuration:
# In twin's ~/.bashrc or ~/.zshrc
export ZELTA_BIN="$HOME/bin"
export ZELTA_SHARE="$HOME/.local/share/zelta"
export ZELTA_ETC="$HOME/.config/zelta"
export PATH="$ZELTA_BIN:$PATH"
Policy file (~/.config/zelta/zelta.conf):
SNAP_NAME: "$(date -u +twin-%Y-%m-%d_%H-%M)"
SNAP_MODE: IF_NEEDED # Only snapshot if source has written data (default)
SEND_INTR: 0 # Skip intermediate snapshots for faster replication
JOBS: 4
# Primary to Secondary replication
Failover:
primary-db:
- tank/postgres: secondary-db:sink/postgres
primary-web:
- tank/www: secondary-web:sink/www
# Secondary to Primary replication (for failback)
Failback:
secondary-db:
- sink/postgres: primary-db:tank/postgres
secondary-web:
- sink/www: primary-web:tank/www
How it works:
Normal operation (primary active):
SNAP_MODE: IF_NEEDED snapshots the primary automaticallyFailover site replicates primary → secondaryFailback site does nothing (secondary is read-only, no written data)Failover procedure:
zelta failover primary-db:tank/postgres secondary-db:sink/postgres
zelta failover primary-web:tank/www secondary-web:sink/www
Failback operation:
SNAP_MODE: IF_NEEDED snapshots the secondary automaticallyFailback site replicates secondary → primaryFailover site does nothing (primary is read-only)Return to normal:
Caveats:
For lower-level maintenance, use zelta lock, zelta unlock, and zelta propsync directly.
Cron schedule:
# Every 15 minutes
*/15 * * * * /home/twin/bin/zelta policy
rescue (Recovery Operations)Purpose: Elevated permissions for emergency recovery and reverts
Note that in addition to disaster recovery, zelta revert and zelta rotate can also be used for powerful development workflows—rolling back to previous states, testing different configurations, or managing complex dataset histories.
Configuration:
# In rescue's ~/.bashrc or ~/.zshrc
export ZELTA_BIN="$HOME/bin"
export ZELTA_SHARE="$HOME/.local/share/zelta"
export ZELTA_ETC="$HOME/.config/zelta"
export PATH="$ZELTA_BIN:$PATH"
ZFS permissions (broader than backup users):
# On production systems (as root)
zfs allow -u rescue send,snapshot,hold,destroy,mount,create,clone,promote tank/production
Usage:
# Rescue user performs emergency revert
zelta revert tank/production/database
# Or recover from a backup
zelta clone backup:tank/Backups/database tank/production/database-recovery
No cron schedule - This user operates on-demand only.
Create user accounts:
# As root (FreeBSD syntax shown)
pw useradd space -m -s /bin/sh -c "Backup User"
pw useradd twin -m -s /bin/sh -c "Failover User"
pw useradd rescue -m -s /bin/sh -c "Recovery User"
Configure Zelta for each user:
# Install as root
git clone https://github.com/bell-tower/zelta.git
cd zelta
sudo ./install.sh
# Set ZELTA_ENV for each user
sudo su - twin
mkdir -p "$HOME/.config/zelta"
# Add ZELTA_ENV="$HOME/.config/zelta" to crontab and user RC
exit
Configure SSH keys:
# As each user
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
ssh-copy-id remote-host
Set ZFS permissions:
# As root, grant appropriate permissions to each user
zfs allow -u space send:raw,snapshot,hold,bookmark sink/data
zfs allow -u space receive:append,create,mount,canmount,volmode,readonly,clone,rename,userprop,recordsize tank/backups
zfs allow -u twin send:raw,receive:append,snapshot,hold,bookmark,create,mount,canmount,volmode,readonly,clone,rename,recordsize,compression tank/data
zfs allow -u rescue send,snapshot,hold,destroy,mount,create,clone tank/data
Create independent policy files:
Each user maintains their own ~/.config/zelta/zelta.conf with different targets, schedules, and options.
Zelta's behavior can be customized using environment variables. Variables can be set in three places:
.bashrc, .zshrc, or sessionzelta.env file - System-wide or user-specific defaultsOutside of zelta.env: All environment variables must use the ZELTA_ prefix:
export ZELTA_SNAP_NAME='$(date -u +auto-%Y-%m-%d_%H-%M)'
export ZELTA_JOBS=4
Inside zelta.env: The ZELTA_ prefix is optional (but allowed):
# Both forms work in zelta.env
SNAP_NAME='$(date -u +auto-%Y-%m-%d_%H-%M)'
JOBS=4
Some variables must be set in your shell environment because they're needed before zelta.env is loaded:
ZELTA_AWK - Path to AWK interpreter (default: awk)ZELTA_ENV - Path to environment file (default: /usr/local/etc/zelta/zelta.env)Example:
# In your ~/.bashrc
export ZELTA_AWK="/usr/bin/gawk"
export ZELTA_ENV="$HOME/.config/zelta/zelta.env"
See Environment & Policy Files for a comprehensive reference. Here are the most commonly used:
SNAP_NAME - Snapshot naming pattern (supports command substitution)BACKUP_ROOT - Default target root for policy-based replicationJOBS - Number of concurrent replication jobs (formerly THREADS, backward compatible)RETRY - Number of retry attempts for failed replicationsSEND_INTR - Skip intermediate snapshots (0 or 1)Rerun the same installer command to update an existing install. The installer reports when the installed version is already current.
curl -fsSL https://zelta.space/web-install.sh | sh
Pull the latest changes and reinstall:
cd zelta
git pull
sudo ./install.sh
The installer preserves your existing zelta.env and zelta.conf files.
pkg upgrade zelta
Zelta doesn't install system services or modify system files outside its installation directories. Use the uninstaller from the source tree when available:
./uninstall.sh
Manual removal is also straightforward:
# As root
rm -f /usr/local/bin/zelta
rm -rf /usr/local/share/zelta
rm -rf /usr/local/etc/zelta
rm -f /usr/local/share/man/man8/zelta*.8
# As the user
rm -f ~/bin/zelta
rm -rf ~/.local/share/zelta
rm -rf ~/.config/zelta
Remove the environment variable exports from your shell's startup script.
Zelta pre-1.0 included convenience aliases for several zelta subcommands.
Zelta supports adding these as aliases (global):
for cmd in zeport zpush zpull zp zmatch; do
ln -s /usr/local/bin/zelta /usr/local/bin/$cmd
done
Or as shell aliases (user-specific):
for cmd in zeport zpush zpull zp zmatch; do
echo "alias $cmd=zelta" >> ~/.zshrc # or ~/.bashrc
done
Now that Zelta is installed, you're ready to start replicating:
For questions or issues, see GitHub Issues or the Zelta Wiki.