From 7c070224bd71ea96d134bdb625e50f76f05a27d0 Mon Sep 17 00:00:00 2001 From: Niranjan Date: Tue, 7 Apr 2026 04:28:40 +0530 Subject: [PATCH] new changes --- .../app/api/__pycache__/soft.cpython-314.pyc | Bin 5597 -> 16071 bytes YakPanel-server/backend/app/api/soft.py | 93 +++++++++++++----- .../core/__pycache__/utils.cpython-314.pyc | Bin 7489 -> 8927 bytes YakPanel-server/backend/app/core/utils.py | 20 ++++ .../frontend/src/pages/SoftPage.tsx | 2 +- YakPanel-server/install.sh | 3 +- 6 files changed, 94 insertions(+), 24 deletions(-) diff --git a/YakPanel-server/backend/app/api/__pycache__/soft.cpython-314.pyc b/YakPanel-server/backend/app/api/__pycache__/soft.cpython-314.pyc index 876c2fe99e24975fab529d97fe598d86010d3754..d6ba5970d56d2f62d8852bf94a4390c7dd63a0ab 100644 GIT binary patch literal 16071 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 TU*;P&5w;UyaG7D&7$&K literal 5597 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 TR+#$799xOwfxm)dLEZlWdh!w{ diff --git a/YakPanel-server/backend/app/api/soft.py b/YakPanel-server/backend/app/api/soft.py index b68fd989..7f2b3e45 100644 --- a/YakPanel-server/backend/app/api/soft.py +++ b/YakPanel-server/backend/app/api/soft.py @@ -10,13 +10,13 @@ from typing import Literal from fastapi import APIRouter, Depends, HTTPException -from app.core.utils import exec_shell_sync +from app.core.utils import environment_with_system_path, exec_shell_sync from app.api.auth import get_current_user from app.models.user import User router = APIRouter(prefix="/soft", tags=["soft"]) -PmKind = Literal["apt", "dnf", "yum", "apk", "none"] +PmKind = Literal["apt", "dnf", "yum", "microdnf", "apk", "none"] # Per distro: apt = Debian/Ubuntu, rpm = RHEL/Fedora/Alma/Rocky, apk = Alpine (best-effort) SOFTWARE_LIST: list[dict[str, str]] = [ @@ -135,14 +135,30 @@ SOFTWARE_LIST: list[dict[str, str]] = [ ] +def _resolve_command(name: str) -> str | None: + """Locate an executable; systemd often provides a venv-only PATH, so scan standard dirs too.""" + std = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + merged = f"{os.environ.get('PATH', '').strip()}:{std}".strip(":") + found = shutil.which(name, path=merged) + if found: + return found + for d in ("/usr/bin", "/usr/sbin", "/bin", "/sbin", "/usr/local/bin", "/usr/local/sbin"): + cand = os.path.join(d, name) + if os.path.isfile(cand) and os.access(cand, os.X_OK): + return cand + return None + + def _detect_package_manager() -> PmKind: - if shutil.which("apt-get"): + if _resolve_command("apt-get"): return "apt" - if shutil.which("dnf"): + if _resolve_command("dnf"): return "dnf" - if shutil.which("yum"): + if _resolve_command("yum"): return "yum" - if shutil.which("apk"): + if _resolve_command("microdnf"): + return "microdnf" + if _resolve_command("apk"): return "apk" return "none" @@ -150,7 +166,7 @@ def _detect_package_manager() -> PmKind: def _package_name(entry: dict[str, str], pm: PmKind) -> str: if pm == "apt": return entry["apt"] - if pm in ("dnf", "yum"): + if pm in ("dnf", "yum", "microdnf"): return entry["rpm"] if pm == "apk": return entry.get("apk") or entry["apt"] @@ -158,20 +174,21 @@ def _package_name(entry: dict[str, str], pm: PmKind) -> str: def _apt_env() -> dict[str, str]: - env = os.environ.copy() + env = environment_with_system_path() env.setdefault("DEBIAN_FRONTEND", "noninteractive") env.setdefault("APT_LISTCHANGES_FRONTEND", "none") return env def _run_shell(script: str, timeout: int, env: dict[str, str] | None = None) -> None: + run_env = environment_with_system_path(env) result = subprocess.run( script, shell=True, capture_output=True, text=True, timeout=timeout, - env=env if env is not None else os.environ.copy(), + env=run_env, ) if result.returncode == 0: return @@ -192,11 +209,13 @@ def _check_installed_apt(pkg: str) -> tuple[bool, str]: def _check_installed_rpm(pkg: str) -> tuple[bool, str]: try: + rpm_bin = _resolve_command("rpm") or "rpm" result = subprocess.run( - ["rpm", "-q", "--queryformat", "%{EVR}", pkg], + [rpm_bin, "-q", "--queryformat", "%{EVR}", pkg], capture_output=True, text=True, timeout=5, + env=environment_with_system_path(), ) if result.returncode != 0: return False, "" @@ -208,11 +227,13 @@ def _check_installed_rpm(pkg: str) -> tuple[bool, str]: def _check_installed_apk(pkg: str) -> tuple[bool, str]: try: + apk_bin = _resolve_command("apk") or "apk" r = subprocess.run( - ["apk", "info", "-e", pkg], + [apk_bin, "info", "-e", pkg], capture_output=True, text=True, timeout=5, + env=environment_with_system_path(), ) if r.returncode != 0: return False, "" @@ -228,7 +249,7 @@ def _check_installed_apk(pkg: str) -> tuple[bool, str]: def _check_installed(pm: PmKind, pkg: str) -> tuple[bool, str]: if pm == "apt": return _check_installed_apt(pkg) - if pm in ("dnf", "yum"): + if pm in ("dnf", "yum", "microdnf"): return _check_installed_rpm(pkg) if pm == "apk": return _check_installed_apk(pkg) @@ -238,32 +259,60 @@ def _check_installed(pm: PmKind, pkg: str) -> tuple[bool, str]: def _install_script(pm: PmKind, pkg: str) -> tuple[str, int, dict[str, str] | None]: q = shlex.quote(pkg) if pm == "apt": - return f"apt-get update -qq && apt-get install -y {q}", 600, _apt_env() + ag = _resolve_command("apt-get") + if not ag: + raise HTTPException(status_code=501, detail="apt-get not found on this system.") + exe = shlex.quote(ag) + return f"{exe} update -qq && {exe} install -y {q}", 600, _apt_env() if pm == "dnf": - return f"dnf install -y {q}", 600, None + exe = shlex.quote(x) if (x := _resolve_command("dnf")) else None + if exe: + return f"{exe} install -y {q}", 600, None if pm == "yum": - return f"yum install -y {q}", 600, None + exe = shlex.quote(x) if (x := _resolve_command("yum")) else None + if exe: + return f"{exe} install -y {q}", 600, None + if pm == "microdnf": + exe = shlex.quote(x) if (x := _resolve_command("microdnf")) else None + if exe: + return f"{exe} install -y {q}", 600, None if pm == "apk": - return f"apk update && apk add {q}", 600, None + exe = shlex.quote(x) if (x := _resolve_command("apk")) else None + if exe: + return f"{exe} update && {exe} add {q}", 600, None raise HTTPException( status_code=501, - detail="No supported package manager found (need apt-get, dnf, yum, or apk).", + detail="No supported package manager found (need apt-get, dnf, yum, microdnf, or apk).", ) def _uninstall_script(pm: PmKind, pkg: str) -> tuple[str, int, dict[str, str] | None]: q = shlex.quote(pkg) if pm == "apt": - return f"apt-get remove -y {q}", 180, _apt_env() + ag = _resolve_command("apt-get") + if ag: + exe = shlex.quote(ag) + return f"{exe} remove -y {q}", 180, _apt_env() + raise HTTPException(status_code=501, detail="apt-get not found on this system.") if pm == "dnf": - return f"dnf remove -y {q}", 180, None + exe = shlex.quote(x) if (x := _resolve_command("dnf")) else None + if exe: + return f"{exe} remove -y {q}", 180, None if pm == "yum": - return f"yum remove -y {q}", 180, None + exe = shlex.quote(x) if (x := _resolve_command("yum")) else None + if exe: + return f"{exe} remove -y {q}", 180, None + if pm == "microdnf": + exe = shlex.quote(x) if (x := _resolve_command("microdnf")) else None + if exe: + return f"{exe} remove -y {q}", 180, None if pm == "apk": - return f"apk del {q}", 120, None + exe = shlex.quote(x) if (x := _resolve_command("apk")) else None + if exe: + return f"{exe} del {q}", 120, None raise HTTPException( status_code=501, - detail="No supported package manager found (need apt-get, dnf, yum, or apk).", + detail="No supported package manager found (need apt-get, dnf, yum, microdnf, or apk).", ) diff --git a/YakPanel-server/backend/app/core/__pycache__/utils.cpython-314.pyc b/YakPanel-server/backend/app/core/__pycache__/utils.cpython-314.pyc index 6d3b851b95bd90bbcb12d70bcaf0adb0c1a259f4..ee5fcc94dc63724a82abf3e187802d3b2829674b 100644 GIT binary patch delta 3467 zcmaJ@Z%iD=72nz2+xyFLaK~{RM_5b@KI}jg|3@yy2B%J3Q?l5ER=B6*Zh^Cp+w1I} z|Ij9PN>quYj%;_V%2M4Zid?BkZJZXTU)nGJ(|&0x*)>tpjicm4`yo{#L2Z{0m7=~k zdzd(FJkq{>|K`oio8P>dtGyrZ3vLY7_ylO1dlz$WhRy|J3|!@iz_Ks= zQSvMc$nD=77jeDV*0{~$rnsX+s)?G^Bf`k?r|4@-df~XOP~9rg!cbDQR15y5PW6_kczM(xX1um=Jev=rSPT98?m1cEphpmmZQH5 zo@9sH6!Drf}fiG7pG?9M&KqF%_Vrj;&a(=@AM#;u%DFjdP? zhxZ)XujVFIp8JV%uAt^jbtY$;xx!SpEu+~MW<8EMR~WKMZ&I?QDc!PNkgr_P4x~oD zGjeF(!4&!?MXJEiwv;JR6La06Qa>u{^@3TVddfUyTKY_?n6{>A6bwA81He>-PyE5} zp4tA&_Osi5y!(B>deg67n0VjUdCS*%+n1~ieBiD-JF@7G{mm2n_=zXaj-1_hq3+_Y z3*EQslUMSUfjQ;n1GEML@tm*b9Vdf%zxygerDqKwYw26#9tI5nh`rU=TK+ma2b4rz z5Z#t1(xy(`z$PV%<53S1FDeMd^XR~d=Yk@dIEkU*DIf?bnba;LxpeZ1d}ZVh?Qf60 z6MV08_t+9D-`%n}JhFIr?B?*;U;4P)+FGGMZ#XV|^4;UWAii`Cm+{qHfM!dTKiD!Z zvRm~nY~CAYp9egEyK$T>Z=+<@trf5a7$UPn14i+bYD}s{YGicNRN(-r8w{%Ax|8aO zoHgwrX{wZ_>4K%}Sp-+Ekja;_y6SAY3ddH>=BU}7bn*PyqG8%@2$d^SqhQM!7=(iY zP=u-BPsJ$(kIo;xY|I1=8;m)Gew)Gze8 z&yc6l-*#sV*tdekJFY?*F+tnVEKe9l{!!E1%EI2Qq-Nn6?<=JI1op!z4$*F)Pbb}c zcD83GeN0CP&?hmd8`wg6MyGfNY}W~zvvj%z_@qqvB6a}pOlT``rVOOx!D4*e-&svR zSh!0Gn99O){xFIAfc?M6uAjXaXm>J7ct(F8c${plj=L9n*qnd;hCCsK#wDjT)^K`1YiC6_SagL+$ebj#eaJ9d1-FPk5bEC z&^UDnQMTH!^u3IAXi$XWxDLSnh1|yeTz@pWR%0;P*fg@;#de0*vyYm>N}Dh$4cFj3 z$j*hIAr0)q@J>$Gv))L81ljYER`%0Kv(hRYl7_?F@i&qDVcAwpD{GW2{?{p`(-4|h zkD*UDLCJ$nm_iL+HXN4po4~>GQNY=tCY*0H&3X^zW}tFGq@dJZ$mP5>r?#?t0s^n@D8HmbP<1+HCoda>PUPU$;I#z{@~(r{i4j-8~fiK`Qt&(E*_a!%uFs$AG?`3_TG^^ z@9@8v@e0A}<#h@6t0FZ2#MR2|n7VEp?^oU)Kt|e*w>q?<1b9(czNtN2)i`>GvU9Eo z`(0DmC4-lbd*NyLN~?Nxve8Ji6QU%9RzdTY#1^4l7>7AIO%bwiu_T0|qPfTGSUhf( z3t&mQ;1PuTY6mmvBD^N`6yTs(veuBoQMiUVYCty*wjug`jNqjf{A`Jq3LI<>rhb54 z99ThwBm~`evY4ZKmRCo-jK&41sb2vlylBl*-l8vH?-wyF(M>n}t9W$aSi1&CxIGIQ zItLznU*7?yA}qKPFV^Gd&z#nE;p}ZzivKK##;MQ^WFyG z^*s@wS2wo-z1Gk;P%B;Q_8{F(knRW!_@wJH^4EPXph+?WZ)^e&?OE}}#w0nE6o-bA zQQMtLWsOWK<=hh@)oBL|K`9c<5Zw>NR;*J+xa=rqHK(HrkH_ zYcwy7{NVAG&I=rUPA)vVW{hkpulLb2;E9hTbBg^V(bK=A2%#ZT3EWrkrN0t_C)oX< jOg60q10=Tcn1?jnk9tUCCF&!gl{l2am2Io1frtMueI@pl delta 2073 zcmaJ?O>7%Q6yEW!*K23J@n3AmP7^0?6P&gwYMZv{AJ8OKTGS|Y(^k*~XS3cA6WeQM zHzer+2M}rzR8S^2P)~rAQzcR%!HJ}S0|E(&Qs@HefgY$3lB1-GxFFt)lOiFtEBTxE zy?OIy=Dp`PcY7BPD%%yWhl6kKyB~6z_o@;_x39Jtz}ak9Q&ZS5tf3ioqC{PHXhvaZbDy0B)wFc{A2)UU3h+@?o6w8dA+=N*(hRH* zHB7>B%#PBGEoU-nv1nMTrDrnO3;TGrD?%soxg@|Vjg?*r%N4BIFH6shRPl>j}FiDI2M0v${ZLkpJtE;>%_ZI@;gExl;5h1dnE6p!67U|ImS zFlre2#~m(7PAHvlxU%vVD#z$LdcuAj2Rxo~;VsmQR8S}MYz~{aky00kMYW*g1ZB=C zoP)5pfSeLAyF@F<)dM9#G6jIvhnMj%`|52vV1}7Yf-t*q$rnViN5-*(-0&yis6YFU zAZcT}C&`vzBusN-d!|h@lhq-tIz9x74stU1TNgX96ADSwtI=^(qtgZ!kBdte840z+ zW^aeKA#pbt<2~fFy1gRxhmVj-T{O;itu3k6c*a!6^o%yHYZLf17<7?j_*tk9Bm7tS zk=Hp6hUU`%v)notL7^22zq;iL592*=-JE_UQ*na4CUTy@T=L46ii0k@ti&+AomTqT9$(Da`W&HN+o?kvq{7C`kNG zK~ky@kiPoRLk5v+3fJu7xCX9?8{`LSslCWVKU?~Def|`0i>8$|CM`DV7~^xWYybTn z@Nb2{o&{o8iwSpv3^gx;k%7&52Ob8+MfYH?pl1`{A>|=o#P*Wa*x~pf%&@i0n=O>E zq2cGL@&Y*-k5^;v(WiASV~*?jJTAf31mef zoefWEN>2nW`cSu5So8~24!3ptxy7S3fJ<)FT`MekL@N7SFuN3Opx9Xtc(WFP{-*yx zPl#W3qaImY_PD6*cfjm&5K%eA_e97~4WAay&;y?bcwB{zz`S_`fac$aVk;Cr3|8py z(ew@H$7Loi_kDZjt5Zz8H9UGt8@n|=@x3?5)vW%!s`3P*VwD1`Tb7t z3SWso$CGEGA@XOVpB#hnt`a24rl3F%8_ z<*=S*9fSQDq0Rg+@9-j+lX(m0>EZ%ijC9jW%bNBP_^?c_C+Y%Ipc#Rmc^zPus|X6Y zooH0nYQ%u&vRF~LW2g_29nCGIy?MCY1xByOkICG%7{KRG0xY&S11`ytZjoQwCQwLOIfQ;z=rRM2*1GFd}|r-QXg2i9>A_P{Cy8V9@& zz!t5U60~~aYzg(9qFVNhY^4%yfUH}wzQL*)(@V{B>|b4N|I*=tk)6!zNAasLMPEa6 qkd#_>?R+2#XvexDqv(30gz6rIB@|i@dysEE27O_D?`BmoU;hHUUEdo3 diff --git a/YakPanel-server/backend/app/core/utils.py b/YakPanel-server/backend/app/core/utils.py index ab006636..dc92b065 100644 --- a/YakPanel-server/backend/app/core/utils.py +++ b/YakPanel-server/backend/app/core/utils.py @@ -9,6 +9,24 @@ from typing import Tuple, Optional regex_safe_path = re.compile(r"^[\w\s./\-]*$") +# systemd often sets PATH to venv-only; subprocess shells then miss /usr/bin (dnf, apt-get, …). +_SYSTEM_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + + +def ensure_system_path(env: dict[str, str]) -> None: + """Append standard locations to PATH if /usr/bin is missing.""" + cur = (env.get("PATH") or "").strip() + if "/usr/bin" in cur: + return + env["PATH"] = f"{cur}:{_SYSTEM_PATH}" if cur else _SYSTEM_PATH + + +def environment_with_system_path(base: Optional[dict[str, str]] = None) -> dict[str, str]: + """Copy of process env (or base) with PATH guaranteed to include system bin dirs.""" + env = dict(base) if base is not None else os.environ.copy() + ensure_system_path(env) + return env + def md5(strings: str | bytes) -> str: """Generate MD5 hash""" @@ -78,6 +96,7 @@ async def exec_shell( stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd, + env=environment_with_system_path(), ) try: stdout, stderr = await asyncio.wait_for( @@ -102,6 +121,7 @@ def exec_shell_sync(cmd: str, timeout: Optional[float] = None, cwd: Optional[str capture_output=True, timeout=timeout or 300, cwd=cwd, + env=environment_with_system_path(), ) out = result.stdout.decode("utf-8", errors="replace") if result.stdout else "" err = result.stderr.decode("utf-8", errors="replace") if result.stderr else "" diff --git a/YakPanel-server/frontend/src/pages/SoftPage.tsx b/YakPanel-server/frontend/src/pages/SoftPage.tsx index 714505b1..cc616b43 100644 --- a/YakPanel-server/frontend/src/pages/SoftPage.tsx +++ b/YakPanel-server/frontend/src/pages/SoftPage.tsx @@ -75,7 +75,7 @@ export function SoftPage() {
Installs use your server package manager ({detectedPm || '…loading…'}). Panel must run as root - (or equivalent). Supported: Debian/Ubuntu (apt), RHEL/Fedora/Alma/Rocky (dnf/yum), Alpine (apk). + (or equivalent). Supported: apt, dnf/yum/microdnf, apk.
diff --git a/YakPanel-server/install.sh b/YakPanel-server/install.sh index 80e2daad..759e1a0b 100644 --- a/YakPanel-server/install.sh +++ b/YakPanel-server/install.sh @@ -288,7 +288,8 @@ $REDIS_WANTS Type=simple User=root WorkingDirectory=$INSTALL_PATH/backend -Environment="PATH=$INSTALL_PATH/backend/venv/bin:\$PATH" +# Include system paths: systemd does not expand \$PATH reliably; venv-only PATH breaks dnf/apt from the API. +Environment="PATH=$INSTALL_PATH/backend/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" Environment=PYTHONUNBUFFERED=1 ExecStart=$INSTALL_PATH/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port $BACKEND_PORT Restart=always