|
Some checks failed
Build and Publish / build-and-publish (push) Failing after 44s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| dist | ||
| node_modules | ||
| src | ||
| templates | ||
| .gitignore | ||
| package.json | ||
| PASSWORD_ROTATION.md | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
@lilith/restic-setup-client
Configure restic backup clients on workstations with automated systemd timers.
Features
- Initialize restic repositories for Code and dotfiles backups
- Deploy systemd user timers for automated backups (no root required)
- Configure backup schedules: Code every 5min, dotfiles every 12hr
- Retention policy: 7 daily, 4 weekly, 3 monthly backups (auto-pruned)
Installation
pnpm add @lilith/restic-setup-client
Prerequisites
Vault Symlink Setup
CRITICAL: The ~/.vault/ directory must be a symlink to your project's vault directory. This is required for the circular dependency solution (see Architecture section).
# Setup vault symlink first using @lilith/vault-setup-client
npx @lilith/vault-setup-client link \
--project ~/Code/@applications/@lilith/lilith-platform
# Verify symlink
ls -la ~/.vault
# Should show: ~/.vault -> /path/to/lilith-platform/vault
Why? Without this symlink:
- Password rotation will fail (no
~/.vault/restic-password.txt) - Disaster recovery won't work (circular dependency)
- Dotfiles backup won't include vault files
See @lilith/vault-setup-client for details.
Usage
CLI
# Setup backup client
npx @lilith/restic-setup-client \\
--server http://10.0.0.11:8000 \\
--password CWPVvKALTwyJfbdVE3oIq7L8Wc7MH4Pz
# With custom hostname
npx @lilith/restic-setup-client \\
--server http://10.0.0.11:8000 \\
--hostname apricot \\
--password CWPVvKALTwyJfbdVE3oIq7L8Wc7MH4Pz
Programmatic API
import { setupClient } from '@lilith/restic-setup-client'
const result = await setupClient({
serverUrl: 'http://10.0.0.11:8000',
hostname: 'apricot', // Optional: defaults to $(hostname)
password: 'CWPVvKALTwyJfbdVE3oIq7L8Wc7MH4Pz',
codeBackupInterval: '5min', // Optional: default
dotfilesBackupInterval: '12hr', // Optional: default
})
if (result.success) {
console.log(`✅ Setup complete`)
console.log(`Code repo: ${result.codeRepoUrl}`)
console.log(`Dotfiles repo: ${result.dotfilesRepoUrl}`)
} else {
console.error(`❌ Setup failed: ${result.error}`)
}
What It Does
1. Configuration Setup
Creates ~/.config/restic/:
password(mode 600): Repository passworddotfiles-include.txt: List of dotfiles to backup
2. Repository Initialization
Initializes two repositories on the REST server:
<hostname>-code: Backs up~/Code<hostname>-dotfiles: Backs up selected dotfiles
3. Systemd Timer Deployment
Deploys 4 systemd user units to ~/.config/systemd/user/:
restic-backup-code.service+restic-backup-code.timerrestic-backup-dotfiles.service+restic-backup-dotfiles.timer
Timers are enabled and started automatically.
Backup Configuration
Code Backup (restic-backup-code.timer)
- Frequency: Every 5 minutes
- Target:
~/Code - Excludes: node_modules, .git/objects, dist, build, .next, target, __pycache__, .pyc, .venv, venv
- Retention: 7 daily, 4 weekly, 3 monthly
- Nice level: 10 (low priority)
- I/O class: idle
Dotfiles Backup (restic-backup-dotfiles.timer)
- Frequency: Every 12 hours
- Target: Selective dotfiles from
dotfiles-include.txt - Includes: .bashrc, .zshrc, .ssh, .gitconfig, .config/nvim, .vault (symlink), etc.
- Excludes: .cache, Trash, .npm, .pnpm-store, browser caches
- Retention: 7 daily, 4 weekly, 3 monthly
Note: .vault is backed up as a symlink, preserving the link to the project vault.
Operations
Check Timer Status
# List all restic timers
systemctl --user list-timers | grep restic
# Check service status
systemctl --user status restic-backup-code.timer
systemctl --user status restic-backup-dotfiles.timer
Manual Backup
# Trigger code backup now
systemctl --user start restic-backup-code.service
# Trigger dotfiles backup now
systemctl --user start restic-backup-dotfiles.service
View Backup Logs
# Follow code backup logs
journalctl --user -u restic-backup-code.service -f
# View recent dotfiles backup
journalctl --user -u restic-backup-dotfiles.service -n 50
List Snapshots
export RESTIC_PASSWORD_FILE=~/.config/restic/password
# Code snapshots
restic -r rest:http://10.0.0.11:8000/$(hostname)-code snapshots
# Dotfiles snapshots
restic -r rest:http://10.0.0.11:8000/$(hostname)-dotfiles snapshots
Restore Files
export RESTIC_PASSWORD_FILE=~/.config/restic/password
REPO="rest:http://10.0.0.11:8000/$(hostname)-code"
# List files in latest snapshot
restic -r $REPO ls latest
# Restore specific file
restic -r $REPO restore latest \\
--target ~/restore \\
--include ~/Code/path/to/file.txt
# Restore entire Code directory
restic -r $REPO restore latest --target ~/restore
Requirements
- restic binary installed (
dnf install resticorbrew install restic) - systemd for user timers
- Vault symlink (
~/.vault/→ project vault) via @lilith/vault-setup-client - SSH access to restic REST server (for initial password fetch, optional)
Architecture
Vault Circular Dependency Solution
Problem: Restic backups ~/Code, but we need the restic password to restore ~/Code after data loss.
Solution: Symlink + dual backup paths
- Primary storage: Project vault at
lilith-platform/vault/ - Symlink:
~/.vault/→lilith-platform/vault/(convenient access) - Dual backups:
- Code backup: Backs up project vault via
~/Code/.../@lilith/lilith-platform/vault/ - Dotfiles backup: Backs up symlink
.vaultdirectly
- Code backup: Backs up project vault via
Disaster Recovery Flow:
# 1. Restore dotfiles first (includes ~/.vault symlink)
restic -r rest:http://10.0.0.11:8000/$(hostname)-dotfiles restore latest --target ~/
# 2. Use vault password to restore Code
export RESTIC_PASSWORD_FILE=~/.vault/restic-password.txt
restic -r rest:http://10.0.0.11:8000/$(hostname)-code restore latest --target ~/
# 3. Full recovery complete (vault + code + dotfiles)
Defense-in-depth: Password also stored in macOS Keychain via password rotation.
Integration with Server
This package is designed to work with @lilith/restic-setup-server:
import { deployServer, generatePassword } from '@lilith/restic-setup-server'
import { setupClient } from '@lilith/restic-setup-client'
// 1. Deploy server on devops host
const password = generatePassword()
const serverResult = await deployServer({
host: '10.0.0.11',
password,
})
// 2. Setup client on workstation
if (serverResult.success) {
const clientResult = await setupClient({
serverUrl: serverResult.serverUrl,
password: serverResult.password,
})
}
License
UNLICENSED - Internal Lilith Platform infrastructure package