Files
Atomizer/hq/scripts/sync-credentials.sh
Antoine 3289a76e19 feat: add Atomizer HQ multi-agent cluster infrastructure
- 8-agent OpenClaw cluster (Manager, Tech-Lead, Secretary, Auditor,
  Optimizer, Study-Builder, NX-Expert, Webster)
- Orchestration engine: orchestrate.py (sync delegation + handoffs)
- Workflow engine: YAML-defined multi-step pipelines
- Agent workspaces: SOUL.md, AGENTS.md, MEMORY.md per agent
- Shared skills: delegate, orchestrate, atomizer-protocols
- Capability registry (AGENTS_REGISTRY.json)
- Cluster management: cluster.sh, systemd template
- All secrets replaced with env var references
2026-02-15 21:18:18 +00:00

179 lines
5.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# sync-credentials.sh — Single source of truth for all OpenClaw credentials
# Reads from canonical sources → pushes to all agent auth-profiles.json
#
# Usage:
# sync-credentials.sh # Sync all credentials
# sync-credentials.sh --restart # Sync + restart Atomizer cluster
# sync-credentials.sh --check # Just check expiry/health, no changes
# sync-credentials.sh --codex-login # Run codex login first, then sync
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
MODE="${1:-sync}"
if [ "$MODE" = "--codex-login" ]; then
echo "Starting codex login..."
echo "⚠️ Make sure you have SSH tunnel: ssh -L 1455:localhost:1455 clawdbot"
codex login
MODE="--restart" # After login, sync and restart
fi
export SYNC_MODE="$MODE"
python3 << 'PYEOF'
import json, os, time, sys, glob, shutil
from pathlib import Path
mode = os.environ.get("SYNC_MODE", "sync")
home = os.path.expanduser("~")
now_ms = int(time.time() * 1000)
now_s = time.time()
warnings = []
updates = []
# ─── Canonical credential sources ───
# 1. Anthropic token (from Mario's main profile — the "source of truth")
mario_auth = f"{home}/.openclaw/agents/main/agent/auth-profiles.json"
with open(mario_auth) as f:
mario_profiles = json.load(f)["profiles"]
anthropic_profile = mario_profiles.get("anthropic:default")
google_profile = mario_profiles.get("google:default")
# 2. OpenAI Codex (from Codex CLI — always freshest)
codex_auth_path = f"{home}/.codex/auth.json"
codex_profile = None
if os.path.isfile(codex_auth_path):
with open(codex_auth_path) as f:
codex = json.load(f)
t = codex["tokens"]
# Estimate expiry from access token (JWT)
import base64
try:
payload = t["access_token"].split(".")[1]
payload += "=" * (-len(payload) % 4)
jwt = json.loads(base64.urlsafe_b64decode(payload))
expires_ms = jwt["exp"] * 1000
except:
expires_ms = now_ms + 10 * 24 * 3600 * 1000 # fallback: 10 days
codex_profile = {
"type": "oauth",
"provider": "openai-codex",
"access": t["access_token"],
"refresh": t["refresh_token"],
"expires": expires_ms,
"accountId": t.get("account_id", "")
}
days_left = (expires_ms - now_ms) / (24 * 3600 * 1000)
if days_left < 2:
warnings.append(f"⚠️ OpenAI Codex token expires in {days_left:.1f} days! Run: codex login")
elif days_left < 5:
warnings.append(f"⚡ OpenAI Codex token expires in {days_left:.1f} days")
else:
print(f" ✓ OpenAI Codex token valid for {days_left:.1f} days")
else:
warnings.append("⚠️ No Codex CLI auth found! Run: codex login")
# ─── Check mode: just report ───
if mode == "--check":
# Check Anthropic
if anthropic_profile:
print(f" ✓ Anthropic token: present (token type, no expiry)")
# Check Google
if google_profile:
print(f" ✓ Google AI token: present")
# Check Discord tokens
discord_env = f"{home}/atomizer/config/.discord-tokens.env"
if os.path.isfile(discord_env):
with open(discord_env) as f:
count = sum(1 for l in f if l.startswith("DISCORD_TOKEN_"))
print(f" ✓ Discord bot tokens: {count} configured")
for w in warnings:
print(f" {w}")
sys.exit(0)
# ─── Sync mode: push to all instances ───
print("\nSyncing credentials to all instances...")
# Find all auth-profiles.json
patterns = [
f"{home}/.openclaw/agents/*/agent/auth-profiles.json",
f"{home}/.openclaw-atomizer/agents/*/agent/auth-profiles.json",
]
for pattern in patterns:
for path in glob.glob(pattern):
try:
with open(path) as f:
data = json.load(f)
changed = False
profiles = data.setdefault("profiles", {})
# Sync Anthropic
if anthropic_profile and "anthropic:default" in profiles:
if profiles["anthropic:default"].get("token") != anthropic_profile.get("token"):
profiles["anthropic:default"] = anthropic_profile.copy()
changed = True
# Sync OpenAI Codex
if codex_profile:
for key in list(profiles.keys()):
if key.startswith("openai-codex:"):
if profiles[key].get("refresh") != codex_profile["refresh"]:
profiles[key] = codex_profile.copy()
changed = True
# Sync Google (only for Mario)
if "/.openclaw/agents/" in path and google_profile:
if "google:default" in profiles:
profiles["google:default"] = google_profile.copy()
if changed:
# Backup before writing
backup = path + ".bak"
shutil.copy2(path, backup)
with open(path, "w") as f:
json.dump(data, f, indent=2)
agent = path.split("/agents/")[1].split("/")[0]
instance = "mario" if "/.openclaw/agents/" in path else "atomizer"
updates.append(f"{instance}/{agent}")
except Exception as e:
warnings.append(f"✗ {path}: {e}")
if updates:
print(f"\n Updated {len(updates)} profiles:")
for u in updates:
print(f" ✓ {u}")
else:
print("\n All profiles already in sync ✓")
for w in warnings:
print(f"\n {w}")
PYEOF
# Restart if requested
if [ "$MODE" = "--restart" ]; then
echo ""
CLUSTER="$HOME/atomizer/cluster.sh"
if [ -f "$CLUSTER" ]; then
echo "Restarting Atomizer cluster..."
bash "$CLUSTER" restart
fi
echo "Restarting Mario gateway..."
systemctl --user restart openclaw-gateway.service
echo "All instances restarted."
fi
echo ""
echo "Done."