Skip to content

Commit 25c98be

Browse files
authored
Backport backup fixes (#2838)
* watch for file modifications * fix restore file check
1 parent ef3c97b commit 25c98be

3 files changed

Lines changed: 243 additions & 96 deletions

File tree

runs/backup.sh

Lines changed: 240 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,39 @@ OPENWBDIRNAME=${OPENWBDIRNAME:-/}
55
TARBASEDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
66
BACKUPDIR="$OPENWBBASEDIR/data/backup"
77
RAMDISKDIR="$OPENWBBASEDIR/ramdisk"
8-
TEMPDIR="$RAMDISKDIR/temp"
8+
TEMPDIR=$(mktemp -d --tmpdir openwb_backup_XXXXXX)
99
LOGDIR="$OPENWBBASEDIR/data/log"
1010
LOGFILE="$LOGDIR/backup.log"
11+
HOMEDIR="/home/openwb"
12+
VAR_LIB="/var/lib"
13+
14+
# Mosquitto DB files to monitor
15+
DB_FILES=(
16+
"mosquitto/mosquitto.db"
17+
"mosquitto_local/mosquitto.db"
18+
)
19+
20+
# Timeout for mosquitto DB flush
21+
DB_TIMEOUT=5
1122

1223
useExtendedFilename=$1
13-
if ((useExtendedFilename == 1)); then
14-
# only use characters supported in most OS!
15-
# for Win see https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-shares--directories--files--and-metadata
16-
FILENAME="openWB_backup_$(date +"%Y-%m-%d_%H-%M-%S").tar"
17-
else
18-
FILENAME="backup.tar"
19-
fi
24+
FILENAMESUFFIX=".tar.gz"
2025

21-
{
26+
generate_filename() {
27+
# generate filename
28+
# if useExtendedFilename is 1, use date and version info
29+
# else just use "backup"
30+
if ((useExtendedFilename == 1)); then
31+
# only use characters supported in most OS!
32+
# for Win see https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-shares--directories--files--and-metadata
33+
FILENAME="openWB_backup_$(date +"%Y-%m-%d_%H-%M-%S")"
34+
else
35+
FILENAME="backup"
36+
fi
37+
BACKUPFILE="$BACKUPDIR/$FILENAME"
38+
}
39+
40+
log_environment() {
2241
echo "starting backup script"
2342
echo "environment:"
2443
echo " OPENWBBASEDIR: $OPENWBBASEDIR"
@@ -30,95 +49,223 @@ fi
3049
echo " LOGDIR: $LOGDIR"
3150
echo " LOGFILE: $LOGFILE"
3251
echo " FILENAME: $FILENAME"
52+
}
3353

34-
echo "deleting old backup files if present in '$BACKUPDIR'"
35-
# remove old backup files
36-
rm -v "$BACKUPDIR/"*
37-
BACKUPFILE="$BACKUPDIR/$FILENAME"
54+
remove_old_backups() {
55+
echo "removing old files in '$BACKUPDIR' if present"
56+
# Delete old backups (robust, no glob issues)
57+
find "$BACKUPDIR" -mindepth 1 -maxdepth 1 -not -name '.donotdelete' -exec rm -vrf {} +
58+
}
59+
60+
force_mosquitto_write() {
61+
collect_baseline() {
62+
echo "collecting Baseline-mtime before SIGUSR1:"
63+
# Baseline mtimes to avoid race condition (captured before SIGUSR1)
64+
declare -Ag BASELINE_DB_MTIME=()
65+
66+
for f in "${DB_FILES[@]}"; do
67+
if [ -e "$VAR_LIB/$f" ]; then
68+
BASELINE_DB_MTIME["$f"]=$(stat -c %Y "$VAR_LIB/$f")
69+
echo " $f -> ${BASELINE_DB_MTIME["$f"]}"
70+
else
71+
BASELINE_DB_MTIME["$f"]=0
72+
echo " $f -> (does not yet exist, setting to 0)"
73+
fi
74+
done
75+
}
76+
77+
flush_mosquitto() {
78+
# inform mosquitto to flush data to disk
79+
echo "sending 'SIGUSR1' to mosquitto processes to flush data to disk"
80+
sudo pkill -e -SIGUSR1 mosquitto || echo "WARNING: no processes found?"
81+
}
82+
83+
wait_for_mosquitto_flush() {
84+
# wait for mosquitto to flush db files to disk
85+
# uses inotifywait if available, else falls back to polling
86+
# requires inotify-tools package for inotifywait
87+
local start_ts
88+
start_ts=$(date +%s)
89+
declare -A initial_mtime
90+
91+
# Use previously captured baseline (recorded before signal)
92+
if ((${#BASELINE_DB_MTIME[@]})); then
93+
for f in "${DB_FILES[@]}"; do
94+
initial_mtime["$VAR_LIB/$f"]=${BASELINE_DB_MTIME["$f"]:-0}
95+
done
96+
else
97+
# Fallback (should not occur)
98+
for f in "${DB_FILES[@]}"; do
99+
[ -e "$VAR_LIB/$f" ] && initial_mtime["$VAR_LIB/$f"]=$(stat -c %Y "$VAR_LIB/$f") || initial_mtime["$VAR_LIB/$f"]=0
100+
done
101+
fi
38102

39-
# tell mosquitto to store all retained topics in db now
40-
echo "sending 'SIGUSR1' to mosquitto processes"
41-
sudo pkill -e -SIGUSR1 mosquitto
42-
# give mosquitto some time to finish
43-
sleep 1
44-
45-
# create backup file
46-
echo "creating new backup file: $BACKUPFILE"
47-
echo "adding openWB files"
48-
tar --verbose --create \
49-
--file="$BACKUPFILE" \
50-
--directory="$TARBASEDIR/" \
51-
"$OPENWBDIRNAME/data/charge_log" \
52-
"$OPENWBDIRNAME/data/daily_log" \
53-
"$OPENWBDIRNAME/data/monthly_log" \
54-
"$OPENWBDIRNAME/data/log/uuid"
55-
echo "adding configuration file"
56-
sudo tar --verbose --append \
57-
--file="$BACKUPFILE" \
58-
--directory="/home/openwb/" \
59-
"configuration.json"
60-
echo "adding mosquitto files"
61-
sudo tar --verbose --append \
62-
--file="$BACKUPFILE" \
63-
--directory="/var/lib/" \
64-
"mosquitto/" "mosquitto_local/"
65-
echo "adding mosquitto configuration"
66-
sudo tar --verbose --append \
67-
--file="$BACKUPFILE" \
68-
--directory="/etc/mosquitto/" \
69-
"conf_local.d/"
70-
echo "adding boot file"
71-
sudo tar --verbose --append \
72-
--file="$BACKUPFILE" \
73-
"/boot/config.txt"
74-
echo "adding git information"
75-
git branch --no-color --show-current >"$RAMDISKDIR/GIT_BRANCH"
76-
git log --pretty='format:%H' -n1 >"$RAMDISKDIR/GIT_HASH"
77-
echo "branch: $(<"$RAMDISKDIR/GIT_BRANCH") commit-hash: $(<"$RAMDISKDIR/GIT_HASH")"
78-
tar --verbose --append \
79-
--file="$BACKUPFILE" \
80-
--directory="$RAMDISKDIR/" \
81-
"GIT_BRANCH" "GIT_HASH"
82-
83-
echo "calculating checksums"
84-
IFS=$'\n'
85-
mapfile -t file_list < <(tar -tf "$BACKUPFILE")
86-
mkdir -p "$TEMPDIR"
87-
# process each file
88-
for file in "${file_list[@]}"; do
89-
# skip directories
90-
if [[ $file =~ /$ ]]; then
91-
echo "skipping directory $file"
92-
continue
103+
echo "waiting for mosquitto to flush db files (timeout ${DB_TIMEOUT}s)..."
104+
105+
if command -v inotifywait >/dev/null 2>&1; then
106+
echo "using 'inotifywait'"
107+
local pending=()
108+
for f in "${DB_FILES[@]}"; do
109+
pending+=("$VAR_LIB/$f")
110+
done
111+
while ((${#pending[@]})); do
112+
local elapsed=$(( $(date +%s) - start_ts ))
113+
local remain=$(( DB_TIMEOUT - elapsed ))
114+
(( remain <= 0 )) && echo "timeout reached (inotify), continuing." && break
115+
inotifywait -q -t "$remain" -e modify -e close_write -e attrib -e move -e create "${pending[@]}" 2>/dev/null || true
116+
local new_pending=()
117+
for f in "${pending[@]}"; do
118+
if [ -e "$f" ]; then
119+
local mt
120+
mt=$(stat -c %Y "$f")
121+
if (( mt != initial_mtime["$f"] )); then
122+
echo "modified: $f (old: ${initial_mtime["$f"]} -> new: $mt)"
123+
else
124+
new_pending+=("$f")
125+
fi
126+
else
127+
new_pending+=("$f")
128+
fi
129+
done
130+
pending=("${new_pending[@]}")
131+
done
132+
else
133+
echo "'inotifywait' not found, falling back to polling"
134+
while true; do
135+
local all_ok=1
136+
for f in "${DB_FILES[@]}"; do
137+
if [ -e "$VAR_LIB/$f" ]; then
138+
local mt
139+
mt=$(stat -c %Y "$VAR_LIB/$f")
140+
if (( mt != initial_mtime["$VAR_LIB/$f"] )); then
141+
echo "modified (polling): $f (old: ${initial_mtime["$VAR_LIB/$f"]} -> new: $mt)"
142+
initial_mtime["$VAR_LIB/$f"]=$mt
143+
else
144+
all_ok=0
145+
fi
146+
else
147+
all_ok=0
148+
fi
149+
done
150+
local elapsed=$(( $(date +%s) - start_ts ))
151+
(( all_ok == 1 )) && echo "all relevant files are modified after ${elapsed}s" && break
152+
(( elapsed >= DB_TIMEOUT )) && echo "timeout reached (polling), continuing." && break
153+
sleep 0.1
154+
done
93155
fi
94-
# extract the file
95-
tar -xf "$BACKUPFILE" -C "$TEMPDIR" "$file"
96-
# calculate the checksum
97-
sha256sum "$TEMPDIR/$file" | sed -n "s|$TEMPDIR/||p" >> "$RAMDISKDIR/SHA256SUM"
98-
# remove the file
99-
rm -f "$TEMPDIR/$file"
100-
done
101-
tar --verbose --append \
102-
--file="$BACKUPFILE" \
103-
--directory="$RAMDISKDIR/" \
104-
"SHA256SUM"
105-
106-
# cleanup
107-
echo "removing temporary files"
108-
rm -v "$RAMDISKDIR/GIT_BRANCH" "$RAMDISKDIR/GIT_HASH" "$RAMDISKDIR/SHA256SUM"
109-
rm -rf "${TEMPDIR:?}/"
110-
tar --append \
111-
--file="$BACKUPFILE" \
112-
--directory="$LOGDIR/" \
113-
"backup.log"
114-
echo "zipping archive"
115-
gzip --verbose "$BACKUPFILE"
116-
echo "setting permissions of new backup file"
117-
sudo chown openwb:www-data "$BACKUPFILE.gz"
118-
sudo chmod 664 "$BACKUPFILE.gz"
156+
}
157+
158+
copy_db_to_temp() {
159+
echo "copying mosquitto db files to temporary location"
160+
for f in "${DB_FILES[@]}"; do
161+
if [ -e "$VAR_LIB/$f" ]; then
162+
local dest_dir
163+
dest_dir="$TEMPDIR/$(dirname "$f")"
164+
mkdir -p "$dest_dir"
165+
sudo cp -v "$VAR_LIB/$f" "$dest_dir/"
166+
else
167+
echo "skipping $f (does not exist)"
168+
fi
169+
done
170+
}
171+
172+
collect_baseline
173+
flush_mosquitto
174+
wait_for_mosquitto_flush
175+
copy_db_to_temp
176+
}
177+
178+
collect_git_info() {
179+
echo "collecting git information"
180+
git branch --no-color --show-current >"$TEMPDIR/GIT_BRANCH"
181+
git log --pretty='format:%H' -n1 >"$TEMPDIR/GIT_HASH"
182+
echo "branch: $(<"$TEMPDIR/GIT_BRANCH") commit-hash: $(<"$TEMPDIR/GIT_HASH")"
183+
}
184+
185+
create_archive() {
186+
create_backup() {
187+
echo "creating new backup file: $BACKUPFILE"
188+
echo "adding files"
189+
190+
sudo tar --verbose --create \
191+
--file="$BACKUPFILE" \
192+
--exclude=".gitignore" \
193+
--directory="$TEMPDIR/" \
194+
"${DB_FILES[@]}" \
195+
"GIT_BRANCH" \
196+
"GIT_HASH" \
197+
--directory="/etc/mosquitto/" \
198+
"conf_local.d/" \
199+
--directory="$TARBASEDIR/" \
200+
"$OPENWBDIRNAME/data/charge_log" \
201+
"$OPENWBDIRNAME/data/daily_log" \
202+
"$OPENWBDIRNAME/data/monthly_log" \
203+
"$OPENWBDIRNAME/data/log/uuid" \
204+
--directory="$HOMEDIR/" \
205+
"configuration.json" \
206+
--directory="/" \
207+
"boot/config.txt"
208+
}
119209

210+
calculate_checksums() {
211+
echo "calculating checksums"
212+
IFS=$'\n'
213+
mapfile -t file_list < <(tar -tf "$BACKUPFILE")
214+
# process each file
215+
for file in "${file_list[@]}"; do
216+
# skip directories
217+
if [[ $file =~ /$ ]]; then
218+
echo "skipping directory $file"
219+
continue
220+
fi
221+
# extract the file
222+
tar -xf "$BACKUPFILE" -C "$TEMPDIR" "$file"
223+
# calculate the checksum
224+
sha256sum "$TEMPDIR/$file" | sed -n "s|$TEMPDIR/||p" >> "$TEMPDIR/SHA256SUM"
225+
# remove the file
226+
rm -f "$TEMPDIR/$file"
227+
done
228+
229+
echo "adding checksum file to archive"
230+
sudo tar --verbose --append \
231+
--file="$BACKUPFILE" \
232+
--directory="$TEMPDIR/" \
233+
"SHA256SUM"
234+
}
235+
236+
cleanup_and_compress() {
237+
echo "removing temporary files"
238+
rm -rf "${TEMPDIR:?}/"
239+
echo "adding log file to archive"
240+
sudo tar --append \
241+
--file="$BACKUPFILE" \
242+
--directory="$LOGDIR/" \
243+
"backup.log"
244+
echo "zipping archive"
245+
gzip --verbose --suffix "$FILENAMESUFFIX" "$BACKUPFILE"
246+
}
247+
248+
fix_permissions() {
249+
echo "setting permissions of new backup file"
250+
sudo chown openwb:www-data "$BACKUPFILE$FILENAMESUFFIX"
251+
sudo chmod 664 "$BACKUPFILE$FILENAMESUFFIX"
252+
}
253+
254+
create_backup
255+
calculate_checksums
256+
cleanup_and_compress
257+
fix_permissions
258+
}
259+
260+
{
261+
generate_filename
262+
log_environment
263+
remove_old_backups
264+
collect_git_info
265+
force_mosquitto_write
266+
create_archive
120267
echo "backup finished"
121268
} >"$LOGFILE" 2>&1
122269

123270
# return our filename for further processing
124-
echo "$FILENAME.gz"
271+
echo "$FILENAME$FILENAMESUFFIX"

runs/install_packages.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
echo "install required packages with 'apt-get'..."
33
sudo apt-get -q update
44
sudo apt-get -q -y install \
5-
vim bc jq socat sshpass sudo ssl-cert mmc-utils \
5+
vim bc jq socat sshpass sudo ssl-cert mmc-utils inotify-tools \
66
apache2 libapache2-mod-php \
77
php php-gd php-curl php-xml php-json \
88
git \

web/settings/uploadFile.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ function check_gzip() {
2121
// quick check for file contents
2222
function check_restore_file_contents() {
2323
$output = null;
24-
$result = exec("tar --list --file=\"" . $_FILES["file"]["tmp_name"] . "\" | grep -c \"^\(mosquitto/\|mosquitto_local/\|GIT_HASH\|GIT_BRANCH\|SHA256SUM\|backup.log\)$\"", $output);
24+
$result = exec("tar --list --file=\"" . $_FILES["file"]["tmp_name"] . "\" | egrep -c \"^(mosquitto/.+|mosquitto_local/.+|GIT_HASH|GIT_BRANCH|SHA256SUM|backup.log)$\"", $output);
2525
if ($result === false || $result != "6") {
2626
exit_with_error("Prüfung des Archivinhalts fehlgeschlagen! Das Archiv ist unvollständig.");
2727
}
28-
$result = exec("tar --list --file=\"" . $_FILES["file"]["tmp_name"] . "\" | grep -c \"^openWB/.*\"", $output);
28+
$result = exec("tar --list --file=\"" . $_FILES["file"]["tmp_name"] . "\" | grep -c \"^openWB/\"", $output);
2929
if ($result === false || $result <= 4) {
3030
exit_with_error("Prüfung des Archivinhalts fehlgeschlagen! Das Archiv ist unvollständig.");
3131
}

0 commit comments

Comments
 (0)