Add 3x-ui user sync via panel API (login or Bearer token).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 00:14:27 +03:00
co-authored by Cursor
parent 865260cc9d
commit 0acd27d840
10 changed files with 551 additions and 8 deletions
+10
View File
@@ -78,5 +78,15 @@ def init_schema():
if stmt:
cur.execute(stmt)
conn.commit()
# Soft migrations for existing databases
with pool.connection() as conn:
with conn.cursor() as cur:
cur.execute(
"ALTER TABLE users ADD COLUMN IF NOT EXISTS xui_email TEXT"
)
cur.execute(
"CREATE INDEX IF NOT EXISTS idx_users_xui_email ON users(xui_email)"
)
conn.commit()
_schema_ready = True
logger.info('PostgreSQL schema ready')
+3
View File
@@ -29,11 +29,14 @@ CREATE TABLE IF NOT EXISTS users (
last_reset_at TIMESTAMPTZ,
expiration_date TIMESTAMPTZ,
remnawave_uuid TEXT,
xui_email TEXT,
share_enabled BOOLEAN NOT NULL DEFAULT FALSE,
share_token TEXT,
share_password_hash TEXT
);
CREATE INDEX IF NOT EXISTS idx_users_xui_email ON users(xui_email);
CREATE TABLE IF NOT EXISTS user_connections (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+14 -3
View File
@@ -35,6 +35,15 @@ DEFAULT_SETTINGS = {
'remnawave_create_conns': False,
'remnawave_server_id': 0,
'remnawave_protocol': 'awg',
'xui_sync': False,
'xui_url': '',
'xui_username': '',
'xui_password': '',
'xui_api_token': '',
'xui_sync_users': False,
'xui_create_conns': False,
'xui_server_id': 0,
'xui_protocol': 'xray',
},
}
@@ -116,6 +125,7 @@ def _row_to_user(row) -> dict:
'last_reset_at': _ts_iso(row['last_reset_at']),
'expiration_date': _ts_iso(row['expiration_date']),
'remnawave_uuid': row['remnawave_uuid'],
'xui_email': row.get('xui_email'),
'share_enabled': bool(row['share_enabled']),
'share_token': row['share_token'],
'share_password_hash': row['share_password_hash'],
@@ -163,7 +173,7 @@ def load_data() -> dict:
'SELECT id, username, password_hash, role, enabled, created_at, '
'telegram_id, email, description, traffic_limit, traffic_used, '
'traffic_total, traffic_reset_strategy, last_reset_at, '
'expiration_date, remnawave_uuid, share_enabled, share_token, '
'expiration_date, remnawave_uuid, xui_email, share_enabled, share_token, '
'share_password_hash FROM users ORDER BY created_at NULLS LAST, username'
)
users = [_row_to_user(r) for r in cur.fetchall()]
@@ -242,10 +252,10 @@ def save_data(data: dict) -> None:
'id, username, password_hash, role, enabled, created_at, '
'telegram_id, email, description, traffic_limit, traffic_used, '
'traffic_total, traffic_reset_strategy, last_reset_at, '
'expiration_date, remnawave_uuid, share_enabled, share_token, '
'expiration_date, remnawave_uuid, xui_email, share_enabled, share_token, '
'share_password_hash'
') VALUES ('
'%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s'
'%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s'
')',
(
_as_uuid(user['id']),
@@ -264,6 +274,7 @@ def save_data(data: dict) -> None:
_parse_ts(user.get('last_reset_at')),
_parse_ts(user.get('expiration_date')),
user.get('remnawave_uuid'),
user.get('xui_email'),
bool(user.get('share_enabled', False)),
user.get('share_token'),
user.get('share_password_hash'),