189 lines
5.8 KiB
Bash
Executable File
189 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# brain-sync-replica.sh — Réplication master → replica (embeddings)
|
|
# Le desktop est source de vérité. Le laptop reçoit une copie read-only.
|
|
#
|
|
# Usage :
|
|
# brain-sync-replica.sh status → écart master/replica
|
|
# brain-sync-replica.sh sync <replica_host> → sync vers replica
|
|
# brain-sync-replica.sh sync laptop → alias pour le peer "laptop"
|
|
#
|
|
# Prérequis : SSH sans mot de passe vers la replica
|
|
# Ne sync QUE la table embeddings — pas claims, pas locks (BSI local à chaque machine)
|
|
|
|
set -euo pipefail
|
|
|
|
BRAIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
DB_PATH="$BRAIN_ROOT/brain.db"
|
|
REMOTE_DB_PATH="Dev/Brain/brain.db"
|
|
|
|
# Résoudre le peer depuis brain-compose.local.yml
|
|
resolve_peer() {
|
|
local name="$1"
|
|
python3 - "$BRAIN_ROOT/brain-compose.local.yml" "$name" << 'PY'
|
|
import sys, yaml
|
|
with open(sys.argv[1]) as f:
|
|
data = yaml.safe_load(f) or {}
|
|
peers = data.get('peers', {})
|
|
peer = peers.get(sys.argv[2], {})
|
|
url = peer.get('url', '')
|
|
# Extraire host depuis http://192.168.1.10:7700
|
|
if '://' in url:
|
|
host = url.split('://')[1].split(':')[0]
|
|
print(host)
|
|
PY
|
|
}
|
|
|
|
# --- STATUS ---
|
|
cmd_status() {
|
|
local local_count local_updated
|
|
|
|
local_count=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "SELECT COUNT(*) FROM embeddings WHERE indexed=1")
|
|
local_updated=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "SELECT MAX(updated_at) FROM embeddings")
|
|
|
|
echo "=== Embedding master (local) ==="
|
|
echo " Chunks indexés : $local_count"
|
|
echo " Dernier update : $local_updated"
|
|
|
|
# Check peers
|
|
local compose="$BRAIN_ROOT/brain-compose.local.yml"
|
|
if [ -f "$compose" ]; then
|
|
echo ""
|
|
echo "=== Peers ==="
|
|
python3 - "$compose" << 'PY'
|
|
import yaml, json, urllib.request
|
|
with open(__import__('sys').argv[1]) as f:
|
|
data = yaml.safe_load(f) or {}
|
|
for name, peer in data.get('peers', {}).items():
|
|
if not peer.get('active', False):
|
|
continue
|
|
url = peer.get('url', '').rstrip('/')
|
|
try:
|
|
with urllib.request.urlopen(f"{url}/health", timeout=3) as r:
|
|
health = json.loads(r.read())
|
|
indexed = health.get('indexed', '?')
|
|
print(f" {name}: {indexed} chunks (online)")
|
|
except Exception:
|
|
print(f" {name}: offline")
|
|
PY
|
|
fi
|
|
}
|
|
|
|
# --- SYNC ---
|
|
cmd_sync() {
|
|
local target="$1"
|
|
local host
|
|
|
|
# Résoudre si c'est un nom de peer
|
|
host=$(resolve_peer "$target" 2>/dev/null || echo "")
|
|
if [ -z "$host" ]; then
|
|
host="$target"
|
|
fi
|
|
|
|
local user="tetardtek"
|
|
local remote="${user}@${host}"
|
|
|
|
echo "=== Sync embeddings → $remote ==="
|
|
|
|
# 1. Check connexion
|
|
if ! ssh -o ConnectTimeout=3 "$remote" "echo ok" > /dev/null 2>&1; then
|
|
echo "❌ SSH unreachable : $remote"
|
|
exit 1
|
|
fi
|
|
|
|
# 2. Stats locales
|
|
local local_count
|
|
local_count=$(python3 "$BRAIN_ROOT/scripts/bsi-db.py" "SELECT COUNT(*) FROM embeddings WHERE indexed=1")
|
|
echo " Master : $local_count chunks"
|
|
|
|
# 3. Stats replica
|
|
local remote_count
|
|
remote_count=$(ssh "$remote" "python3 ~/Dev/Brain/scripts/bsi-db.py 'SELECT COUNT(*) FROM embeddings WHERE indexed=1' 2>/dev/null || echo 0")
|
|
echo " Replica : $remote_count chunks"
|
|
|
|
local delta=$((local_count - remote_count))
|
|
if [ "$delta" -eq 0 ]; then
|
|
echo "✅ Déjà synchronisé — 0 écart"
|
|
exit 0
|
|
fi
|
|
echo " Écart : $delta chunks"
|
|
echo ""
|
|
|
|
# 4. Export embeddings → fichier temporaire
|
|
local tmp="/tmp/brain-embeddings-sync.db"
|
|
echo " Exporting embeddings table..."
|
|
python3 - "$DB_PATH" "$tmp" << 'PY'
|
|
import sqlite3, sys
|
|
src = sqlite3.connect(sys.argv[1])
|
|
dst = sqlite3.connect(sys.argv[2])
|
|
dst.execute("DROP TABLE IF EXISTS embeddings")
|
|
# Copy schema
|
|
schema = src.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='embeddings'").fetchone()[0]
|
|
dst.execute(schema)
|
|
# Copy data
|
|
rows = src.execute("SELECT * FROM embeddings").fetchall()
|
|
cols = [d[0] for d in src.execute("PRAGMA table_info(embeddings)").fetchall()]
|
|
placeholders = ','.join(['?'] * len(cols))
|
|
dst.executemany(f"INSERT INTO embeddings VALUES ({placeholders})", rows)
|
|
dst.commit()
|
|
dst.close()
|
|
src.close()
|
|
print(f" ✅ {len(rows)} chunks exportés")
|
|
PY
|
|
|
|
# 5. SCP vers replica
|
|
echo " Transferring to $remote..."
|
|
scp -q "$tmp" "${remote}:/tmp/brain-embeddings-sync.db"
|
|
|
|
# 6. Import sur replica
|
|
ssh "$remote" python3 - << 'PY'
|
|
import sqlite3
|
|
src = sqlite3.connect("/tmp/brain-embeddings-sync.db")
|
|
dst = sqlite3.connect("/home/tetardtek/Dev/Brain/brain.db")
|
|
# Drop and recreate
|
|
dst.execute("DROP TABLE IF EXISTS embeddings")
|
|
schema = src.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='embeddings'").fetchone()[0]
|
|
dst.execute(schema)
|
|
rows = src.execute("SELECT * FROM embeddings").fetchall()
|
|
cols = [d[0] for d in src.execute("PRAGMA table_info(embeddings)").fetchall()]
|
|
placeholders = ','.join(['?'] * len(cols))
|
|
dst.executemany(f"INSERT INTO embeddings VALUES ({placeholders})", rows)
|
|
dst.commit()
|
|
dst.close()
|
|
src.close()
|
|
print(f" ✅ {len(rows)} chunks importés sur replica")
|
|
PY
|
|
|
|
# 7. Cleanup
|
|
rm -f "$tmp"
|
|
ssh "$remote" "rm -f /tmp/brain-embeddings-sync.db"
|
|
|
|
# 8. Verify
|
|
local new_count
|
|
new_count=$(ssh "$remote" "python3 ~/Dev/Brain/scripts/bsi-db.py 'SELECT COUNT(*) FROM embeddings WHERE indexed=1' 2>/dev/null || echo '?'")
|
|
echo ""
|
|
echo "=== Sync terminé ==="
|
|
echo " Master : $local_count chunks"
|
|
echo " Replica : $new_count chunks"
|
|
if [ "$local_count" = "$new_count" ]; then
|
|
echo " ✅ Synchronisé — 0 écart"
|
|
else
|
|
echo " ⚠️ Écart résiduel : $((local_count - new_count))"
|
|
fi
|
|
}
|
|
|
|
# --- Router ---
|
|
CMD="${1:-}"
|
|
case "$CMD" in
|
|
status) cmd_status ;;
|
|
sync) cmd_sync "${2:-}" ;;
|
|
*)
|
|
echo "Usage : brain-sync-replica.sh <status|sync>"
|
|
echo ""
|
|
echo " status → écart master/replica"
|
|
echo " sync <host|peer_name> → sync embeddings vers replica"
|
|
echo ""
|
|
echo " Exemple : brain-sync-replica.sh sync laptop"
|
|
exit 1
|
|
;;
|
|
esac
|