Research-Stack/5-Applications/scripts/restore_gdrive.py

231 lines
7.9 KiB
Python

#!/usr/bin/env python3
"""
restore_gdrive.py — Restore Google Drive access and re-ingest into ENE.
Steps:
1. Test current rclone Gdrive: remote
2. If token is dead, print re-auth instructions
3. If token works, extract credentials and re-ingest into ENE substrate
4. Verify ENE can decrypt the new credentials
Usage:
# If gdrive token is dead, re-auth first:
rclone config reconnect Gdrive:
# Then run:
python3 5-Applications/scripts/restore_gdrive.py
"""
import sys
import json
import os
import subprocess
from pathlib import Path
from datetime import datetime
# Load .env
project_root = Path(__file__).parent.parent.parent
try:
from dotenv import load_dotenv
if (project_root / ".env").exists():
load_dotenv(project_root / ".env")
except ImportError:
pass
sys.path.insert(0, str(project_root))
sys.path.insert(0, str(project_root / "4-Infrastructure" / "infra"))
from infra.ene_api import ENEAPIHook, AccessLevel
RCLONE_CONF = Path.home() / ".config" / "rclone" / "rclone.conf"
def test_rclone_gdrive() -> dict:
"""Test if rclone can list Gdrive root."""
result = subprocess.run(
["rclone", "ls", "Gdrive:", "--max-depth", "1"],
capture_output=True, text=True, timeout=60
)
return {
"ok": result.returncode == 0,
"stdout": result.stdout.strip(),
"stderr": result.stderr.strip(),
"returncode": result.returncode
}
def parse_rclone_token() -> dict:
"""Extract token JSON from rclone config."""
if not RCLONE_CONF.exists():
return {"error": f"rclone.conf not found at {RCLONE_CONF}"}
in_gdrive = False
token_line = None
with open(RCLONE_CONF) as f:
for line in f:
line = line.strip()
if line.startswith("[Gdrive]"):
in_gdrive = True
elif line.startswith("[") and line.endswith("]"):
in_gdrive = False
elif in_gdrive and line.startswith("token = "):
token_line = line[len("token = "):]
break
if not token_line:
return {"error": "No token found in [Gdrive] section"}
try:
token = json.loads(token_line)
return {"ok": True, "token": token}
except json.JSONDecodeError as e:
return {"error": f"Invalid token JSON: {e}"}
def update_rclone_config(new_token: dict) -> bool:
"""Update rclone.conf with new token JSON."""
if not RCLONE_CONF.exists():
return False
lines = []
in_gdrive = False
replaced = False
with open(RCLONE_CONF) as f:
for line in f:
stripped = line.strip()
if stripped.startswith("[Gdrive]"):
in_gdrive = True
elif stripped.startswith("[") and stripped.endswith("]"):
in_gdrive = False
if in_gdrive and stripped.startswith("token = ") and not replaced:
lines.append(f"token = {json.dumps(new_token)}\n")
replaced = True
continue
lines.append(line)
with open(RCLONE_CONF, "w") as f:
f.writelines(lines)
return replaced
def ingest_gdrive_to_ene(token: dict, client_id: str, client_secret: str) -> dict:
"""Store gdrive credentials in ENE substrate."""
api = ENEAPIHook()
# Store the refresh token as the primary credential
payload = json.dumps({
"provider": "gdrive",
"remote_name": "Gdrive",
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": token.get("refresh_token", ""),
"token_type": token.get("token_type", "Bearer"),
"updated_at": datetime.utcnow().isoformat(),
})
result = api.store_sensitive_data(
pkg="credentials/gdrive",
payload=payload,
classification=AccessLevel.SECRET
)
return result
def main():
print("=" * 70)
print("GDRIVE RESTORE")
print("=" * 70)
# 1. Test rclone
print("\n[1] Testing rclone Gdrive: access...")
test = test_rclone_gdrive()
if test["ok"]:
print(" ✅ Gdrive is accessible")
print(f" Listing: {test['stdout'][:200]}...")
else:
print(" ❌ Gdrive access failed")
print(f" Error: {test['stderr'][:200]}")
if "invalid_grant" in test["stderr"] or "token expired" in test["stderr"]:
print("\n The refresh token is revoked or expired.")
print(" You need to re-authenticate:")
print("\n ┌─────────────────────────────────────────────────────────────┐")
print(" │ Run this command in your terminal: │")
print(" │ │")
print(" │ rclone config reconnect Gdrive: │")
print(" │ │")
print(" │ Then follow the browser prompt to authorize. │")
print(" │ After that, run this script again. │")
print(" └─────────────────────────────────────────────────────────────┘")
sys.exit(1)
# 2. Parse token
print("\n[2] Reading rclone token...")
token_info = parse_rclone_token()
if "error" in token_info:
print(f"{token_info['error']}")
sys.exit(1)
token = token_info["token"]
refresh_token = token.get("refresh_token", "")
if not refresh_token:
print(" ❌ No refresh_token in rclone config. Re-authenticate with:")
print(" rclone config reconnect Gdrive:")
sys.exit(1)
print(f" ✅ Refresh token present ({len(refresh_token)} chars)")
# 3. Read client_id/client_secret from rclone config
client_id = None
client_secret = None
with open(RCLONE_CONF) as f:
in_gdrive = False
for line in f:
line = line.strip()
if line.startswith("[Gdrive]"):
in_gdrive = True
elif line.startswith("[") and line.endswith("]"):
in_gdrive = False
elif in_gdrive and line.startswith("client_id = "):
client_id = line[len("client_id = "):]
elif in_gdrive and line.startswith("client_secret = "):
client_secret = line[len("client_secret = "):]
# 4. Ingest into ENE
print("\n[3] Ingesting gdrive credentials into ENE substrate...")
ene_result = ingest_gdrive_to_ene(token, client_id, client_secret)
if ene_result.get("success"):
print(f" ✅ Stored in ENE. ID: {ene_result['id']}")
else:
print(f" ❌ ENE storage failed: {ene_result.get('error')}")
sys.exit(1)
# 5. Verify decryption
print("\n[4] Verifying ENE decryption...")
api = ENEAPIHook()
verify = api.retrieve_sensitive_data("credentials/gdrive", AccessLevel.SECRET)
if verify.get("success"):
print(" ✅ Credentials decryptable")
parsed = json.loads(verify["payload"])
print(f" Provider: {parsed.get('provider')}")
print(f" Remote: {parsed.get('remote_name')}")
print(f" Refresh token length: {len(parsed.get('refresh_token', ''))}")
else:
print(f" ❌ Decryption failed: {verify.get('error')}")
sys.exit(1)
print("\n" + "=" * 70)
print("GDRIVE RESTORED")
print("=" * 70)
print("\nNext steps:")
print(" - ENE substrate now holds the gdrive credential")
print(" - rclone Gdrive: remote is working")
print(" - Run 'rclone mount Gdrive: ~/Gdrive' to mount if desired")
print(" - Run bridge/dump scripts to push gdrive metadata into Neo4j")
if __name__ == "__main__":
main()