95 lines
4.0 KiB
Bash
95 lines
4.0 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
if [[ -f "${ROOT_DIR}/secrets/runtime.env" ]]; then
|
|
set -a
|
|
# shellcheck disable=SC1091
|
|
source "${ROOT_DIR}/secrets/runtime.env"
|
|
set +a
|
|
fi
|
|
|
|
if [[ "${1:-}" == "--apply-legacy" ]]; then
|
|
shift
|
|
else
|
|
for candidate in \
|
|
"${PROD_BACKUP_RESTORE_RUST:-}" \
|
|
"${CARGO_TARGET_DIR:-}/release/prod-backup-restore" \
|
|
"$ROOT_DIR/adk-rust/target/release/prod-backup-restore" \
|
|
"/usr/local/bin/prod-backup-restore"; do
|
|
if [[ -n "$candidate" && -x "$candidate" ]]; then
|
|
exec "$candidate" --root "$ROOT_DIR" "$@"
|
|
fi
|
|
done
|
|
cat >&2 <<'EOF'
|
|
prod_backup_restore.sh is destructive and now requires the Rust planner.
|
|
Build it first:
|
|
cd adk-rust && cargo build --release -p prod-backup-restore
|
|
|
|
To run the old destructive restore explicitly:
|
|
scripts/prod_backup_restore.sh --apply-legacy
|
|
EOF
|
|
exit 2
|
|
fi
|
|
|
|
: "${AW_SSH_PASSWORD:?AW_SSH_PASSWORD is required}"
|
|
: "${AW_WINRM_PASSWORD:?AW_WINRM_PASSWORD is required}"
|
|
|
|
command -v sshpass >/dev/null 2>&1 || { echo "missing sshpass" >&2; exit 127; }
|
|
command -v ansible-playbook >/dev/null 2>&1 || { echo "missing ansible-playbook" >&2; exit 127; }
|
|
|
|
SERVER_HOST="${AW_SERVER_HOST:-10.10.10.13}"
|
|
SERVER_USER="${AW_SERVER_USER:-igor}"
|
|
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|
REMOTE_BACKUP_DIR="/var/lib/activitywatch/backups/prod-restore-${TIMESTAMP}"
|
|
LEGACY_DB="/root/.local/share/activitywatch/aw-server-rust/sqlite.db"
|
|
TARGET_DB="/var/lib/activitywatch/.local/share/activitywatch/aw-server-rust/sqlite.db"
|
|
REMOTE_MERGE_SCRIPT="/tmp/merge_aw_server_dbs.py"
|
|
|
|
ssh_remote() {
|
|
sshpass -p "$AW_SSH_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "${SERVER_USER}@${SERVER_HOST}" "$@"
|
|
}
|
|
|
|
scp_remote() {
|
|
sshpass -p "$AW_SSH_PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$@"
|
|
}
|
|
|
|
scp_remote "${ROOT_DIR}/scripts/merge_aw_server_dbs.py" "${SERVER_USER}@${SERVER_HOST}:${REMOTE_MERGE_SCRIPT}"
|
|
|
|
ssh_remote "sudo mkdir -p '${REMOTE_BACKUP_DIR}' && sudo chown root:root '${REMOTE_BACKUP_DIR}'"
|
|
ssh_remote "sudo test -f '${LEGACY_DB}'"
|
|
ssh_remote "sudo test -f '${TARGET_DB}'"
|
|
ssh_remote "sudo cp -a '${LEGACY_DB}' '${REMOTE_BACKUP_DIR}/legacy-root-sqlite.db' && sudo cp -a '${TARGET_DB}' '${REMOTE_BACKUP_DIR}/target-before-merge-sqlite.db'"
|
|
ssh_remote "sudo systemctl stop activitywatch-server.service || true"
|
|
ssh_remote "sudo python3 '${REMOTE_MERGE_SCRIPT}' --base '${LEGACY_DB}' --overlay '${TARGET_DB}' --output '${REMOTE_BACKUP_DIR}/sqlite.merged.db'"
|
|
ssh_remote "sudo install -o activitywatch -g activitywatch -m 0644 '${REMOTE_BACKUP_DIR}/sqlite.merged.db' '${TARGET_DB}'"
|
|
|
|
ansible-playbook -i ansible/inventory.ini ansible/deploy_aw_server.yml
|
|
ansible-playbook -i ansible/inventory.ini ansible/deploy_aw_windows.yml
|
|
ansible-playbook -i ansible/inventory.ini ansible/post_validate_aw_windows.yml
|
|
|
|
python3 - <<'PY'
|
|
import json, urllib.request
|
|
base = 'http://10.10.10.13:5600'
|
|
window_payload = {
|
|
'timeperiods': ['2026-04-29T00:00:00+03:00/2026-04-29T23:59:59+03:00'],
|
|
'query': [
|
|
'window_events = query_bucket(find_bucket("aw-watcher-window_SHARKON2025"));',
|
|
'RETURN = window_events;'
|
|
]
|
|
}
|
|
req = urllib.request.Request(base + '/api/0/query/', data=json.dumps(window_payload).encode(), method='POST', headers={'Content-Type': 'application/json', 'Origin': 'http://10.10.10.13:5600'})
|
|
with urllib.request.urlopen(req) as response:
|
|
data = json.loads(response.read().decode())
|
|
window_count = len(data[0]) if isinstance(data, list) and data else 0
|
|
if window_count <= 0:
|
|
raise SystemExit('no historical window data restored for 2026-04-29')
|
|
with urllib.request.urlopen(base + '/api/0/settings/') as response:
|
|
settings = json.loads(response.read().decode())
|
|
if settings.get('always_active_pattern') != 'aw-watcher-window':
|
|
raise SystemExit('always_active_pattern is not configured')
|
|
print(json.dumps({'restored_window_events_2026_04_29': window_count, 'always_active_pattern': settings.get('always_active_pattern')}, ensure_ascii=False))
|
|
PY
|