Skip to content

Commit e57702b

Browse files
jasonacoxjasonacox-sam
authored andcommitted
fix: PWD_USER consistency, CQ error detection, staging disk-space guards
- PWD_USER now derives as SUDO_UID:SUDO_GID (invoking user's uid and primary gid) — the exact same uid:gid convention setup.sh writes — so a restore never silently changes how an install is owned. Drops the docker-GID pairing, which was self-consistent but diverged from the rest of the project. Root-shell (UID 0) guard retained. - CQ replay now detects failures: the influx 1.x CLI prints statement errors to stdout and exits 0 regardless, so the previous exit-code check reported success even if every CREATE failed. New replay_cqs helper captures output, filters harmless 'already exists' responses, greps for ERR, and surfaces the first errors to the user. - Staging disk-space checks: restore.sh warns (with sizes and a TMPDIR override hint) if the staging filesystem can't hold ~6x the compressed archive; backup.sh aborts before copying a snapshot that won't fit (protects RAM-backed /tmp from multi-GB datasets, cron-safe: no prompt). - Docs: README large-dataset/TMPDIR note, updated restore step list, RELEASE.md v5.3.0 entry updated to match final behavior (also folds in the previously-unreleased jasonacox#840 datasource UID note post-rebase).
1 parent 1117ed4 commit e57702b

4 files changed

Lines changed: 94 additions & 26 deletions

File tree

RELEASE.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
### New Features
1010

1111
* **Restore script** — new `backups/restore.sh.sample`, an automated companion to `backup.sh.sample`, based on `restore_v16a.sh` by **@JonMurphy** ([#836](https://github.com/jasonacox/Powerwall-Dashboard/issues/836)). One command restores a backup archive on the same machine or migrates to a new one:
12-
- Auto-detects the Powerwall-Dashboard directory by locating `compose-dash.sh`
12+
- Auto-detects the Powerwall-Dashboard directory from the script's own location
13+
- Checks staging disk space before extracting (warns and offers `TMPDIR` override — protects RAM-backed `/tmp` from multi-GB archives)
1314
- Non-destructive — moves existing InfluxDB `data`/`meta`/`wal` and `grafana.db` aside as `.pre-restore.<timestamp>` copies (rollback path) instead of deleting
14-
- Restores InfluxDB from the `influxd backup -portable` snapshot into a clean instance, then re-creates continuous queries from `influxdb.sql` (`influxd restore -portable` does not reliably restore CQs — a known InfluxDB 1.x limitation)
15-
- Restores Grafana database and provisioning files with correct ownership, deriving `PWD_USER` from live filesystem state (`compose-dash.sh` owner + docker group GID)
15+
- Restores InfluxDB from the `influxd backup -portable` snapshot into a clean instance, then re-creates continuous queries from the archive's `continuous_queries.txt` (live CQ state, including customizations) with `influxdb.sql` as fallback — `influxd restore -portable` does not reliably restore CQs (known InfluxDB 1.x limitation). CQ replay output is checked for errors (the influx CLI exits 0 even on failed statements).
16+
- Restores Grafana database and provisioning files with correct ownership, deriving `PWD_USER` as the invoking user's `uid:gid` (`SUDO_UID:SUDO_GID`) — the same convention `setup.sh` uses — with a guard against running from a root shell
1617
- Restores user configuration files (`*.env`, `telegraf.local`, etc.) while **skipping git-managed project files** (`powerwall.yml`, `telegraf.conf`, `influxdb.conf`, `VERSION`) so an older backup can never downgrade the stack or break future `git pull`/upgrades
1718
- Finishes with `compose-dash.sh up -d` so containers are **recreated** and restored settings (including the rewritten `PWD_USER`) actually take effect
18-
* **Backup script**`backup.sh.sample` now auto-detects the dashboard directory (no more editing `DASHBOARD=` by hand) and exports `SHOW CONTINUOUS QUERIES` output into the archive as a safety net.
19+
* **Backup script**`backup.sh.sample` now auto-detects the dashboard directory (no more editing `DASHBOARD=` by hand), exports live continuous queries into the archive as a safety net, and aborts with a clear message if the staging area can't hold the snapshot (with `TMPDIR` override).
1920

2021
### Documentation
2122

backups/README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ The following shows an example of how to set up automated backups (see backup.sh
4545
2. Make the script executable with `chmod +x backup.sh` (the script auto-detects the dashboard location by finding `compose-dash.sh`)
4646
3. Add to crontab for daily backups: `0 2 * * * /home/user/Powerwall-Dashboard/backups/backup.sh`
4747

48+
> **Large datasets:** both `backup.sh` and `restore.sh` stage data in a temporary directory (`mktemp -d`, usually under `/tmp`). If your InfluxDB history is large (multi-GB) and `/tmp` is a RAM-backed tmpfs, staging there can exhaust memory. Both scripts check available space first — backup aborts and restore warns — and you can point staging at a disk with more room: `sudo TMPDIR=/path/with/space ./backup.sh`
49+
4850
## Backup Script Example
4951

5052
```bash
@@ -55,8 +57,8 @@ if [ "$EUID" -ne 0 ]
5557
exit
5658
fi
5759

58-
# Dashboard location is auto-detected (script-relative, with a fallback
59-
# search for compose-dash.sh) - see backup.sh.sample
60+
# Dashboard location is auto-detected from the script's own location
61+
# (must live in Powerwall-Dashboard/backups/) - see backup.sh.sample
6062
BACKUP_FOLDER="${DASHBOARD}/backups" # Destination folder for backups
6163
KEEP="5" # Days to keep backup
6264

@@ -89,11 +91,12 @@ A companion `restore.sh.sample` is provided to automate the restore process. It
8991

9092
The restore script will:
9193
1. **Auto-detect** the Powerwall-Dashboard directory by locating `compose-dash.sh`
92-
2. **Stop all containers** before touching data
93-
3. **Restore InfluxDB** from the `influxd backup -portable` snapshot, moving existing data aside first (not deleted — you get a rollback path)
94-
4. **Restore Grafana** database and provisioning files with correct ownership
95-
5. **Restore configuration files**, rewriting `PWD_USER` in `compose.env` to match this host's actual user and docker group. Project files managed by git (`powerwall.yml`, `telegraf.conf`, `influxdb.conf`, `VERSION`) are kept in the archive for reference but are NOT restored over the current checkout — this prevents an older backup from downgrading the stack or breaking future upgrades.
96-
6. **Recreate the stack** (`compose-dash.sh up -d`, so restored settings take effect) and print a list of pre-restore backup paths to clean up once confirmed
94+
2. **Check staging disk space** and warn before extracting a large archive into a location that can't hold it (use `sudo TMPDIR=/path/with/space ./restore.sh` to relocate staging)
95+
3. **Stop all containers** before touching data
96+
4. **Restore InfluxDB** from the `influxd backup -portable` snapshot, moving existing data aside first (not deleted — you get a rollback path), then re-create continuous queries from the archive (with `influxdb.sql` as fallback)
97+
5. **Restore Grafana** database and provisioning files with correct ownership
98+
6. **Restore configuration files**, rewriting `PWD_USER` in `compose.env` to match this host's actual user and primary group (same `uid:gid` convention as `setup.sh`). Project files managed by git (`powerwall.yml`, `telegraf.conf`, `influxdb.conf`, `VERSION`) are kept in the archive for reference but are NOT restored over the current checkout — this prevents an older backup from downgrading the stack or breaking future upgrades.
99+
7. **Recreate the stack** (`compose-dash.sh up -d`, so restored settings take effect) and print a list of pre-restore backup paths to clean up once confirmed
97100

98101
### Manual restore from a backup script archive
99102

backups/backup.sh.sample

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,26 @@ mkdir -p influxdb/backups
6262
chmod g+w influxdb/backups
6363
docker exec influxdb influxd backup -portable -database powerwall /var/lib/influxdb/backups
6464

65+
# ── Check staging disk space ──────────────────────────────────────────
66+
# The snapshot is copied into ${STAGING} (usually under /tmp) before being
67+
# archived. On systems where /tmp is a RAM-backed tmpfs, a multi-GB
68+
# snapshot can exhaust memory. Abort (rather than risk OOM) if the staging
69+
# filesystem can't hold the snapshot plus 10% headroom. Override the
70+
# staging location with: sudo TMPDIR=/path/with/space ./backup.sh
71+
SNAPSHOT_KB=$(du -sk "${DASHBOARD}/influxdb/backups" | cut -f1)
72+
NEEDED_KB=$((SNAPSHOT_KB + SNAPSHOT_KB / 10))
73+
AVAIL_KB=$(df -Pk "${STAGING}" | awk 'NR==2 {print $4}')
74+
if [ -n "${AVAIL_KB}" ] && [ "${AVAIL_KB}" -lt "${NEEDED_KB}" ]; then
75+
echo "ERROR: Staging area ${STAGING} does not have enough free space."
76+
echo " Snapshot size: $((SNAPSHOT_KB / 1024)) MB"
77+
echo " Available: $((AVAIL_KB / 1024)) MB"
78+
echo "If /tmp is a RAM-backed tmpfs, staging there can exhaust memory."
79+
echo "Point staging at a disk with more space and re-run:"
80+
echo " sudo TMPDIR=/path/with/space $0"
81+
rm -rf "${DASHBOARD}/influxdb/backups"/*
82+
exit 1
83+
fi
84+
6585
# Copy the snapshot into staging (the backup files are the consistent copy)
6686
mkdir -p "${STAGING}/influxdb"
6787
cp -a "${DASHBOARD}/influxdb/backups/." "${STAGING}/influxdb/"

backups/restore.sh.sample

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ if [ ! -d "${BACKUP_FOLDER}" ]; then
3737
fi
3838

3939
# ── Determine this host's correct owner:group for container files ─────
40-
# Under sudo, SUDO_UID gives the invoking user (not root). Pair that with
41-
# the docker GID so container files are owned by the real user + docker group.
42-
# Falls back to id -u/id -g when not under sudo.
43-
DOCKER_GID=$(getent group docker 2>/dev/null | cut -d: -f3)
40+
# Under sudo, SUDO_UID/SUDO_GID give the invoking user (not root).
41+
# This matches setup.sh, which derives PWD_USER as "$(id -u):$(id -g)" of
42+
# the installing user — keeping the same uid:primary-gid convention means a
43+
# restore never changes how the install is owned.
4444
HOST_UID="${SUDO_UID:-$(id -u)}"
4545
HOST_GID="${SUDO_GID:-$(id -g)}"
4646

@@ -54,7 +54,7 @@ if [ "${HOST_UID}" -eq 0 ] 2>/dev/null; then
5454
exit 1
5555
fi
5656

57-
PWD_USER="${HOST_UID}:${DOCKER_GID:-${HOST_GID}}"
57+
PWD_USER="${HOST_UID}:${HOST_GID}"
5858
echo "Detected owner:group for this host: ${PWD_USER}"
5959

6060
# ── Determine which archive to restore ────────────────────────────────
@@ -82,6 +82,31 @@ cleanup() {
8282
}
8383
trap cleanup EXIT
8484

85+
# ── Check staging disk space ─────────────────────────────────────────────
86+
# The full archive is extracted into ${STAGING} (usually under /tmp). On
87+
# systems where /tmp is a RAM-backed tmpfs, a multi-GB archive can exhaust
88+
# memory. xz compresses time-series data heavily, so require 6x the archive
89+
# size as a conservative uncompressed estimate. Override the staging
90+
# location with: sudo TMPDIR=/path/with/space ./restore.sh
91+
ARCHIVE_KB=$(du -k "${ARCHIVE}" | cut -f1)
92+
NEEDED_KB=$((ARCHIVE_KB * 6))
93+
AVAIL_KB=$(df -Pk "${STAGING}" | awk 'NR==2 {print $4}')
94+
if [ -n "${AVAIL_KB}" ] && [ "${AVAIL_KB}" -lt "${NEEDED_KB}" ]; then
95+
echo "WARNING: Staging area ${STAGING} may not have enough free space."
96+
echo " Archive size: $((ARCHIVE_KB / 1024)) MB (compressed)"
97+
echo " Estimated needed: $((NEEDED_KB / 1024)) MB (uncompressed estimate)"
98+
echo " Available: $((AVAIL_KB / 1024)) MB"
99+
echo "If /tmp is a RAM-backed tmpfs, extraction can exhaust memory."
100+
echo "Point staging at a disk with more space and re-run:"
101+
echo " sudo TMPDIR=/path/with/space $0 ${1:-}"
102+
echo ""
103+
read -r -p "Continue anyway? [y/N] " SPACE_CONFIRM
104+
if [[ ! "${SPACE_CONFIRM}" =~ ^([yY][eE][sS]|[yY])$ ]]; then
105+
echo "Restore cancelled."
106+
exit 1
107+
fi
108+
fi
109+
85110
echo "========================================="
86111
echo " Powerwall-Dashboard Restore - ${STAMP}"
87112
echo "========================================="
@@ -169,22 +194,41 @@ if [ -d "${STAGING}/influxdb" ] && [ -n "$(ls -A "${STAGING}/influxdb" 2>/dev/nu
169194
# SHOW CONTINUOUS QUERIES table output (older versions). Handle both.
170195
# Each statement must be terminated with ';' so the influx CLI can parse
171196
# multiple CREATE CONTINUOUS QUERY lines piped via stdin.
197+
#
198+
# NOTE: the influx 1.x CLI prints statement errors to STDOUT and still
199+
# exits 0, so exit-code checks alone would report success even when every
200+
# statement failed. replay_cqs captures the output and greps it for ERR
201+
# instead. "already exists" responses are harmless and ignored.
202+
replay_cqs() {
203+
# stdin: CREATE CONTINUOUS QUERY statements, ';'-terminated
204+
local OUT
205+
OUT=$(docker exec -i influxdb influx -database powerwall 2>&1)
206+
if echo "${OUT}" | grep -vi 'already exists' | grep -qiE '(^|\s)ERR'; then
207+
echo "${OUT}" | grep -iE '(^|\s)ERR' | head -5 | sed 's/^/ /'
208+
return 1
209+
fi
210+
return 0
211+
}
212+
172213
CQ_RESTORED=0
173214
if [ -f "${STAGING}/influxdb/continuous_queries.txt" ] && grep -q 'CREATE CONTINUOUS QUERY' "${STAGING}/influxdb/continuous_queries.txt"; then
174215
# Extract just the CREATE statement from each line (handles both
175216
# clean format and raw table output with query_id prefix), append ';'.
176-
sed -n 's/.*\(CREATE CONTINUOUS QUERY.*\)/\1/p' "${STAGING}/influxdb/continuous_queries.txt" \
177-
| sed 's/$/;/' \
178-
| docker exec -i influxdb influx -database powerwall > /dev/null \
179-
&& { echo " ✓ Continuous queries re-created from backup (continuous_queries.txt)"; CQ_RESTORED=1; } \
180-
|| echo " ⚠ Failed to re-create continuous queries from continuous_queries.txt"
217+
if sed -n 's/.*\(CREATE CONTINUOUS QUERY.*\)/\1/p' "${STAGING}/influxdb/continuous_queries.txt" \
218+
| sed 's/$/;/' | replay_cqs; then
219+
echo " ✓ Continuous queries re-created from backup (continuous_queries.txt)"
220+
CQ_RESTORED=1
221+
else
222+
echo " ⚠ Errors re-creating continuous queries from continuous_queries.txt (see above)"
223+
fi
181224
fi
182225
if [ "${CQ_RESTORED}" -eq 0 ] && [ -f "${DASHBOARD}/influxdb/influxdb.sql" ]; then
183-
grep '^CREATE CONTINUOUS QUERY' "${DASHBOARD}/influxdb/influxdb.sql" \
184-
| sed 's/$/;/' \
185-
| docker exec -i influxdb influx -database powerwall > /dev/null \
186-
&& echo " ✓ Continuous queries re-created from influxdb.sql (repo defaults)" \
187-
|| echo " ⚠ Failed to re-create continuous queries — see influxdb/influxdb.sql"
226+
if grep '^CREATE CONTINUOUS QUERY' "${DASHBOARD}/influxdb/influxdb.sql" \
227+
| sed 's/$/;/' | replay_cqs; then
228+
echo " ✓ Continuous queries re-created from influxdb.sql (repo defaults)"
229+
else
230+
echo " ⚠ Errors re-creating continuous queries — see influxdb/influxdb.sql and output above"
231+
fi
188232
fi
189233

190234
rm -rf influxdb/backups/*

0 commit comments

Comments
 (0)