diff --git a/Makefile b/Makefile index 9c562cb..1aaf4c2 100644 --- a/Makefile +++ b/Makefile @@ -284,7 +284,7 @@ show-admin-password: @sudo python3 scripts/reset_admin_password.py --show reset-admin-password: - @sudo python3 scripts/reset_admin_password.py --generate + @docker exec cell-api python3 /app/scripts/reset_admin_password.py --generate # ── Network / peers ─────────────────────────────────────────────────────────── diff --git a/docker-compose.yml b/docker-compose.yml index 5706bb1..493df73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -206,6 +206,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./.env:/app/.env.compose - ./docker-compose.yml:/app/docker-compose.yml:ro + - ./scripts:/app/scripts:ro pid: host restart: unless-stopped networks: diff --git a/scripts/reset_admin_password.py b/scripts/reset_admin_password.py index a684d40..1ecca0a 100644 --- a/scripts/reset_admin_password.py +++ b/scripts/reset_admin_password.py @@ -11,11 +11,24 @@ import os import secrets import string -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api')) +_here = os.path.dirname(os.path.abspath(__file__)) +# Find auth_manager: host layout has api/ sibling, container has it one level up at /app +for _api_path in [os.path.join(_here, '..', 'api'), os.path.join(_here, '..'), '/app']: + if os.path.isfile(os.path.join(_api_path, 'auth_manager.py')): + sys.path.insert(0, os.path.normpath(_api_path)) + break -ROOT = os.path.join(os.path.dirname(__file__), '..') -INIT_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.admin_initial_password')) -TEST_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.test_admin_pass')) +ROOT = os.path.normpath(os.path.join(_here, '..')) +# data dir: host uses ROOT/data/api, container mounts data/api at ROOT/data +for _data in [os.path.join(ROOT, 'data', 'api'), os.path.join(ROOT, 'data'), '/app/data']: + if os.path.isdir(_data): + _DATA_DIR = _data + break +else: + _DATA_DIR = os.path.join(ROOT, 'data', 'api') + +INIT_PW_FILE = os.path.join(_DATA_DIR, '.admin_initial_password') +TEST_PW_FILE = os.path.join(_DATA_DIR, '.test_admin_pass') def _generate_password(length: int = 20) -> str: @@ -32,7 +45,7 @@ def _generate_password(length: int = 20) -> str: def _set_password(new_password: str) -> None: from auth_manager import AuthManager - data_dir = os.path.normpath(os.path.join(ROOT, 'data', 'api')) + data_dir = _DATA_DIR os.makedirs(data_dir, exist_ok=True) mgr = AuthManager(data_dir=data_dir, config_dir='/tmp') if mgr.set_password_admin('admin', new_password): diff --git a/webui/src/pages/CellNetwork.jsx b/webui/src/pages/CellNetwork.jsx index ca1b8cc..774eac0 100644 --- a/webui/src/pages/CellNetwork.jsx +++ b/webui/src/pages/CellNetwork.jsx @@ -23,7 +23,17 @@ const SERVICE_DEFS = [ function CopyButton({ text, small }) { const [copied, setCopied] = useState(false); const copy = () => { - navigator.clipboard.writeText(text); + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text); + } else { + const el = document.createElement('textarea'); + el.value = text; + el.style.cssText = 'position:fixed;opacity:0;top:0;left:0'; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + } setCopied(true); setTimeout(() => setCopied(false), 1500); };