f
This commit is contained in:
2
run-client.bat
Normal file
2
run-client.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
"C:\hydrusnetwork\.venv\Scripts\python.exe" "C:\Forgejo\Medios-Macina\scripts\run_client.py" %*
|
||||
@@ -83,26 +83,38 @@ def _ensure_interactive_stdin() -> None:
|
||||
print(f"DEBUG: Failed to re-open stdin: {e}")
|
||||
|
||||
|
||||
def run(cmd: list[str], quiet: bool = False, debug: bool = False, cwd: Optional[Path] = None, env: Optional[dict[str, str]] = None) -> None:
|
||||
def run(cmd: list[str], quiet: bool = False, debug: bool = False, cwd: Optional[Path] = None, env: Optional[dict[str, str]] = None, check: bool = True) -> subprocess.CompletedProcess:
|
||||
if debug:
|
||||
print(f"\n> {' '.join(cmd)}")
|
||||
|
||||
# Create a copy of the environment to potentially modify it for pip
|
||||
run_env = env.copy() if env is not None else os.environ.copy()
|
||||
|
||||
# If we are running a python command, ensure we don't leak user site-packages
|
||||
# which can cause "ModuleNotFoundError: No module named 'attr'" errors in recent pip/rich,
|
||||
# and ensures we only use packages installed in our venv.
|
||||
if len(cmd) >= 1 and "python" in str(cmd[0]).lower():
|
||||
run_env["PYTHONNOUSERSITE"] = "1"
|
||||
# Also clear any other potentially conflicting variables
|
||||
run_env.pop("PYTHONPATH", None)
|
||||
|
||||
# Ensure subprocess uses the re-opened interactive stdin if we have one
|
||||
stdin_handle = sys.stdin if not sys.stdin.isatty() or platform.system().lower() == "windows" else None
|
||||
|
||||
if quiet and not debug:
|
||||
subprocess.check_call(
|
||||
return subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=str(cwd) if cwd else None,
|
||||
env=env,
|
||||
stdin=stdin_handle
|
||||
env=run_env,
|
||||
stdin=stdin_handle,
|
||||
check=check
|
||||
)
|
||||
else:
|
||||
if not debug:
|
||||
print(f"> {' '.join(cmd)}")
|
||||
subprocess.check_call(cmd, cwd=str(cwd) if cwd else None, env=env, stdin=stdin_handle)
|
||||
return subprocess.run(cmd, cwd=str(cwd) if cwd else None, env=run_env, stdin=stdin_handle, check=check)
|
||||
|
||||
|
||||
REPO_URL = "https://code.glowers.club/goyimnose/Medios-Macina.git"
|
||||
@@ -121,6 +133,7 @@ class ProgressBar:
|
||||
if self.quiet:
|
||||
return
|
||||
|
||||
|
||||
term_width = shutil.get_terminal_size((80, 20)).columns
|
||||
percent = int(100 * (self.current / self.total))
|
||||
filled = int(self.bar_width * self.current // self.total)
|
||||
@@ -513,28 +526,33 @@ def main() -> int:
|
||||
# Running via pipe/eval, __file__ is not defined
|
||||
script_path = None
|
||||
script_dir = Path.cwd()
|
||||
repo_root = Path.cwd()
|
||||
# In Web Installer mode, we don't assume CWD is the repo root.
|
||||
repo_root = None
|
||||
|
||||
# DETECT REPOSITORY
|
||||
# Check if we are already inside a valid Medios-Macina repo
|
||||
def _is_valid_mm_repo(p: Path) -> bool:
|
||||
def _is_valid_mm_repo(p: Path | None) -> bool:
|
||||
if p is None: return False
|
||||
return (p / "CLI.py").exists() and (p / "scripts").exists()
|
||||
|
||||
# If running from a pipe/standalone, don't auto-probe the current directory
|
||||
# as a repo. This prevents "auto-choosing" existing folders in Web Installer mode.
|
||||
is_in_repo = False
|
||||
if script_path is None:
|
||||
is_in_repo = False
|
||||
pass
|
||||
else:
|
||||
is_in_repo = _is_valid_mm_repo(repo_root)
|
||||
# If not in the parent of the script, check the current working directory
|
||||
if not is_in_repo and _is_valid_mm_repo(Path.cwd()):
|
||||
repo_root = Path.cwd()
|
||||
script_dir = repo_root / "scripts"
|
||||
is_in_repo = True
|
||||
# Check if we are already inside a valid Medios-Macina repo.
|
||||
# We do this to provide a sensible default if we need to prompt the user.
|
||||
# But we don't set is_in_repo = True yet, so that _ensure_repo_available
|
||||
# will still prompt for confirmation in interactive mode.
|
||||
if not _is_valid_mm_repo(repo_root):
|
||||
if _is_valid_mm_repo(Path.cwd()):
|
||||
repo_root = Path.cwd()
|
||||
script_dir = repo_root / "scripts"
|
||||
|
||||
if not args.quiet:
|
||||
if not args.quiet and args.debug:
|
||||
print(f"Bootstrap script location: {script_dir}")
|
||||
print(f"Detected project root: {repo_root}")
|
||||
print(f"Project root: {repo_root}")
|
||||
print(f"Current working directory: {Path.cwd()}")
|
||||
|
||||
# Helpers for interactive menu and uninstall detection
|
||||
@@ -548,13 +566,18 @@ def main() -> int:
|
||||
|
||||
def _is_installed() -> bool:
|
||||
"""Return True if the project appears installed into the local .venv."""
|
||||
if repo_root is None:
|
||||
return False
|
||||
|
||||
vdir = repo_root / ".venv"
|
||||
py = _venv_python_path(vdir)
|
||||
if py is None:
|
||||
return False
|
||||
try:
|
||||
rc = subprocess.run([str(py), "-m", "pip", "show", "medeia-macina"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False)
|
||||
return rc.returncode == 0
|
||||
# We use the global run() with check=False to avoid raising CalledProcessError
|
||||
# when the package is not found (which returns exit code 1).
|
||||
res = run([str(py), "-m", "pip", "show", "medeia-macina"], quiet=True, check=False)
|
||||
return res.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -745,15 +768,21 @@ def main() -> int:
|
||||
"""Show a simple interactive menu to choose install/uninstall or delegate."""
|
||||
try:
|
||||
installed = _is_installed()
|
||||
term_width = shutil.get_terminal_size((80, 20)).columns
|
||||
while True:
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
# Center the logo
|
||||
logo_lines = LOGO.strip().splitlines()
|
||||
term_width = shutil.get_terminal_size((80, 20)).columns
|
||||
|
||||
# Use the same centering logic as the main installation screen
|
||||
logo_lines = LOGO.strip('\n').splitlines()
|
||||
max_logo_width = 0
|
||||
for line in logo_lines:
|
||||
max_logo_width = max(max_logo_width, len(line.rstrip()))
|
||||
|
||||
logo_padding = ' ' * max((term_width - max_logo_width) // 2, 0)
|
||||
|
||||
print("\n" * 2)
|
||||
for line in logo_lines:
|
||||
print(line.center(term_width))
|
||||
print(f"{logo_padding}{line.rstrip()}")
|
||||
|
||||
print("\n")
|
||||
menu_title = " MEDEIA MACINA BOOTSTRAP MENU "
|
||||
@@ -763,17 +792,29 @@ def main() -> int:
|
||||
print(border.center(term_width))
|
||||
print("\n")
|
||||
|
||||
install_text = "1) Reinstall" if installed else "1) Install"
|
||||
print(install_text.center(term_width))
|
||||
print("2) Extras > HydrusNetwork".center(term_width))
|
||||
# Define menu options
|
||||
options = [
|
||||
"1) Reinstall" if installed else "1) Install",
|
||||
"2) Extras > HydrusNetwork"
|
||||
]
|
||||
if installed:
|
||||
print("3) Uninstall".center(term_width))
|
||||
print("q) Quit".center(term_width))
|
||||
options.append("3) Uninstall")
|
||||
options.append("4) Install Hydrus System Service (Auto-update + Headless)")
|
||||
options.append("q) Quit")
|
||||
|
||||
# Center the block of options by finding the longest one
|
||||
max_opt_width = max(len(opt) for opt in options)
|
||||
opt_padding = ' ' * max((term_width - max_opt_width) // 2, 0)
|
||||
|
||||
for opt in options:
|
||||
print(f"{opt_padding}{opt}")
|
||||
|
||||
prompt = "\nChoose an option: "
|
||||
# Try to center the prompt roughly
|
||||
indent = " " * ((term_width // 2) - (len(prompt) // 2))
|
||||
choice = input(f"{indent}{prompt}").strip().lower()
|
||||
indent = " " * max((term_width // 2) - (len(prompt) // 2), 0)
|
||||
sys.stdout.write(f"{indent}{prompt}")
|
||||
sys.stdout.flush()
|
||||
choice = sys.stdin.readline().strip().lower()
|
||||
|
||||
if choice in ("1", "install", "reinstall"):
|
||||
return "install"
|
||||
@@ -784,6 +825,9 @@ def main() -> int:
|
||||
if installed and choice in ("3", "uninstall"):
|
||||
return "uninstall"
|
||||
|
||||
if choice == "4":
|
||||
return "install_service"
|
||||
|
||||
if choice in ("q", "quit", "exit"):
|
||||
return 0
|
||||
except EOFError:
|
||||
@@ -794,16 +838,10 @@ def main() -> int:
|
||||
"""Prompt for a clone location when running outside the repository."""
|
||||
nonlocal repo_root, script_dir, is_in_repo
|
||||
|
||||
# If script_path is None, we are running from a pipe/URL.
|
||||
# In this mode, we ALWAYS want to ask for the directory,
|
||||
# unless we have already asked and set is_in_repo to True within this session.
|
||||
if is_in_repo and script_path is not None:
|
||||
# If we have already settled on a repository path in this session, skip.
|
||||
if is_in_repo and repo_root is not None:
|
||||
return True
|
||||
|
||||
# If we are in standalone mode and is_in_repo is True, it means we
|
||||
# just finished cloning/setting up the path in the current execution.
|
||||
# But we need to be careful: if we haven't asked yet, we must ask.
|
||||
|
||||
if not shutil.which("git"):
|
||||
print("\nError: 'git' was not found on your PATH.", file=sys.stderr)
|
||||
print("Please install Git (https://git-scm.com/) and try again.", file=sys.stderr)
|
||||
@@ -852,6 +890,7 @@ def main() -> int:
|
||||
return False
|
||||
|
||||
os.chdir(str(repo_root))
|
||||
is_in_repo = True
|
||||
if not args.quiet:
|
||||
print(f"\nSuccessfully set up repository at {repo_root}")
|
||||
print("Resuming bootstrap...\n")
|
||||
@@ -1012,18 +1051,12 @@ def main() -> int:
|
||||
if (sys.stdin.isatty() or sys.stdout.isatty() or script_path is None) and not args.quiet:
|
||||
sel = _interactive_menu()
|
||||
if sel == "install":
|
||||
# Force set is_in_repo to False if piped to ensure _ensure_repo_available asks
|
||||
if script_path is None:
|
||||
is_in_repo = False
|
||||
if not _ensure_repo_available():
|
||||
return 1
|
||||
args.skip_deps = False
|
||||
args.install_editable = True
|
||||
args.no_playwright = False
|
||||
elif sel == "extras_hydrus":
|
||||
# Force set is_in_repo to False if piped to ensure _ensure_repo_available asks
|
||||
if script_path is None:
|
||||
is_in_repo = False
|
||||
if not _ensure_repo_available():
|
||||
return 1
|
||||
hydrus_script = repo_root / "scripts" / "hydrusnetwork.py"
|
||||
@@ -1049,6 +1082,57 @@ def main() -> int:
|
||||
else:
|
||||
print(f"\nError: {hydrus_script} not found.")
|
||||
return 0
|
||||
elif sel == "install_service":
|
||||
# Direct path input for the target repository
|
||||
print("\n[ SYSTEM SERVICE INSTALLATION ]")
|
||||
print("Enter the root directory of the Hydrus repository you want to run as a service.")
|
||||
print("This is the folder containing 'hydrus_client.py'.")
|
||||
|
||||
# Default to repo_root/hydrusnetwork if available, otherwise CWD
|
||||
default_path = repo_root / "hydrusnetwork" if repo_root else Path.cwd()
|
||||
sys.stdout.write(f"Repository Root [{default_path}]: ")
|
||||
sys.stdout.flush()
|
||||
|
||||
path_raw = sys.stdin.readline().strip()
|
||||
target_repo = Path(path_raw).resolve() if path_raw else default_path
|
||||
|
||||
if not (target_repo / "hydrus_client.py").exists():
|
||||
print(f"\n[!] Error: 'hydrus_client.py' not found in: {target_repo}")
|
||||
print(" Please ensure you've entered the correct repository root.")
|
||||
sys.stdout.write("\nPress Enter to return to menu...")
|
||||
sys.stdout.flush()
|
||||
sys.stdin.readline()
|
||||
return "menu"
|
||||
|
||||
run_client_script = repo_root / "scripts" / "run_client.py" if repo_root else Path(__file__).parent / "run_client.py"
|
||||
|
||||
if run_client_script.exists():
|
||||
try:
|
||||
# We pass --repo-root explicitly to the target_repo provided by the user
|
||||
subprocess.check_call(
|
||||
[
|
||||
sys.executable,
|
||||
str(run_client_script),
|
||||
"--install-service",
|
||||
"--service-name", "hydrus-client",
|
||||
"--repo-root", str(target_repo),
|
||||
"--headless",
|
||||
"--pull"
|
||||
],
|
||||
stdin=sys.stdin
|
||||
)
|
||||
print("\nHydrus System service installed successfully.")
|
||||
except subprocess.CalledProcessError:
|
||||
print("\nService installation failed.")
|
||||
except Exception as e:
|
||||
print(f"\nError installing service: {e}")
|
||||
else:
|
||||
print(f"\nError: {run_client_script} not found.")
|
||||
|
||||
sys.stdout.write("\nPress Enter to continue...")
|
||||
sys.stdout.flush()
|
||||
sys.stdin.readline()
|
||||
return "menu"
|
||||
elif sel == "uninstall":
|
||||
return _do_uninstall()
|
||||
elif sel == "delegate":
|
||||
@@ -1087,6 +1171,10 @@ def main() -> int:
|
||||
print(f"{padding}{line.rstrip()}")
|
||||
print("\n")
|
||||
|
||||
if repo_root is None:
|
||||
print("Error: No project repository found. Please ensure you are running this script inside the project folder or follow the interactive install prompts.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Determine total steps for progress bar
|
||||
total_steps = 7 # Base: venv, pip, deps, project, cli, finalize, env
|
||||
if args.upgrade_pip: total_steps += 1
|
||||
@@ -1139,19 +1227,17 @@ def main() -> int:
|
||||
"""Ensure pip is available inside the venv; fall back to ensurepip if needed."""
|
||||
|
||||
try:
|
||||
subprocess.run(
|
||||
[str(python_path), "-m", "pip", "--version"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=True,
|
||||
)
|
||||
# Use run() to ensure clean environment (fixes ModuleNotFoundError: No module named 'attr' in pipe mode)
|
||||
run([str(python_path), "-m", "pip", "--version"], quiet=True)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
_run_cmd([str(python_path), "-m", "ensurepip", "--upgrade"])
|
||||
except subprocess.CalledProcessError:
|
||||
# ensurepip is a stdlib module, but it can still benefit from a clean environment
|
||||
# although usually it doesn't use site-packages.
|
||||
run([str(python_path), "-m", "ensurepip", "--upgrade"], quiet=True)
|
||||
except Exception:
|
||||
print(
|
||||
"Failed to install pip inside the local virtualenv via ensurepip; ensure your Python build includes ensurepip and retry.",
|
||||
file=sys.stderr,
|
||||
@@ -1239,15 +1325,12 @@ def main() -> int:
|
||||
pb.update("Verifying CLI configuration...")
|
||||
# Check core dependencies (including mpv) before finalizing
|
||||
try:
|
||||
# Check basic imports and specifically mpv
|
||||
missing = []
|
||||
for mod in ["importlib", "shutil", "subprocess", "mpv"]:
|
||||
try:
|
||||
subprocess.run(
|
||||
[str(venv_python), "-c", f"import {mod}"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=True
|
||||
)
|
||||
# Use run() for verification to ensure clean environment
|
||||
run([str(venv_python), "-c", f"import {mod}"], quiet=True)
|
||||
except Exception:
|
||||
missing.append(mod)
|
||||
|
||||
@@ -1267,17 +1350,11 @@ def main() -> int:
|
||||
pass
|
||||
|
||||
try:
|
||||
cli_verify_result = subprocess.run(
|
||||
[
|
||||
str(venv_python),
|
||||
"-c",
|
||||
"import importlib; importlib.import_module('CLI')"
|
||||
],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
if cli_verify_result.returncode != 0:
|
||||
# Use run() for CLI verification to ensure clean environment (fixes global interference)
|
||||
run([str(venv_python), "-c", "import importlib; importlib.import_module('CLI')"], quiet=True)
|
||||
except Exception:
|
||||
try:
|
||||
# If direct import fails, try to diagnose why or add a .pth link
|
||||
cmd = [
|
||||
str(venv_python),
|
||||
"-c",
|
||||
@@ -1291,7 +1368,7 @@ def main() -> int:
|
||||
"for s in res:\n print(s)\n"
|
||||
),
|
||||
]
|
||||
out = subprocess.check_output(cmd, text=True).strip().splitlines()
|
||||
out = subprocess.check_output(cmd, text=True, env={**os.environ, "PYTHONNOUSERSITE": "1"}).strip().splitlines()
|
||||
site_dir: Path | None = None
|
||||
for sp in out:
|
||||
if sp and Path(sp).exists():
|
||||
@@ -1308,6 +1385,8 @@ def main() -> int:
|
||||
else:
|
||||
with pth_file.open("w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ def install_service_windows(
|
||||
headless: bool = True,
|
||||
detached: bool = True,
|
||||
start_on: str = "logon",
|
||||
pull: bool = False,
|
||||
workspace_root: Optional[Path] = None,
|
||||
) -> bool:
|
||||
try:
|
||||
schtasks = shutil.which("schtasks")
|
||||
@@ -226,14 +228,27 @@ def install_service_windows(
|
||||
)
|
||||
return False
|
||||
|
||||
bat = repo_root / "run-client.bat"
|
||||
# If we have a workspace root (Medios-Macina), we use it for the wrapper scripts
|
||||
base_dir = workspace_root if workspace_root else repo_root
|
||||
bat = base_dir / "run-client.bat"
|
||||
|
||||
python_exe = venv_py
|
||||
this_script = Path(__file__).resolve()
|
||||
|
||||
if not bat.exists():
|
||||
# Use escaped backslashes to avoid Python "invalid escape sequence" warnings
|
||||
content = '@echo off\n"%~dp0\\.venv\\Scripts\\python.exe" "%~dp0hydrus_client.py" %*\n'
|
||||
# Absolute paths ensure the service works regardless of where it starts
|
||||
content = f'@echo off\n"{python_exe}" "{this_script}" %*\n'
|
||||
bat.write_text(content, encoding="utf-8")
|
||||
|
||||
tr = str(bat)
|
||||
sc = "ONLOGON" if start_on == "logon" else "ONSTART"
|
||||
|
||||
task_args = "--detached "
|
||||
if headless: task_args += "--headless "
|
||||
if pull: task_args += "--pull "
|
||||
# Force the correct repo root for the service
|
||||
task_args += f'--repo-root "{repo_root}" '
|
||||
|
||||
cmd = [
|
||||
schtasks,
|
||||
"/Create",
|
||||
@@ -242,7 +257,7 @@ def install_service_windows(
|
||||
"/TN",
|
||||
service_name,
|
||||
"/TR",
|
||||
f'"{tr}"',
|
||||
f'{tr} {task_args}',
|
||||
"/RL",
|
||||
"LIMITED",
|
||||
"/F",
|
||||
@@ -283,7 +298,9 @@ def install_service_systemd(
|
||||
repo_root: Path,
|
||||
venv_py: Path,
|
||||
headless: bool = True,
|
||||
detached: bool = True
|
||||
detached: bool = True,
|
||||
pull: bool = False,
|
||||
workspace_root: Optional[Path] = None,
|
||||
) -> bool:
|
||||
try:
|
||||
systemctl = shutil.which("systemctl")
|
||||
@@ -296,15 +313,22 @@ def install_service_systemd(
|
||||
repo_root,
|
||||
venv_py,
|
||||
headless,
|
||||
detached
|
||||
detached,
|
||||
pull=pull,
|
||||
workspace_root=workspace_root
|
||||
)
|
||||
|
||||
unit_dir = Path.home() / ".config" / "systemd" / "user"
|
||||
unit_dir.mkdir(parents=True, exist_ok=True)
|
||||
unit_file = unit_dir / f"{service_name}.service"
|
||||
exec_args = f'"{venv_py}" "{str(repo_root / "run_client.py")}" --detached '
|
||||
exec_args += "--headless " if headless else "--gui "
|
||||
content = f"[Unit]\nDescription=Hydrus Client (user)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart={exec_args}\nWorkingDirectory={str(repo_root)}\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=default.target\n"
|
||||
|
||||
this_script = Path(__file__).resolve()
|
||||
exec_args = f'"{venv_py}" "{this_script}" --detached '
|
||||
if headless: exec_args += "--headless "
|
||||
if pull: exec_args += "--pull "
|
||||
exec_args += f'--repo-root "{repo_root}" '
|
||||
|
||||
content = f"[Unit]\nDescription=Medios-Macina Client\nAfter=network.target\n\n[Service]\nType=simple\nExecStart={exec_args}\nWorkingDirectory={str(workspace_root if workspace_root else repo_root)}\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=default.target\n"
|
||||
unit_file.write_text(content, encoding="utf-8")
|
||||
subprocess.run([systemctl, "--user", "daemon-reload"], check=True)
|
||||
subprocess.run(
|
||||
@@ -356,14 +380,23 @@ def install_service_cron(
|
||||
repo_root: Path,
|
||||
venv_py: Path,
|
||||
headless: bool = True,
|
||||
detached: bool = True
|
||||
detached: bool = True,
|
||||
pull: bool = False,
|
||||
workspace_root: Optional[Path] = None,
|
||||
) -> bool:
|
||||
try:
|
||||
crontab = shutil.which("crontab")
|
||||
if not crontab:
|
||||
print("crontab not available; cannot install reboot cron job.")
|
||||
return False
|
||||
entry = f"@reboot {venv_py} {str(repo_root / 'run_client.py')} --detached {'--headless' if headless else '--gui'} # {service_name}\n"
|
||||
|
||||
this_script = Path(__file__).resolve()
|
||||
exec_args = f'"{venv_py}" "{this_script}" --detached '
|
||||
if headless: exec_args += "--headless "
|
||||
if pull: exec_args += "--pull "
|
||||
exec_args += f'--repo-root "{repo_root}" '
|
||||
|
||||
entry = f"@reboot {exec_args} # {service_name}\n"
|
||||
proc = subprocess.run(
|
||||
[crontab,
|
||||
"-l"],
|
||||
@@ -421,7 +454,9 @@ def install_service_auto(
|
||||
repo_root: Path,
|
||||
venv_py: Path,
|
||||
headless: bool = True,
|
||||
detached: bool = True
|
||||
detached: bool = True,
|
||||
pull: bool = False,
|
||||
workspace_root: Optional[Path] = None,
|
||||
) -> bool:
|
||||
try:
|
||||
if os.name == "nt":
|
||||
@@ -430,7 +465,9 @@ def install_service_auto(
|
||||
repo_root,
|
||||
venv_py,
|
||||
headless=headless,
|
||||
detached=detached
|
||||
detached=detached,
|
||||
pull=pull,
|
||||
workspace_root=workspace_root
|
||||
)
|
||||
else:
|
||||
if shutil.which("systemctl"):
|
||||
@@ -439,7 +476,9 @@ def install_service_auto(
|
||||
repo_root,
|
||||
venv_py,
|
||||
headless=headless,
|
||||
detached=detached
|
||||
detached=detached,
|
||||
pull=pull,
|
||||
workspace_root=workspace_root
|
||||
)
|
||||
else:
|
||||
return install_service_cron(
|
||||
@@ -447,11 +486,16 @@ def install_service_auto(
|
||||
repo_root,
|
||||
venv_py,
|
||||
headless=headless,
|
||||
detached=detached
|
||||
detached=detached,
|
||||
pull=pull,
|
||||
workspace_root=workspace_root
|
||||
)
|
||||
except Exception as exc:
|
||||
print("install_service_auto error:", exc)
|
||||
return False
|
||||
except Exception as exc:
|
||||
print("install_service_auto error:", exc)
|
||||
return False
|
||||
|
||||
|
||||
def uninstall_service_auto(service_name: str, repo_root: Path, venv_py: Path) -> bool:
|
||||
@@ -602,6 +646,11 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
help=
|
||||
"Attempt to launch the client without showing the Qt GUI (best-effort). Default for subsequent runs; first run will show GUI unless --headless is supplied",
|
||||
)
|
||||
p.add_argument(
|
||||
"--pull",
|
||||
action="store_true",
|
||||
help="Run 'git pull' before starting the client",
|
||||
)
|
||||
p.add_argument(
|
||||
"--gui",
|
||||
action="store_true",
|
||||
@@ -660,6 +709,23 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
else:
|
||||
repo_root = workspace_root
|
||||
|
||||
# Handle git pull update if requested
|
||||
if args.pull:
|
||||
if shutil.which("git"):
|
||||
if (repo_root / ".git").exists():
|
||||
if not args.quiet:
|
||||
print(f"Updating repository via 'git pull' in {repo_root}...")
|
||||
try:
|
||||
subprocess.run(["git", "pull"], cwd=str(repo_root), check=False)
|
||||
except Exception as e:
|
||||
print(f"Warning: git pull failed: {e}")
|
||||
else:
|
||||
if not args.quiet:
|
||||
print("Skipping 'git pull': directory is not a git repository.")
|
||||
else:
|
||||
if not args.quiet:
|
||||
print("Skipping 'git pull': 'git' not found on PATH.")
|
||||
|
||||
venv_py = find_venv_python(repo_root, args.venv, args.venv_name)
|
||||
|
||||
def _is_running_in_virtualenv() -> bool:
|
||||
@@ -817,7 +883,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||
repo_root,
|
||||
venv_py,
|
||||
headless=use_headless,
|
||||
detached=True
|
||||
detached=True,
|
||||
pull=args.pull,
|
||||
workspace_root=workspace_root
|
||||
)
|
||||
return 0 if ok else 6
|
||||
if args.uninstall_service:
|
||||
|
||||
Reference in New Issue
Block a user