File size: 4,855 Bytes
46252cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env bash
#
# OpenWA backup.
#
# Captures the load-bearing state needed to restore a working install:
#   - main.sqlite   β€” auth (API keys) + audit log, ALWAYS SQLite (see app.module.ts)
#   - data store    β€” openwa.sqlite (SQLite) OR a pg_dump (when DATABASE_TYPE=postgres)
#   - sessions/     β€” whatsapp-web.js LocalAuth session data
#   - baileys/      β€” Baileys engine authentication state
#   - media/        β€” locally-stored media (skipped automatically when using S3)
#   - plugin-packages/ β€” installed plugin packages from PLUGINS_DIR
#   - plugin-state/    β€” registry and persisted ctx.storage state under OPENWA_DATA_DIR
#   - .env.generated and .api-key β€” dashboard config and plaintext bootstrap admin key
#
# The previous runbook backed up the wrong file (openwa.db) and omitted main.sqlite,
# so a "successful" backup silently lost every API key and all audit history.
#
# Usage:
#   ./scripts/backup.sh
# Environment:
#   OPENWA_DATA_DIR   data directory (default: ./data)
#   BACKUP_DIR        where archives are written (default: ./backups)
#   DATABASE_TYPE     sqlite (default) | postgres
#   SESSION_DATA_PATH, BAILEYS_AUTH_DIR, STORAGE_LOCAL_PATH, PLUGINS_DIR
#                     override the corresponding state directories
#   For postgres: DATABASE_URL, or DATABASE_HOST/PORT/USERNAME/PASSWORD/NAME
#
set -euo pipefail
# The archive now contains bootstrap credentials and generated database secrets. Never inherit a
# permissive operator umask for newly-created backup artifacts.
umask 077

DATA_DIR="${OPENWA_DATA_DIR:-./data}"
BACKUP_DIR="${BACKUP_DIR:-./backups}"
DATABASE_TYPE="${DATABASE_TYPE:-sqlite}"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"

MAIN_DB="$DATA_DIR/main.sqlite"
DATA_DB="$DATA_DIR/openwa.sqlite"
SESSIONS_DIR="${SESSION_DATA_PATH:-$DATA_DIR/sessions}"
BAILEYS_DIR="${BAILEYS_AUTH_DIR:-$DATA_DIR/baileys}"
MEDIA_DIR="${STORAGE_LOCAL_PATH:-$DATA_DIR/media}"
PLUGIN_PACKAGES_DIR="${PLUGINS_DIR:-./plugins}"
PLUGIN_STATE_DIR="$DATA_DIR/plugins"
GENERATED_ENV="$DATA_DIR/.env.generated"
ADMIN_KEY_FILE="$DATA_DIR/.api-key"

log() { echo "[backup] $*"; }

STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT

# Online SQLite backup (consistent without stopping the app) when sqlite3 is present,
# else a plain copy with a warning.
backup_sqlite() {
  src="$1"
  dest="$2"
  if [ ! -f "$src" ]; then
    log "WARN: $src not found β€” skipping"
    return 0
  fi
  if command -v sqlite3 >/dev/null 2>&1; then
    sqlite3 "$src" ".backup '$dest'"
  else
    log "WARN: sqlite3 not found β€” plain-copying $src (stop the app first for a consistent copy)"
    cp "$src" "$dest"
  fi
}

log "Backing up auth/audit DB (main.sqlite) β€” the API-key + audit store"
backup_sqlite "$MAIN_DB" "$STAGE/main.sqlite"

if [ "$DATABASE_TYPE" = "postgres" ]; then
  log "Backing up data store via pg_dump"
  if ! command -v pg_dump >/dev/null 2>&1; then
    log "ERROR: DATABASE_TYPE=postgres but pg_dump is not installed"
    exit 1
  fi
  if [ -n "${DATABASE_URL:-}" ]; then
    pg_dump "$DATABASE_URL" >"$STAGE/database.sql"
  else
    PGPASSWORD="${DATABASE_PASSWORD:-}" pg_dump \
      -h "${DATABASE_HOST:-localhost}" \
      -p "${DATABASE_PORT:-5432}" \
      -U "${DATABASE_USERNAME:-openwa}" \
      "${DATABASE_NAME:-openwa}" >"$STAGE/database.sql"
  fi
else
  log "Backing up data store (openwa.sqlite)"
  backup_sqlite "$DATA_DB" "$STAGE/openwa.sqlite"
fi

if [ -d "$SESSIONS_DIR" ]; then
  log "Backing up whatsapp-web.js sessions"
  cp -pR "$SESSIONS_DIR" "$STAGE/sessions"
else
  log "WARN: $SESSIONS_DIR not found β€” skipping sessions"
fi

if [ -d "$BAILEYS_DIR" ]; then
  log "Backing up Baileys authentication state"
  cp -pR "$BAILEYS_DIR" "$STAGE/baileys"
elif [ "${ENGINE_TYPE:-}" = "baileys" ]; then
  log "WARN: ENGINE_TYPE=baileys but $BAILEYS_DIR was not found β€” restored sessions will require pairing"
fi

if [ -d "$MEDIA_DIR" ]; then
  log "Backing up local media"
  cp -pR "$MEDIA_DIR" "$STAGE/media"
fi

if [ -d "$PLUGIN_PACKAGES_DIR" ]; then
  log "Backing up installed plugin packages"
  cp -pR "$PLUGIN_PACKAGES_DIR" "$STAGE/plugin-packages"
fi

if [ -d "$PLUGIN_STATE_DIR" ]; then
  log "Backing up plugin registry and persisted state"
  cp -pR "$PLUGIN_STATE_DIR" "$STAGE/plugin-state"
fi

if [ -f "$GENERATED_ENV" ]; then
  log "Backing up dashboard-generated configuration"
  cp -p "$GENERATED_ENV" "$STAGE/.env.generated"
fi

if [ -f "$ADMIN_KEY_FILE" ]; then
  log "Backing up plaintext admin key"
  cp -p "$ADMIN_KEY_FILE" "$STAGE/.api-key"
fi

mkdir -p "$BACKUP_DIR"
ARCHIVE="$BACKUP_DIR/openwa-backup-$TIMESTAMP.tar.gz"
tar -czf "$ARCHIVE" -C "$STAGE" .

log "Backup complete: $ARCHIVE"
log "SECURITY: this archive can contain database passwords, plugin secrets, and an admin API key; restrict and encrypt it"
log "Contents:"
tar -tzf "$ARCHIVE" | sed 's/^/[backup]   /'