Files
momentry_core/scripts/sync_users_from_sftpgo.py

118 lines
4.0 KiB
Python

#!/opt/homebrew/bin/python3.11
"""
Sync users from SFTPGo to Momentry users table.
Usage:
python3 scripts/sync_users_from_sftpgo.py
python3 scripts/sync_users_from_sftpgo.py --sftpgo-url http://localhost:8080
python3 scripts/sync_users_from_sftpgo.py --db "dbname=momentry user=accusys"
Environment:
SFTPGO_BASE_URL Default: http://localhost:8080
DATABASE_URL Default: dbname=momentry user=accusys host=localhost
This script does NOT copy passwords. It creates user records with placeholder
password hashes. The real password will be captured on the user's first
login through Momentry (which verifies against SFTPGo and caches the hash).
"""
import argparse
import json
import os
import sys
from typing import Any
import psycopg2
import psycopg2.extras
import requests
def get_sftpgo_users(sftpgo_url: str, admin_user: str, admin_pass: str) -> list[dict[str, Any]]:
"""Get all users from SFTPGo."""
# Get admin token (SFTPGo uses GET, not POST)
token_url = f"{sftpgo_url}/api/v2/token"
resp = requests.get(token_url, auth=(admin_user, admin_pass), timeout=10)
resp.raise_for_status()
token = resp.json().get("access_token")
if not token:
print("ERROR: Failed to get SFTPGo admin token", file=sys.stderr)
sys.exit(1)
# List users
users_url = f"{sftpgo_url}/api/v2/users"
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(users_url, headers=headers, timeout=10)
resp.raise_for_status()
return resp.json()
def main():
parser = argparse.ArgumentParser(description="Sync SFTPGo users to Momentry")
parser.add_argument("--sftpgo-url", default=os.getenv("SFTPGO_BASE_URL", "http://localhost:8080"))
parser.add_argument("--db", default=os.getenv("DATABASE_URL", "dbname=momentry user=accusys host=localhost"))
parser.add_argument("--admin-user", default="admin")
parser.add_argument("--admin-pass", default=os.getenv("SFTPGO_ADMIN_PASSWORD", "Test3200Test3200"))
parser.add_argument("--dry-run", action="store_true", help="Print what would be done without executing")
args = parser.parse_args()
# Fetch users from SFTPGo
print(f"[SFTPGo] Connecting to {args.sftpgo_url}...")
try:
sftpgo_users = get_sftpgo_users(args.sftpgo_url, args.admin_user, args.admin_pass)
except Exception as e:
print(f"ERROR: Failed to fetch SFTPGo users: {e}", file=sys.stderr)
sys.exit(1)
print(f"[SFTPGo] Found {len(sftpgo_users)} users")
# Connect to Momentry DB and set schema
conn = psycopg2.connect(args.db)
cur = conn.cursor()
cur.execute("SET search_path TO dev")
synced = 0
skipped = 0
for user in sftpgo_users:
username = user.get("username")
status = user.get("status", 0)
if not username or status != 1:
skipped += 1
continue
role = "admin" if username == "admin" else "user"
# Placeholder hash — will be updated on first login via SFTPGo fallback
placeholder_hash = "$placeholder$synced_from_sftpgo"
if args.dry_run:
print(f" Would insert: {username} (role={role})")
synced += 1
continue
try:
cur.execute(
"INSERT INTO users (username, password_hash, role) VALUES (%s, %s, %s) "
"ON CONFLICT (username) DO NOTHING",
(username, placeholder_hash, role),
)
if cur.rowcount > 0:
print(f"{username} (role={role})")
synced += 1
else:
print(f" ⏭️ {username} already exists, skipped")
skipped += 1
except Exception as e:
print(f"{username}: {e}", file=sys.stderr)
skipped += 1
conn.commit()
cur.close()
conn.close()
print(f"\nDone: {synced} synced, {skipped} skipped/errors")
print("Note: Password hashes are placeholders. First login via Momentry will cache the real hash.")
if __name__ == "__main__":
main()