blob: 1c95a5dba78111710d2dadcbf526d90a813a1238 (
plain) (
blame)
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
|
#!/usr/bin/env bash
set -euo pipefail
export PATH="/usr/sbin:/sbin:/usr/bin:/bin:$PATH"
#################### CONFIG – EDIT THESE ####################
# LUKS2 block device
LUKS_DEVICE="/dev/disk/by-uuid/6659e638-d52a-4c32-9781-9dcedd44db35"
# Name for the mapper device (will appear as /dev/mapper/${LUKS_NAME})
LUKS_NAME="shield"
# Mount point for the decrypted filesystem
LUKS_MOUNTPOINT="/mnt/shield"
# Local folders to back up with rsync → LUKS volume
BACKUP_ITEMS=(
"$HOME/"
"/media/.secrets/"
)
# Machine identifier (destination subdir on the LUKS volume)
MACHINE_DIR="T480"
# rsync exclude patterns (passed via stdin to --exclude-from=-)
# Paths are relative to each rsync source root (i.e. $HOME for the home backup).
read -r -d '' RSYNC_EXCLUDES <<'EOF' || true
# === Top-level junk ===
downloads/
Downloads/
tmp/
.tmp/
# === All caches (3.9G in .cache alone) ===
.cache/
# === Browsers (rebuilt on next launch) ===
.mozilla/
.mullvad-browser/
# === Shell / history / runtime state ===
.fasd
.viminfo
.zcompdump*
.lesshst
.sqlite_history
.python_history
.node_repl_history
.wget-hsts
.xsession-errors*
.Xauthority
.ICEauthority
.dbus/
.pki/
.gvfs/
# === Trash ===
.local/share/Trash/
.Trash-*/
# === Claude Code old versions (884M, current one re-fetches) ===
.local/share/claude/versions/
# === Haskell / cabal (1.9G total, fully regenerable) ===
.cache/cabal/
.local/state/cabal/
# === Rust / cargo registry (152M, regenerable from Cargo.lock) ===
.cargo/registry/
.cargo/git/
# === Python: venvs, caches, bytecode (anywhere in tree) ===
__pycache__/
*.pyc
*.pyo
.mypy_cache/
.pytest_cache/
.ruff_cache/
.tox/
.venv/
venv/
.virtualenvs/
.local/share/pipx/
# === Node / JS ===
node_modules/
.npm/
.yarn/cache/
.pnpm-store/
# === Build artifacts in repos ===
build/
dist/
target/
.gradle/
.m2/repository/
# === LaTeX caches ===
.texlive*/texmf-var/
.texlive*/texmf-cache/
# === Java runtime junk ===
.java/
# === Notmuch index (668M, rebuild with `notmuch new`) ===
.local/share/mail/.notmuch/xapian/
# === Whisper.cpp models (754M, redownloadable) ===
git/whisper.cpp/models/
git/whisper.cpp/build/
# === Reinstallable third-party software in ~/opt ===
opt/tor-browser/
opt/monero-gui-*/
opt/Electrum-*/
# === Themes (509M, reinstall from upstream) ===
.themes/gruvbox-gtk-theme/
# === Crypto node chain data (re-syncs) ===
.bitmonero/
.shared-ringdb/
.ethereum/geth/chaindata/
.bitcoin/blocks/
.bitcoin/chainstate/
# === Electron / chat app caches ===
.config/Signal/Cache/
.config/Code/Cache/
.config/Code/CachedData/
.config/Code/logs/
# === Flatpak / Snap caches ===
.var/app/*/cache/
snap/*/common/.cache/
# === OS junk ===
.DS_Store
Thumbs.db
*.swp
*.swo
*~
core
core.*
EOF
#################### INTERNALS – NO NEED TO EDIT ############
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: missing command: $1" >&2; exit 1; }; }
cleanup() {
echo
echo ">>> Cleaning up..."
if mountpoint -q "${LUKS_MOUNTPOINT}"; then
echo ">>> Unmounting ${LUKS_MOUNTPOINT}..."
sudo umount "${LUKS_MOUNTPOINT}" || true
fi
if [ -e "/dev/mapper/${LUKS_NAME}" ]; then
echo ">>> Closing LUKS mapping ${LUKS_NAME}..."
sudo cryptsetup close "${LUKS_NAME}" || true
fi
}
trap cleanup EXIT
need_cmd cryptsetup
need_cmd rsync
need_cmd mountpoint
# Cache sudo credentials early
sudo -v
echo ">>> Ensuring mountpoint exists: ${LUKS_MOUNTPOINT}"
sudo mkdir -p "${LUKS_MOUNTPOINT}"
if [ -e "/dev/mapper/${LUKS_NAME}" ]; then
echo ">>> WARNING: /dev/mapper/${LUKS_NAME} already exists, assuming already open."
else
echo ">>> Opening LUKS volume (interactive prompt)..."
sudo cryptsetup open "${LUKS_DEVICE}" "${LUKS_NAME}"
fi
if [ ! -e "/dev/mapper/${LUKS_NAME}" ]; then
echo "ERROR: /dev/mapper/${LUKS_NAME} not found after cryptsetup open." >&2
exit 1
fi
if mountpoint -q "${LUKS_MOUNTPOINT}"; then
echo ">>> WARNING: ${LUKS_MOUNTPOINT} already mounted."
else
echo ">>> Mounting /dev/mapper/${LUKS_NAME} on ${LUKS_MOUNTPOINT}..."
sudo mount "/dev/mapper/${LUKS_NAME}" "${LUKS_MOUNTPOINT}"
fi
echo ">>> LUKS volume mounted at ${LUKS_MOUNTPOINT}"
echo ">>> Starting rsync backups of local folders -> ${LUKS_MOUNTPOINT}/${MACHINE_DIR}/..."
sudo mkdir -p "${LUKS_MOUNTPOINT}/${MACHINE_DIR}"
for SRC in "${BACKUP_ITEMS[@]}"; do
if [ ! -d "$SRC" ]; then
echo ">>> WARNING: source folder does not exist, skipping: $SRC"
continue
fi
case "$SRC" in
"$HOME/"|"$HOME")
RELDEST="${MACHINE_DIR}/HOME"
;;
"/media/.secrets/"|"/media/.secrets")
RELDEST="${MACHINE_DIR}/secrets"
;;
*)
NAME="$(basename "${SRC%/}")"
RELDEST="${MACHINE_DIR}/${NAME}"
;;
esac
DEST="${LUKS_MOUNTPOINT}/${RELDEST}"
sudo mkdir -p "${DEST}"
echo ">>> rsync: ${SRC} -> ${DEST}"
set +e
sudo rsync -a --delete --progress \
--exclude-from=- \
"${SRC%/}/" \
"${DEST}/" <<< "${RSYNC_EXCLUDES}"
RSYNC_RC=$?
set -e
case "${RSYNC_RC}" in
0) ;;
23|24)
echo ">>> rsync finished with code ${RSYNC_RC} (some files skipped/vanished); continuing."
;;
*)
echo "ERROR: rsync failed with code ${RSYNC_RC} on ${SRC}" >&2
exit "${RSYNC_RC}"
;;
esac
done
echo ">>> All rsync backups finished."
echo ">>> Unmounting and closing LUKS (via trap)..."
|