agnes-the-ai-analyst/scripts/generate_user_sync_configs.py
Petr c56905d34f Initial commit: OSS data distribution platform
Open-source AI data analyst platform extracted from internal repo.
Includes data sync engine, Keboola adapter, Flask web portal,
server deployment scripts, and configuration templates.
2026-03-08 23:31:28 +01:00

162 lines
4.3 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Generate per-user sync config files from central sync_settings.json.
This script reads /data/notifications/sync_settings.json and writes
~/.sync_settings.yaml for each user with their configured settings.
Usage:
python3 generate_user_sync_configs.py [--dry-run]
Run this script:
- After manual changes to sync_settings.json
- As a cron job to ensure configs stay in sync
- The webapp calls this automatically after settings changes
"""
import argparse
import json
import os
import pwd
import sys
from pathlib import Path
SYNC_SETTINGS_FILE = Path("/data/notifications/sync_settings.json")
DEFAULT_SETTINGS = {
"jira": False,
"jira_attachments": False,
}
def read_settings() -> dict:
"""Read sync settings from JSON file."""
if not SYNC_SETTINGS_FILE.exists():
return {}
try:
with open(SYNC_SETTINGS_FILE) as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading {SYNC_SETTINGS_FILE}: {e}", file=sys.stderr)
return {}
def generate_yaml(settings: dict) -> str:
"""Generate YAML content for a user's sync config."""
lines = [
"# AI Data Analyst - Data Sync Configuration",
"# Managed by web portal - changes here may be overwritten",
"#",
"# To manage settings, visit the web portal (Data Settings page)",
"",
"datasets:",
]
for dataset, enabled in sorted(settings.items()):
value = "true" if enabled else "false"
lines.append(f" {dataset}: {value}")
lines.append("")
return "\n".join(lines)
def get_user_home(username: str) -> Path | None:
"""Get home directory for a user."""
try:
return Path(pwd.getpwnam(username).pw_dir)
except KeyError:
return None
def write_user_config(username: str, yaml_content: str, dry_run: bool = False) -> bool:
"""Write config file to user's home directory.
Returns True on success, False on failure.
"""
home = get_user_home(username)
if not home:
print(f" [SKIP] User {username} not found on system")
return False
config_path = home / ".sync_settings.yaml"
if dry_run:
print(f" [DRY-RUN] Would write {config_path}")
return True
try:
# Write to temp file first
import tempfile
fd, tmp_path = tempfile.mkstemp(suffix=".yaml", dir=str(home.parent))
try:
with os.fdopen(fd, "w") as f:
f.write(yaml_content)
# Get user's uid/gid
user_info = pwd.getpwnam(username)
# Set ownership
os.chown(tmp_path, user_info.pw_uid, user_info.pw_gid)
os.chmod(tmp_path, 0o644)
# Atomic move
os.replace(tmp_path, config_path)
print(f" [OK] {config_path}")
return True
except Exception as e:
os.unlink(tmp_path)
raise e
except PermissionError:
print(f" [ERROR] Permission denied for {username}")
return False
except Exception as e:
print(f" [ERROR] {username}: {e}")
return False
def main():
parser = argparse.ArgumentParser(
description="Generate per-user sync config files from central settings."
)
parser.add_argument(
"--dry-run", action="store_true", help="Show what would be done without making changes"
)
parser.add_argument("--user", help="Generate config for specific user only")
args = parser.parse_args()
all_settings = read_settings()
if not all_settings:
print("No sync settings found or file is empty.")
return 0
if args.user:
users = {args.user: all_settings.get(args.user, {})}
else:
users = all_settings
print(f"Generating sync configs for {len(users)} user(s)...")
success = 0
failed = 0
for username, user_data in users.items():
# Merge with defaults
settings = dict(DEFAULT_SETTINGS)
settings.update(user_data.get("datasets", {}))
yaml_content = generate_yaml(settings)
if write_user_config(username, yaml_content, args.dry_run):
success += 1
else:
failed += 1
print(f"\nDone: {success} succeeded, {failed} failed")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())