8a18c894dd
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>
131 lines
5.0 KiB
Bash
Executable File
131 lines
5.0 KiB
Bash
Executable File
#!/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
|