Многопользовательский режим: авторизация, изоляция данных, лимиты генераций, деплой без VNC
This commit is contained in:
1 parent
0ca55f3818
commit
03555a400f
10 files changed
+1195
-154
No files matched your search
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Управление пользователями сервиса hh_auto.
|
||||
|
||||
Примеры:
|
||||
python3 manage_users.py add friend1 # спросит пароль
|
||||
python3 manage_users.py add friend1 --password 123 --limit 50
|
||||
python3 manage_users.py list
|
||||
python3 manage_users.py passwd friend1
|
||||
python3 manage_users.py limit friend1 100
|
||||
python3 manage_users.py remove friend1
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
USERS_FILE = os.path.join(BASE_DIR, "users.json")
|
||||
|
||||
|
||||
def load():
|
||||
if os.path.exists(USERS_FILE):
|
||||
with open(USERS_FILE, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
|
||||
def save(users):
|
||||
tmp = USERS_FILE + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(users, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, USERS_FILE)
|
||||
os.chmod(USERS_FILE, 0o600)
|
||||
|
||||
|
||||
def add(username, password, limit):
|
||||
users = load()
|
||||
if username in users:
|
||||
sys.exit(f"Пользователь «{username}» уже существует")
|
||||
users[username] = {
|
||||
"password_hash": generate_password_hash(password),
|
||||
"llm_limit": limit,
|
||||
"llm_usage": {},
|
||||
"created": datetime.date.today().isoformat(),
|
||||
}
|
||||
save(users)
|
||||
os.makedirs(os.path.join(BASE_DIR, "users", username, "accounts"), exist_ok=True)
|
||||
print(f"Пользователь «{username}» создан")
|
||||
print(f" Данные: {BASE_DIR}/users/{username}/")
|
||||
print(f" Дневной лимит генераций: {limit or 'без лимита'}")
|
||||
print(f" Вход: https://ВАШ-ДОМЕН/ (логин: {username})")
|
||||
|
||||
|
||||
def remove(username):
|
||||
users = load()
|
||||
if username not in users:
|
||||
sys.exit(f"Пользователь «{username}» не найден")
|
||||
del users[username]
|
||||
save(users)
|
||||
shutil.rmtree(os.path.join(BASE_DIR, "users", username), ignore_errors=True)
|
||||
print(f"Пользователь «{username}» удалён вместе с данными")
|
||||
|
||||
|
||||
def set_password(username, password):
|
||||
users = load()
|
||||
if username not in users:
|
||||
sys.exit(f"Пользователь «{username}» не найден")
|
||||
users[username]["password_hash"] = generate_password_hash(password)
|
||||
save(users)
|
||||
print(f"Пароль «{username}» обновлён")
|
||||
|
||||
|
||||
def set_limit(username, limit):
|
||||
users = load()
|
||||
if username not in users:
|
||||
sys.exit(f"Пользователь «{username}» не найден")
|
||||
users[username]["llm_limit"] = limit
|
||||
save(users)
|
||||
print(f"Лимит «{username}»: {limit or 'без лимита'} генераций в день")
|
||||
|
||||
|
||||
def list_users():
|
||||
users = load()
|
||||
if not users:
|
||||
print("Пользователей нет")
|
||||
return
|
||||
today = datetime.date.today().isoformat()
|
||||
print(f"{'Логин':20} {'Генерации сегодня':18} Создан")
|
||||
for name, u in sorted(users.items()):
|
||||
used = (u.get("llm_usage") or {}).get(today, 0)
|
||||
limit = u.get("llm_limit") or 0
|
||||
lim = f"{used}/{limit}" if limit else f"{used}/∞"
|
||||
print(f" {name:18} {lim:16} {u.get('created', '?')}")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Управление пользователями hh_auto")
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
a = sub.add_parser("add", help="Создать пользователя")
|
||||
a.add_argument("username")
|
||||
a.add_argument("--password", help="Пароль (иначе спросит)")
|
||||
a.add_argument("--limit", type=int, default=50,
|
||||
help="Дневной лимит генераций (0 = без лимита)")
|
||||
|
||||
sub.add_parser("list", help="Список пользователей")
|
||||
|
||||
p = sub.add_parser("passwd", help="Сменить пароль")
|
||||
p.add_argument("username")
|
||||
|
||||
l = sub.add_parser("limit", help="Установить дневной лимит генераций")
|
||||
l.add_argument("username")
|
||||
l.add_argument("limit", type=int)
|
||||
|
||||
r = sub.add_parser("remove", help="Удалить пользователя с данными")
|
||||
r.add_argument("username")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.cmd == "add":
|
||||
password = args.password or getpass.getpass(f"Пароль для «{args.username}»: ")
|
||||
add(args.username, password, args.limit)
|
||||
elif args.cmd == "list":
|
||||
list_users()
|
||||
elif args.cmd == "passwd":
|
||||
password = getpass.getpass(f"Новый пароль для «{args.username}»: ")
|
||||
set_password(args.username, password)
|
||||
elif args.cmd == "limit":
|
||||
set_limit(args.username, args.limit)
|
||||
elif args.cmd == "remove":
|
||||
if input(f"Удалить «{args.username}» и все его данные? [y/N]: ").strip().lower() \
|
||||
in ("y", "д", "yes", "да"):
|
||||
remove(args.username)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user