-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackup_tool_duplicity.sh
More file actions
executable file
·195 lines (171 loc) · 4.67 KB
/
Copy pathbackup_tool_duplicity.sh
File metadata and controls
executable file
·195 lines (171 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/env bash
set -euo pipefail
# set -x
# --- Configuration & Defaults ---
HOSTNAME="$(hostname)"
readonly HOSTNAME
readonly CONFIG_DIR="${HOME}/.config/duplicity"
# Load local overrides (This is where TARGET_URL is defined per-machine)
if [[ -f "${CONFIG_DIR}/config.conf" ]]; then
# shellcheck source=/dev/null
source "${CONFIG_DIR}/config.conf"
fi
# Set pragmatic defaults if not defined in config.conf
readonly TARGET_URL="${TARGET_URL:-file:///backups/${HOSTNAME}-backup}"
readonly ARCHIVE_DIR="${ARCHIVE_DIR:-${HOME}/.cache/duplicity}"
readonly FULL_BACKUP_AGE="${FULL_BACKUP_AGE:-1M}"
readonly RETENTION_AGE="${RETENTION_AGE:-1M}"
# Disable all GPG encryption & passphrase prompts
export PASSPHRASE=""
# --- Duplicity Options ---
OPTS=(
--verbosity="notice"
--volsize=200
--concurrency=4
--no-encryption
--exclude-device-files
--archive-dir="${ARCHIVE_DIR}"
--exclude-filelist="${CONFIG_DIR}/exclude.conf"
--include-filelist="${CONFIG_DIR}/include.conf"
--exclude="**" # ignore everything not explicitly included
)
# --- Functions ---
setup_environment() {
if ! command -v duplicity >/dev/null 2>&1; then
echo "Error: duplicity is not installed or not in PATH" >&2
exit 1
fi
# Create config and archive directories
mkdir -p "${CONFIG_DIR}" "${ARCHIVE_DIR}"
# Only attempt to create the target directory locally if it is a file:// URL
if [[ "${TARGET_URL}" == file://* ]]; then
mkdir -p "${TARGET_URL#file://}"
fi
# Ensure filter files exist so duplicity doesn't crash
touch "${CONFIG_DIR}/exclude.conf" "${CONFIG_DIR}/include.conf"
}
usage() {
cat <<EOF
Universal Duplicity Backup Script
Usage: $(basename "$0") [OPTIONS] COMMAND
Commands:
backup Perform backup and prune old backups
status Show backup collection status
list List files in latest backup
verify Verify latest backup against local files
prune Remove backups older than ${RETENTION_AGE}
prune --all Remove ALL backups (requires confirmation)
cleanup Clean up failed backup attempts
help Show this help message
Options:
--dry-run Simulate operation without making changes
Configuration:
Override defaults by creating: ${CONFIG_DIR}/config.conf
Example config.conf:
TARGET_URL="sftp://backup_user@192.168.1.100:22//remote-backup"
FULL_BACKUP_AGE="1M"
RETENTION_AGE="3M"
EOF
}
notify() {
local message="$1"
local title="${2:-Backup}"
command -v notify-send >/dev/null 2>&1 || return 0
# notify-send will fail silently in systemd timers, which is expected and safe
notify-send \
--icon=drive-harddisk \
--app-name="System Backup" \
--urgency=low \
"${title}" "${message}" || true
}
remove_old_backups() {
echo "Pruning old backups"
duplicity remove-older-than "${RETENTION_AGE}" \
--force \
"${OPTS[@]}" \
"${TARGET_URL}"
}
remove_all_backups() {
echo "WARNING: This will permanently delete ALL backups at ${TARGET_URL}"
read -r -p "Type 'yes' to confirm: " confirmation
if [[ "${confirmation}" != "yes" ]]; then
echo "Aborted."
exit 0
fi
echo "Removing all backups"
duplicity remove-older-than now \
--force \
"${OPTS[@]}" \
"${TARGET_URL}"
}
backup() {
local lockfile="/tmp/backup_tool_${HOSTNAME}.lock"
exec 9>"${lockfile}"
if ! flock -n 9; then
echo "Another backup is already running. Exiting." >&2
exit 1
fi
echo "Starting backup to ${TARGET_URL}"
local output
output=$(
duplicity backup \
"${OPTS[@]}" \
--full-if-older-than "${FULL_BACKUP_AGE}" \
/ "${TARGET_URL}" 2>&1
) || {
echo "Backup failed! See details below" >&2
echo "Run '$(basename "$0") cleanup' to attempt to fix the backend" >&2
echo "${output}" >&2
notify "Backup failed. Check logs for details." "Backup Failed"
exit 1
}
echo "${output}"
remove_old_backups
local summary
summary=$(echo "${output}" | grep -E "ElapsedTime|NewFiles|DeletedFiles|ChangedFiles|Errors" || true)
notify "${summary:-Completed}" "Backup Successful"
}
# --- Main ---
if [[ "${1:-}" == "--dry-run" ]]; then
OPTS+=(--dry-run)
echo "DRY RUN MODE: No changes will be made"
shift
fi
case "${1:-}" in
backup)
setup_environment
backup
;;
cleanup)
setup_environment
duplicity cleanup "${OPTS[@]}" --force "${TARGET_URL}"
;;
prune)
setup_environment
if [[ "${2:-}" == "--all" ]]; then
remove_all_backups
else
remove_old_backups
fi
;;
status)
setup_environment
duplicity collection-status "${OPTS[@]}" "${TARGET_URL}"
;;
list)
setup_environment
duplicity list-current-files "${OPTS[@]}" "${TARGET_URL}"
;;
verify)
setup_environment
duplicity verify "${OPTS[@]}" "${TARGET_URL}" /
;;
help)
usage
;;
*)
echo "Error: unknown command '${1:-}'" >&2
usage
exit 1
;;
esac