Fix PATH for apt/git/nmcli in systemd service environment
systemd runs services with a minimal PATH that may not include /usr/bin or /usr/sbin. Add _safe_env() helpers that ensure standard paths are present, and expand apt-cache discovery to check common locations directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9c402e3726
commit
0ec9726db4
3 changed files with 52 additions and 6 deletions
|
|
@ -46,6 +46,17 @@ def _require_nmcli():
|
|||
raise HTTPException(503, "NetworkManager (nmcli) not available on this system")
|
||||
|
||||
|
||||
def _safe_env():
|
||||
"""Build environment with full PATH for nmcli commands."""
|
||||
env = {**os.environ, "LC_ALL": "C"}
|
||||
path = env.get("PATH", "")
|
||||
for p in ("/usr/bin", "/usr/sbin", "/bin", "/sbin"):
|
||||
if p not in path:
|
||||
path = p + ":" + path
|
||||
env["PATH"] = path
|
||||
return env
|
||||
|
||||
|
||||
async def _nmcli(*args: str, timeout: float = 30) -> str:
|
||||
"""Run nmcli with C locale, return stdout. Raise HTTPException on failure."""
|
||||
_require_nmcli()
|
||||
|
|
@ -54,7 +65,7 @@ async def _nmcli(*args: str, timeout: float = 30) -> str:
|
|||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env={**os.environ, "LC_ALL": "C"},
|
||||
env=_safe_env(),
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,19 @@ router = APIRouter(prefix="/api/packages", tags=["packages"])
|
|||
# Guard
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_HAS_APT = bool(shutil.which("apt-cache"))
|
||||
def _find_apt():
|
||||
"""Find apt-cache binary, checking common paths if PATH is restricted."""
|
||||
found = shutil.which("apt-cache")
|
||||
if found:
|
||||
return True
|
||||
# systemd services may have a minimal PATH — check directly
|
||||
for p in ("/usr/bin/apt-cache", "/usr/sbin/apt-cache"):
|
||||
if os.path.isfile(p) and os.access(p, os.X_OK):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
_HAS_APT = _find_apt()
|
||||
|
||||
|
||||
def _require_apt():
|
||||
|
|
@ -38,6 +50,18 @@ def _validate_name(name: str) -> str:
|
|||
return name
|
||||
|
||||
|
||||
def _safe_env():
|
||||
"""Build environment with full PATH for apt/dpkg commands."""
|
||||
env = {**os.environ, "LC_ALL": "C", "DEBIAN_FRONTEND": "noninteractive"}
|
||||
# Ensure standard paths are included (systemd may use minimal PATH)
|
||||
path = env.get("PATH", "")
|
||||
for p in ("/usr/bin", "/usr/sbin", "/bin", "/sbin"):
|
||||
if p not in path:
|
||||
path = p + ":" + path
|
||||
env["PATH"] = path
|
||||
return env
|
||||
|
||||
|
||||
async def _apt(*args: str, timeout: float = 30) -> str:
|
||||
"""Run an apt/dpkg command and return stdout."""
|
||||
_require_apt()
|
||||
|
|
@ -46,7 +70,7 @@ async def _apt(*args: str, timeout: float = 30) -> str:
|
|||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env={**os.environ, "LC_ALL": "C", "DEBIAN_FRONTEND": "noninteractive"},
|
||||
env=_safe_env(),
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
|
@ -68,7 +92,7 @@ async def _apt_nofail(*args: str, timeout: float = 30) -> tuple[int, str, str]:
|
|||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env={**os.environ, "LC_ALL": "C", "DEBIAN_FRONTEND": "noninteractive"},
|
||||
env=_safe_env(),
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ def _require_git():
|
|||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _safe_env():
|
||||
"""Build environment with full PATH for git commands."""
|
||||
env = {**os.environ, "LC_ALL": "C"}
|
||||
path = env.get("PATH", "")
|
||||
for p in ("/usr/bin", "/usr/sbin", "/bin", "/sbin"):
|
||||
if p not in path:
|
||||
path = p + ":" + path
|
||||
env["PATH"] = path
|
||||
return env
|
||||
|
||||
|
||||
async def _git(*args: str, timeout: float = 30) -> str:
|
||||
"""Run a git command in the Atlus install directory."""
|
||||
_require_git()
|
||||
|
|
@ -38,7 +49,7 @@ async def _git(*args: str, timeout: float = 30) -> str:
|
|||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env={**os.environ, "LC_ALL": "C"},
|
||||
env=_safe_env(),
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
|
@ -60,7 +71,7 @@ async def _git_nofail(*args: str, timeout: float = 30) -> tuple[int, str]:
|
|||
*cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env={**os.environ, "LC_ALL": "C"},
|
||||
env=_safe_env(),
|
||||
)
|
||||
try:
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
||||
|
|
|
|||
Loading…
Reference in a new issue