This commit is contained in:
2026-01-21 22:52:52 -08:00
parent d94e321148
commit 201663bb62
9 changed files with 377 additions and 124 deletions

View File

@@ -30,7 +30,7 @@ import urllib.request
import zipfile
import re
from pathlib import Path
from typing import Optional, Tuple
from typing import Optional, Sequence, Tuple
import logging
@@ -308,7 +308,11 @@ def find_project_venv(root: Path) -> Optional[Path]:
return None
def maybe_reexec_under_project_venv(root: Path, disable: bool = False) -> None:
def maybe_reexec_under_project_venv(
root: Path,
disable: bool = False,
extra_argv: Sequence[str] | None = None,
) -> None:
"""If a project venv exists and we are not already running under it, re-exec
the current script using that venv's python interpreter.
@@ -344,9 +348,12 @@ def maybe_reexec_under_project_venv(root: Path, disable: bool = False) -> None:
script_path = Path(sys.argv[0]).resolve()
except Exception:
script_path = None
args = [str(py),
str(script_path) if script_path is not None else sys.argv[0]
] + sys.argv[1:]
args = [
str(py),
str(script_path) if script_path is not None else sys.argv[0]
] + sys.argv[1:]
if extra_argv:
args += list(extra_argv)
logging.debug("Exec args: %s", args)
os.execvpe(str(py), args, env)
except Exception as exc:
@@ -852,6 +859,11 @@ def main(argv: Optional[list[str]] = None) -> int:
action="store_true",
help="Do not attempt to re-exec the script under a project venv (if present)",
)
parser.add_argument(
"--use-project-venv",
action="store_true",
help="Force using the project venv even when running interactively",
)
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose logging")
args = parser.parse_args(argv)
@@ -861,6 +873,7 @@ def main(argv: Optional[list[str]] = None) -> int:
# Interactive setup for root and name if not provided and in a TTY
# We check sys.argv directly to see if the flags were explicitly passed.
interactive_setup = False
if sys.stdin.isatty() and not any(arg in sys.argv for arg in ["--root", "-r", "--dest-name", "-d"]):
print("\nHydrusNetwork Setup")
print("--------------------")
@@ -870,6 +883,9 @@ def main(argv: Optional[list[str]] = None) -> int:
try:
root_input = input(f"Enter root directory for Hydrus installation [default: {default_root}]: ").strip()
if root_input:
# If they typed "C:" or similar, assume they want the root "C:\"
if len(root_input) == 2 and root_input[1] == ":" and root_input[0].isalpha():
root_input += "\\"
args.root = root_input
else:
args.root = str(default_root)
@@ -881,6 +897,7 @@ def main(argv: Optional[list[str]] = None) -> int:
except (EOFError, KeyboardInterrupt):
print("\nSetup cancelled.")
return 0
interactive_setup = True
# Expand variables like $HOME or %USERPROFILE% and ~
args.root = os.path.expandvars(args.root)
@@ -889,7 +906,25 @@ def main(argv: Optional[list[str]] = None) -> int:
venv_py = None
# Re-exec under project venv by default when present (opt-out with --no-project-venv)
try:
maybe_reexec_under_project_venv(root, disable=bool(args.no_project_venv))
# If we are already running in a venv-like environment, we might skip re-exec.
# However, we only re-exec if the target root is the same as the project root.
disable_reexec = bool(args.no_project_venv)
# Don't re-exec when running interactively unless explicitly requested.
if interactive_setup and not args.use_project_venv:
disable_reexec = True
current_repo_root = Path(__file__).resolve().parent.parent
# Only re-exec if the target root folder matches the folder where THIS script lives.
# This prevents picking up Medios-Macina's .venv when installing Hydrus to a separate drive/folder.
if root != current_repo_root and not args.use_project_venv:
disable_reexec = True
maybe_reexec_under_project_venv(
root,
disable=disable_reexec,
extra_argv=["--root", args.root, "--dest-name", args.dest_name],
)
except Exception:
pass
@@ -1036,8 +1071,10 @@ def main(argv: Optional[list[str]] = None) -> int:
"pyopenssl": "OpenSSL",
"pysocks": "socks",
"service-identity": "service_identity",
"show-in-file-manager": "showinfm",
"opencv-python-headless": "cv2",
"pyyside6": "PySide6",
"mpv": "mpv",
"pyside6": "PySide6",
"pyside6-essentials": "PySide6",
"pyside6-addons": "PySide6",
}
@@ -1345,14 +1382,16 @@ def main(argv: Optional[list[str]] = None) -> int:
"python-dateutil": "dateutil",
"beautifulsoup4": "bs4",
"pillow-heif": "pillow_heif",
"pillow-jxl-plugin": "pillow_jxl_plugin",
"pillow-jxl-plugin": "pillow_jxl",
"pyopenssl": "OpenSSL",
"pysocks": "socks",
"service-identity": "service_identity",
"show-in-file-manager": "show_in_file_manager",
"show-in-file-manager": "showinfm",
"opencv-python-headless": "cv2",
"mpv": "mpv",
"pyside6": "PySide6",
"pyside6-essentials": "PySide6",
"pyside6-addons": "PySide6",
}
for pkg in pkgs:
try:
@@ -1463,8 +1502,18 @@ def main(argv: Optional[list[str]] = None) -> int:
run_client_script = None
if client_found:
# Prefer run_client helper located in the cloned repo; if missing, fall back to top-level scripts folder helper.
script_dir = Path(__file__).resolve().parent
helper_src = script_dir / "run_client.py"
helper_dest = dest / "run_client.py"
if helper_src.exists() and not helper_dest.exists():
try:
shutil.copy2(helper_src, helper_dest)
if os.name != "nt":
helper_dest.chmod(helper_dest.stat().st_mode | 0o111)
logging.debug("Copied run_client helper to %s", helper_dest)
except Exception as exc: # pragma: no cover - best effort
logging.debug("Failed to copy run_client helper: %s", exc)
# Prefer run_client helper located in the cloned repo; if missing, fall back to top-level scripts folder helper.
helper_candidates = [dest / "run_client.py", script_dir / "run_client.py"]
for cand in helper_candidates:
if cand.exists():