-
Notifications
You must be signed in to change notification settings - Fork 10
feat(docker): backup OpenSPP filestore (#66) #304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 19.0
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |||||||||||||||||||||
| # Environment variables: | ||||||||||||||||||||||
| # PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE (standard PostgreSQL vars) | ||||||||||||||||||||||
| # BACKUP_DIR (default: /backups) | ||||||||||||||||||||||
| # FILESTORE_SRC (default: /odoo_data/filestore/$PGDATABASE) | ||||||||||||||||||||||
| # BACKUP_KEEP_DAYS (default: 7) | ||||||||||||||||||||||
| # BACKUP_KEEP_WEEKS (default: 4) | ||||||||||||||||||||||
| # BACKUP_KEEP_MONTHS (default: 6) | ||||||||||||||||||||||
|
|
@@ -52,15 +53,33 @@ ln -sf "${BACKUP_FILE}" "${DAILY_DIR}/${PGDATABASE:-openspp}_latest.dump" | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| echo "[$(date -Iseconds)] Daily backup complete: ${BACKUP_FILE}" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Filestore backup (attachments, documents) when odoo data volume is mounted | ||||||||||||||||||||||
| FILESTORE_SRC="${FILESTORE_SRC:-/odoo_data/filestore/${PGDATABASE:-openspp}}" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No lock around the run.
exec 9>"${BACKUP_DIR}/.backup.lock"
flock -n 9 || { echo "[$(date -Iseconds)] Backup already running; skipping"; exit 0; }Strictly a pre-existing gap that this change amplifies — fine to split into its own PR if you'd rather keep this one tight, but please don't drop it. |
||||||||||||||||||||||
| FILESTORE_BACKUP_FILE="${PGDATABASE:-openspp}_filestore_${TIMESTAMP}.tar.gz" | ||||||||||||||||||||||
| if [ -d "${FILESTORE_SRC}" ]; then | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gzip is close to pure cost on a filestore. Measured in the backup image at the nginx stack's An OpenSPP filestore is dominated by scanned documents and photos, which are already compressed — so that's roughly 38 s/GB of pinned CPU for a rounding error. A 100 GB filestore is about an hour of CPU-bound work every night, on a service capped at half a core. Real filestores do hold some compressible content (web asset bundles, XML, text attachments), so the saving isn't literally zero, but the ratio is bad enough to be worth a knob. Either default to plain |
||||||||||||||||||||||
| echo "[$(date -Iseconds)] Starting filestore backup from ${FILESTORE_SRC}..." | ||||||||||||||||||||||
| tar -czf "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}" -C "$(dirname "${FILESTORE_SRC}")" "$(basename "${FILESTORE_SRC}")" | ||||||||||||||||||||||
| ln -sf "${FILESTORE_BACKUP_FILE}" "${DAILY_DIR}/${PGDATABASE:-openspp}_filestore_latest.tar.gz" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking — a The bot flagged this and it holds up under test. I ran the two race cases inside the exact backup image (
The unlink case is precisely what Odoo does on a schedule:
It also fails quietly: crond only writes A command used as an if [ "${BACKUP_FILESTORE}" != "true" ]; then
echo "[$(date -Iseconds)] Filestore backup disabled (BACKUP_FILESTORE=${BACKUP_FILESTORE})"
elif [ ! -d "${FILESTORE_SRC}" ]; then
echo "[$(date -Iseconds)] Filestore not found at ${FILESTORE_SRC}; skipping filestore backup"
else
echo "[$(date -Iseconds)] Starting filestore backup from ${FILESTORE_SRC}..."
# tar exits 1 when Odoo's filestore GC unlinks a file mid-archive. Running it
# as an `if` condition keeps `set -e` from skipping the retention pass below.
if tar -czf "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}.part" \
-C "$(dirname "${FILESTORE_SRC}")" "$(basename "${FILESTORE_SRC}")"; then
mv "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}.part" "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}"
ln -sf "${FILESTORE_BACKUP_FILE}" "${DAILY_DIR}/${PGDATABASE:-openspp}_filestore_latest.tar.gz"
echo "[$(date -Iseconds)] Filestore backup complete: ${FILESTORE_BACKUP_FILE}"
else
rm -f "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}.part"
echo "[$(date -Iseconds)] WARNING: filestore backup failed; database dump kept"
fi
fi( |
||||||||||||||||||||||
| echo "[$(date -Iseconds)] Filestore backup complete: ${FILESTORE_BACKUP_FILE}" | ||||||||||||||||||||||
|
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Running Wrapping the
Suggested change
|
||||||||||||||||||||||
| else | ||||||||||||||||||||||
| echo "[$(date -Iseconds)] Filestore not found at ${FILESTORE_SRC}; skipping filestore backup" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Weekly backup (Sunday) | ||||||||||||||||||||||
| if [ "${DAY_OF_WEEK}" = "7" ]; then | ||||||||||||||||||||||
| cp "${DAILY_DIR}/${BACKUP_FILE}" "${WEEKLY_DIR}/" | ||||||||||||||||||||||
| if [ -f "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}" ]; then | ||||||||||||||||||||||
| cp "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}" "${WEEKLY_DIR}/" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
| echo "[$(date -Iseconds)] Weekly backup saved" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Monthly backup (1st of month) | ||||||||||||||||||||||
| if [ "${DAY_OF_MONTH}" = "01" ]; then | ||||||||||||||||||||||
| cp "${DAILY_DIR}/${BACKUP_FILE}" "${MONTHLY_DIR}/" | ||||||||||||||||||||||
| if [ -f "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}" ]; then | ||||||||||||||||||||||
| cp "${DAILY_DIR}/${FILESTORE_BACKUP_FILE}" "${MONTHLY_DIR}/" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
| echo "[$(date -Iseconds)] Monthly backup saved" | ||||||||||||||||||||||
| fi | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -69,12 +88,15 @@ echo "[$(date -Iseconds)] Cleaning up old backups..." | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Remove daily backups older than BACKUP_KEEP_DAYS | ||||||||||||||||||||||
| find "${DAILY_DIR}" -name "*.dump" -type f -mtime +${BACKUP_KEEP_DAYS} -delete 2>/dev/null || true | ||||||||||||||||||||||
| find "${DAILY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +${BACKUP_KEEP_DAYS} -delete 2>/dev/null || true | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking — filestore archives need their own retention knobs, and the feature needs to be opt-in. These three lines reuse the DB-dump retention policy verbatim for full filestore copies. With Two changes, please: 1. Make it opt-in, defaulting off. Add alongside the existing defaults at the top of the script (lines 21-25): BACKUP_FILESTORE="${BACKUP_FILESTORE:-false}"Leave the compose mount unconditional — it's harmless when the toggle is off — and gate the work on the variable (see the 2. Give the filestore its own retention knobs, defaulting to the DB values. Declared after the BACKUP_FILESTORE_KEEP_DAYS="${BACKUP_FILESTORE_KEEP_DAYS:-${BACKUP_KEEP_DAYS}}"
BACKUP_FILESTORE_KEEP_WEEKS="${BACKUP_FILESTORE_KEEP_WEEKS:-${BACKUP_KEEP_WEEKS}}"
BACKUP_FILESTORE_KEEP_MONTHS="${BACKUP_FILESTORE_KEEP_MONTHS:-${BACKUP_KEEP_MONTHS}}"then use them here: find "${DAILY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +${BACKUP_FILESTORE_KEEP_DAYS} -delete 2>/dev/null || true
find "${WEEKLY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +$((BACKUP_FILESTORE_KEEP_WEEKS * 7)) -delete 2>/dev/null || true
find "${MONTHLY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +$((BACKUP_FILESTORE_KEEP_MONTHS * 30)) -delete 2>/dev/null || trueOut of the box this behaves identically to what you have — the defaults align with the DB knobs — but an operator can now age filestore copies out far faster than dumps without touching their dump policy. One detail: keep these three |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Remove weekly backups older than BACKUP_KEEP_WEEKS weeks | ||||||||||||||||||||||
| find "${WEEKLY_DIR}" -name "*.dump" -type f -mtime +$((BACKUP_KEEP_WEEKS * 7)) -delete 2>/dev/null || true | ||||||||||||||||||||||
| find "${WEEKLY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +$((BACKUP_KEEP_WEEKS * 7)) -delete 2>/dev/null || true | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Remove monthly backups older than BACKUP_KEEP_MONTHS months (approximate: 30 days per month) | ||||||||||||||||||||||
| find "${MONTHLY_DIR}" -name "*.dump" -type f -mtime +$((BACKUP_KEEP_MONTHS * 30)) -delete 2>/dev/null || true | ||||||||||||||||||||||
| find "${MONTHLY_DIR}" -name "*_filestore_*.tar.gz" -type f -mtime +$((BACKUP_KEEP_MONTHS * 30)) -delete 2>/dev/null || true | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Report disk usage | ||||||||||||||||||||||
| echo "[$(date -Iseconds)] Backup sizes:" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -427,6 +427,7 @@ services: | |
| - ./backup.sh:/backup.sh:ro,z | ||
| - ./backup-entrypoint.sh:/backup-entrypoint.sh:ro,z | ||
| - backup_data:/backups:rw,z | ||
| - odoo_data:/odoo_data:ro,z | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing here has been executed yet, including by CI. Both test-plan checkboxes are unticked and the note on #66 says the runtime test needs a production stack — so as far as I can tell this code has never run. CI hasn't covered the gap either: the head repo is Worth exercising this stack specifically rather than the Traefik one, because it's the more constrained of the two: |
||
| networks: | ||
| - openspp-prod | ||
| restart: unless-stopped | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -333,6 +333,7 @@ services: | |
| - ./backup.sh:/backup.sh:ro | ||
| - ./backup-entrypoint.sh:/backup-entrypoint.sh:ro | ||
| - backup_data:/backups | ||
| - odoo_data:/odoo_data:ro | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new settings aren't reachable from configuration.
# Filestore backup (opt-in; see docker/README.md for sizing)
BACKUP_FILESTORE: ${BACKUP_FILESTORE:-false}
BACKUP_FILESTORE_KEEP_DAYS: ${BACKUP_FILESTORE_KEEP_DAYS:-${BACKUP_KEEP_DAYS:-7}}
BACKUP_FILESTORE_KEEP_WEEKS: ${BACKUP_FILESTORE_KEEP_WEEKS:-${BACKUP_KEEP_WEEKS:-4}}
BACKUP_FILESTORE_KEEP_MONTHS: ${BACKUP_FILESTORE_KEEP_MONTHS:-${BACKUP_KEEP_MONTHS:-6}}and give them a block in One trap to avoid: |
||
| networks: | ||
| - openspp-prod | ||
| restart: always | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore procedure is missing, and this is the part issue #66 actually turns on.
A DB/filestore mismatch is the whole bug in #66, so an archive nobody knows how to restore doesn't close it. The "To restore a backup" section right below (line 222) still only covers
pg_restore. It needs the filestore counterpart: untar into theodoo_datavolume, with the ownership Odoo expects, and paired with the dump carrying the same timestamp — the two filenames shareTIMESTAMP, which is a nice property worth spelling out for operators.Two more things while you're in this section:
BACKUP_FILESTOREdefaults tofalse, this bullet's "When theodoo_datavolume is mounted on the backup service" is no longer the trigger. Reword to name the toggle, and add a sizing warning —BACKUP_FILESTORE_KEEP_*× filestore size is the number that bites people.openspp-YYYYMMDD-HHMMSS.sql.gz, while the script actually writesopenspp_YYYYMMDD_HHMMSS.dump(underscores,pg_dump -Fccustom format). Worth correcting in passing.