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.
162 lines
4.3 KiB
Python
Executable file
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())
|