Files
experiments-database/scripts/backup.sh
T
Experiments DB Dev 8a18c894dd feat(matlab): add Satterthwaite DF + honest random-slope test to LME reports
Every fitlme-based report (lme_*, paper_*, phase_*, and the variations'
analyze.m) now shows, per effect: residual-DF p, Satterthwaite-DF p, and --
for the interaction -- an HONEST test from a per-animal random-SLOPE model
(day|rat), whose Satterthwaite DF collapses toward the animal count.

New: tdcs_random_slope_interaction.m (shared helper). Wired into tdcs_lme,
tdcs_paper_lme, tdcs_phase_lme, variation_analyze; SUMMARY.csv gains
interaction_p_satt / interaction_p_rs. Regenerated all results/, variations/,
matched-effort outputs.

Key point this surfaces: Satterthwaite ~= residual on the random-INTERCEPT
model (the slope's error is at session level), so it does NOT fix
pseudoreplication; the random-slope model does. Effect: full-range mergeA2
interaction 0.009 -> 0.75 (collapses); unmerge_d0_5 0.015 -> 0.13 (n.s.);
the pooled-control early windows survive honestly (naive_a2_d0_5 0.001 ->
0.028; naive_boxa_d0_5 0.003 -> 0.036). Suite 42/42.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 15:45:19 -04:00

131 lines
5.0 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# Experiments DB — incremental backup script
#
# Usage:
# backup.sh daily backup only if DB changed since last run; keep 5
# backup.sh weekly unconditional backup; keep 2
#
# Cron (added by setup_cron.sh):
# 0 2 * * * /home/sam/docker-images/experiments-database/scripts/backup.sh daily
# 0 3 * * 0 /home/sam/docker-images/experiments-database/scripts/backup.sh weekly
# =============================================================================
set -euo pipefail
# ── Config ────────────────────────────────────────────────────────────────────
MODE="${1:-daily}"
BACKUP_ROOT="/home/sam/synology/Backups/Experiments-DB-Backup"
DAILY_DIR="$BACKUP_ROOT/daily"
WEEKLY_DIR="$BACKUP_ROOT/weekly"
STATE_FILE="$BACKUP_ROOT/.last_backup_state"
LOG_FILE="$BACKUP_ROOT/backup.log"
KEEP_DAILY=5
KEEP_WEEKLY=2
PG_CONTAINER="experiments_postgres"
PG_USER="expuser"
PG_DB="experiments_db"
# ── Helpers ───────────────────────────────────────────────────────────────────
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
die() { log "ERROR: $*"; exit 1; }
# Ensure directories exist
mkdir -p "$DAILY_DIR" "$WEEKLY_DIR"
# Check postgres container is running
if ! docker inspect --format '{{.State.Running}}' "$PG_CONTAINER" 2>/dev/null | grep -q true; then
die "Container $PG_CONTAINER is not running."
fi
# ── DB state fingerprint ──────────────────────────────────────────────────────
# Combines row counts from all key tables + latest audit_log timestamp.
# Changes in any table (insert, update, delete) alter this string.
get_db_state() {
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -A -c "
SELECT
(SELECT COUNT(*) FROM experiments) || ',' ||
(SELECT COUNT(*) FROM animals) || ',' ||
(SELECT COUNT(*) FROM daily_statuses) || ',' ||
(SELECT COUNT(*) FROM daily_analyses) || ',' ||
(SELECT COUNT(*) FROM session_files) || ',' ||
COALESCE(
(SELECT MAX(timestamp)::text FROM audit_logs),
'no-audit'
)
AS fingerprint;
" 2>/dev/null | tr -d '[:space:]'
}
# ── Dump function ─────────────────────────────────────────────────────────────
run_dump() {
local dest="$1"
local tmp="${dest}.tmp"
log "Dumping → $dest"
docker exec "$PG_CONTAINER" pg_dump \
-U "$PG_USER" \
--format=custom \
--compress=9 \
"$PG_DB" > "$tmp" \
&& mv "$tmp" "$dest" \
|| { rm -f "$tmp"; die "pg_dump failed."; }
local size
size=$(du -sh "$dest" | cut -f1)
log "Backup written ($size): $(basename "$dest")"
}
# ── Prune old backups ─────────────────────────────────────────────────────────
prune() {
local dir="$1"
local keep="$2"
local count
count=$(ls -1 "$dir"/*.pgdump 2>/dev/null | wc -l)
if (( count > keep )); then
ls -1t "$dir"/*.pgdump | tail -n "+$((keep + 1))" | while read -r f; do
log "Pruning old backup: $(basename "$f")"
rm -f "$f"
done
fi
}
# ── Main ──────────────────────────────────────────────────────────────────────
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
if [[ "$MODE" == "daily" ]]; then
log "--- Daily backup check ---"
CURRENT_STATE=$(get_db_state)
if [[ -z "$CURRENT_STATE" ]]; then
die "Could not read DB state."
fi
LAST_STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "")
if [[ "$CURRENT_STATE" == "$LAST_STATE" ]]; then
log "No changes detected since last backup. Skipping."
exit 0
fi
DEST="$DAILY_DIR/experiments_db_daily_${TIMESTAMP}.pgdump"
run_dump "$DEST"
# Save new state only after successful dump
echo "$CURRENT_STATE" > "$STATE_FILE"
log "State fingerprint updated."
prune "$DAILY_DIR" "$KEEP_DAILY"
log "Daily backup complete."
elif [[ "$MODE" == "weekly" ]]; then
log "--- Weekly backup (unconditional) ---"
DEST="$WEEKLY_DIR/experiments_db_weekly_${TIMESTAMP}.pgdump"
run_dump "$DEST"
prune "$WEEKLY_DIR" "$KEEP_WEEKLY"
log "Weekly backup complete."
else
die "Unknown mode '$MODE'. Use 'daily' or 'weekly'."
fi