mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-05 04:32:21 +01:00
Squash Odysseus development history
This commit is contained in:
+140
-31
@@ -11,6 +11,7 @@ import shutil
|
||||
import subprocess
|
||||
import uuid
|
||||
import tempfile
|
||||
import time
|
||||
from collections import namedtuple
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
@@ -22,6 +23,7 @@ from src.host_docker_access import (
|
||||
running_in_container as _running_in_container,
|
||||
)
|
||||
from src.optional_deps import prepare_optional_dependency_import
|
||||
from src.auth_helpers import _auth_disabled
|
||||
|
||||
# POSIX-only: `pty`/`fcntl` transitively import `termios`, which does NOT exist
|
||||
# on Windows, so importing them unconditionally crashed app startup there
|
||||
@@ -53,6 +55,11 @@ from core.platform_compat import (
|
||||
def _require_admin(request: Request):
|
||||
"""Reject non-admin callers. Shell exec is admin-only — never expose to
|
||||
regular users; that's RCE-after-signup."""
|
||||
# In the explicitly single-user, auth-disabled deployment the middleware
|
||||
# does not attach a current user. AuthManager is still instantiated by the
|
||||
# app, so checking only for its presence incorrectly returns 403 here.
|
||||
if _auth_disabled():
|
||||
return
|
||||
auth_manager = getattr(request.app.state, "auth_manager", None)
|
||||
if not auth_manager:
|
||||
# No auth at all — only safe in fully-trusted localhost dev mode
|
||||
@@ -78,6 +85,13 @@ def _reject_cross_site(request: Request):
|
||||
_SSH_PORT_RE = re.compile(r"^\d{1,5}$")
|
||||
_SAFE_VENV_RE = re.compile(r"^[A-Za-z0-9_./~-]+$")
|
||||
|
||||
# Dependency probes can involve several SSH/import checks. Keep the result
|
||||
# briefly so the Dependencies tab and a pre-launch check arriving together do
|
||||
# not repeat the same expensive work. Installation clears this cache.
|
||||
_PACKAGE_STATUS_CACHE: dict[tuple[str, ...], tuple[float, dict[str, Any]]] = {}
|
||||
_PACKAGE_STATUS_CACHE_TTL = 3.0
|
||||
_PACKAGE_STATUS_CACHE_MAX = 64
|
||||
|
||||
|
||||
def _ssh_base_argv(host: str, ssh_port: str | None) -> list[str]:
|
||||
"""Build an ssh argv prefix for remote probes without local-shell parsing."""
|
||||
@@ -204,6 +218,19 @@ def _package_installed_from_probe(name: str, probe: dict) -> bool:
|
||||
(dists.get("transformers") or modules.get("transformers", {}).get("real_module"))
|
||||
and (dists.get("torch") or modules.get("torch", {}).get("real_module"))
|
||||
)
|
||||
if name == "office_docs":
|
||||
return bool(
|
||||
dists.get("markitdown")
|
||||
or modules.get("markitdown", {}).get("real_module")
|
||||
or dists.get("python-docx")
|
||||
or modules.get("docx", {}).get("real_module")
|
||||
)
|
||||
if name == "psd_tools":
|
||||
return bool(dists.get("psd-tools") or modules.get("psd_tools", {}).get("real_module"))
|
||||
if name == "pymupdf":
|
||||
return bool(dists.get("PyMuPDF") or modules.get("fitz", {}).get("real_module"))
|
||||
if name == "libreoffice":
|
||||
return bool(binaries.get("soffice") or binaries.get("libreoffice"))
|
||||
if name == "hf_transfer":
|
||||
return bool(
|
||||
dists.get("hf-transfer")
|
||||
@@ -254,6 +281,28 @@ def _package_status_note(name: str, probe: dict) -> str:
|
||||
if _package_installed_from_probe(name, probe):
|
||||
return f"SAM object masks: transformers {dists.get('transformers', 'available')} with torch {dists.get('torch', 'available')}"
|
||||
return "SAM click/object mask selection needs transformers and torch."
|
||||
if name == "office_docs":
|
||||
if _package_installed_from_probe(name, probe):
|
||||
if dists.get("markitdown"):
|
||||
return f"Office document extraction: markitdown {dists['markitdown']}"
|
||||
if dists.get("python-docx"):
|
||||
return f"Word document extraction: python-docx {dists['python-docx']}"
|
||||
return "Office document extraction available"
|
||||
return "Office attachments need MarkItDown for full fidelity; DOCX has a basic built-in fallback."
|
||||
if name == "psd_tools":
|
||||
if _package_installed_from_probe(name, probe):
|
||||
return f"PSD support: psd-tools {dists.get('psd-tools', 'available')}"
|
||||
return "PSD files need psd-tools for layer/image parsing."
|
||||
if name == "pymupdf":
|
||||
if _package_installed_from_probe(name, probe):
|
||||
return f"PDF forms/rendering: PyMuPDF {dists.get('PyMuPDF', 'available')}"
|
||||
return "Advanced PDF open/render/form features need PyMuPDF."
|
||||
if name == "libreoffice":
|
||||
if binaries.get("soffice"):
|
||||
return f"DOCX signable preview converter: {binaries['soffice']}"
|
||||
if binaries.get("libreoffice"):
|
||||
return f"DOCX signable preview converter: {binaries['libreoffice']}"
|
||||
return "DOCX signing preview needs LibreOffice/soffice to convert Word files to PDF."
|
||||
if name == "mlx_lm":
|
||||
if _package_installed_from_probe(name, probe):
|
||||
return f"MLX LM {dists.get('mlx-lm', 'available')}"
|
||||
@@ -399,16 +448,21 @@ dist_names={{
|
||||
'diffusers':['diffusers','torch'],
|
||||
'krea_diffusers':['diffusers','torch'],
|
||||
'sam_mask':['transformers','torch'],
|
||||
'hf_transfer':['hf-transfer','hf_transfer'],
|
||||
}}
|
||||
bin_names={{
|
||||
'office_docs':['markitdown','python-docx'],
|
||||
'psd_tools':['psd-tools'],
|
||||
'pymupdf':['PyMuPDF'],
|
||||
'libreoffice':[],
|
||||
'hf_transfer':['hf-transfer','hf_transfer'],
|
||||
}}
|
||||
bin_names={{
|
||||
'vllm':['vllm'],
|
||||
'llama_cpp':['llama-server'],
|
||||
'mflux':['mflux-generate-qwen', 'mflux-generate'],
|
||||
'mlx_lama_swift':['odysseus-mlx-inpaint', 'mlx-lama-serve'],
|
||||
'mlx_ddcolor_swift':['odysseus-mlx-colorize', 'mlx-ddcolor-serve'],
|
||||
'tmux':['tmux'],
|
||||
}}
|
||||
'mlx_lama_swift':['odysseus-mlx-inpaint', 'mlx-lama-serve'],
|
||||
'mlx_ddcolor_swift':['odysseus-mlx-colorize', 'mlx-ddcolor-serve'],
|
||||
'libreoffice':['soffice', 'libreoffice'],
|
||||
'tmux':['tmux'],
|
||||
}}
|
||||
|
||||
def add_user_install_bins_to_path():
|
||||
candidates = []
|
||||
@@ -457,6 +511,13 @@ def probe(n):
|
||||
mods = {{n: mod_status(n)}}
|
||||
if n == 'diffusers':
|
||||
mods['torch'] = mod_status('torch')
|
||||
if n == 'office_docs':
|
||||
mods['markitdown'] = mod_status('markitdown')
|
||||
mods['docx'] = mod_status('docx')
|
||||
if n == 'psd_tools':
|
||||
mods['psd_tools'] = mod_status('psd_tools')
|
||||
if n == 'pymupdf':
|
||||
mods['fitz'] = mod_status('fitz')
|
||||
dists = dist_status(dist_names.get(n, [n]))
|
||||
bins = {{b: shutil.which(b) for b in bin_names.get(n, [])}}
|
||||
files = {{}}
|
||||
@@ -1145,6 +1206,7 @@ def setup_shell_routes() -> APIRouter:
|
||||
"make": {"debian": ["make"], "arch": ["make"], "fedora": ["make"], "alpine": ["make"], "suse": ["make"], "macos": []},
|
||||
"git": {"debian": ["git"], "arch": ["git"], "fedora": ["git"], "alpine": ["git"], "suse": ["git"], "macos": ["git"]},
|
||||
"tmux": {"debian": ["tmux"], "arch": ["tmux"], "fedora": ["tmux"], "alpine": ["tmux"], "suse": ["tmux"], "macos": ["tmux"]},
|
||||
"libreoffice": {"debian": ["libreoffice"], "arch": ["libreoffice-fresh"], "fedora": ["libreoffice"], "alpine": ["libreoffice"], "suse": ["libreoffice"], "macos": ["--cask", "libreoffice"]},
|
||||
}
|
||||
_BACKEND_EXTRAS = {
|
||||
"cuda": {"debian": ["nvidia-cuda-toolkit"], "arch": ["cuda"], "fedora": ["cuda-toolkit"], "alpine": [], "suse": ["cuda"], "macos": []},
|
||||
@@ -1206,13 +1268,16 @@ def setup_shell_routes() -> APIRouter:
|
||||
import sys
|
||||
|
||||
platform_l = (platform or "").strip().lower()
|
||||
model_hint_l = (model_hint or "").strip().lower()
|
||||
has_krea_model = "krea" in model_hint_l
|
||||
has_lama_mlx_model = any(
|
||||
key in model_hint_l
|
||||
for key in ("lama", "mi-gan", "migan", "inpainting-mlx")
|
||||
package_cache_key = (
|
||||
(host or "").strip(),
|
||||
(ssh_port or "").strip(),
|
||||
(venv or "").strip(),
|
||||
(backend or "").strip().lower(),
|
||||
platform_l,
|
||||
)
|
||||
has_ddcolor_mlx_model = "ddcolor" in model_hint_l
|
||||
cached_status = _PACKAGE_STATUS_CACHE.get(package_cache_key)
|
||||
if cached_status and time.monotonic() - cached_status[0] < _PACKAGE_STATUS_CACHE_TTL:
|
||||
return cached_status[1]
|
||||
_prepend_user_install_bins_to_path()
|
||||
importlib.invalidate_caches()
|
||||
try:
|
||||
@@ -1396,6 +1461,13 @@ def setup_shell_routes() -> APIRouter:
|
||||
"category": "Image",
|
||||
"target": "local",
|
||||
},
|
||||
{
|
||||
"name": "psd_tools",
|
||||
"pip": "psd-tools",
|
||||
"desc": "Open Photoshop PSD files and inspect flattened/layered image data",
|
||||
"category": "Image",
|
||||
"target": "local",
|
||||
},
|
||||
# ── Tools ──
|
||||
{
|
||||
"name": "playwright",
|
||||
@@ -1404,6 +1476,31 @@ def setup_shell_routes() -> APIRouter:
|
||||
"category": "Tools",
|
||||
"target": "local",
|
||||
},
|
||||
{
|
||||
"name": "office_docs",
|
||||
"pip": "markitdown[docx,pptx,xlsx,xls]",
|
||||
"desc": "Open Office attachments and documents (.docx, .pptx, .xlsx, .xls) as readable Markdown",
|
||||
"category": "Tools",
|
||||
"target": "local",
|
||||
},
|
||||
{
|
||||
"name": "pymupdf",
|
||||
"pip": "PyMuPDF",
|
||||
"desc": "Advanced PDF opening, rendering, forms, annotations, and signatures",
|
||||
"category": "Tools",
|
||||
"target": "local",
|
||||
},
|
||||
{
|
||||
"name": "libreoffice",
|
||||
"pip": "",
|
||||
"desc": "Convert DOCX attachments to signable PDF previews",
|
||||
"category": "Tools",
|
||||
"target": "local",
|
||||
"kind": "system",
|
||||
"system_prereqs": ["libreoffice"],
|
||||
"install_cmd": "sudo apt install -y libreoffice || brew install --cask libreoffice",
|
||||
"install_hint": "Install LibreOffice/soffice where Odysseus runs to open DOCX attachments as signable PDF previews. Without it, DOCX opens as readable Markdown.",
|
||||
},
|
||||
]
|
||||
|
||||
# Most packages should not be installed through external means. Hence, set the default of the
|
||||
@@ -1411,21 +1508,10 @@ def setup_shell_routes() -> APIRouter:
|
||||
for pkg in packages:
|
||||
pkg.setdefault("install_cmd", None)
|
||||
pkg.setdefault("update_cmd", None)
|
||||
if not has_krea_model:
|
||||
packages = [
|
||||
p for p in packages
|
||||
if p.get("name") not in {"krea_diffusers", "transformers"}
|
||||
]
|
||||
if not has_lama_mlx_model:
|
||||
packages = [
|
||||
p for p in packages
|
||||
if p.get("name") != "mlx_lama_swift"
|
||||
]
|
||||
if not has_ddcolor_mlx_model:
|
||||
packages = [
|
||||
p for p in packages
|
||||
if p.get("name") != "mlx_ddcolor_swift"
|
||||
]
|
||||
# Keep the Image section complete. Dependency visibility is a product
|
||||
# capability decision, not a substring test against a model id. Model
|
||||
# catalogs may declare an explicit runtime package, while the generic
|
||||
# backend preflight handles ordinary models.
|
||||
# Remote check: for remote-target packages, probe the selected server's
|
||||
# venv over SSH so a remote `pip install` actually reflects here.
|
||||
remote_status: dict = {}
|
||||
@@ -1596,6 +1682,14 @@ def setup_shell_routes() -> APIRouter:
|
||||
if IS_APPLE_SILICON
|
||||
else "Requires a native Apple Silicon Mac with Apple Foundational Models support."
|
||||
)
|
||||
elif pkg["name"] == "libreoffice":
|
||||
soffice_path = shutil.which("soffice") or shutil.which("libreoffice")
|
||||
pkg["installed"] = soffice_path is not None
|
||||
pkg["status_note"] = (
|
||||
f"DOCX signable preview converter: {soffice_path}"
|
||||
if soffice_path
|
||||
else "DOCX signing preview needs LibreOffice/soffice."
|
||||
)
|
||||
else:
|
||||
pkg["installed"] = shutil.which(pkg["name"]) is not None
|
||||
elif pkg["name"] == "llama_cpp" and shutil.which("llama-server"):
|
||||
@@ -1757,7 +1851,12 @@ def setup_shell_routes() -> APIRouter:
|
||||
)
|
||||
pkg["applicable"] = status.applicable
|
||||
pkg["install_hint"] = status.install_hint
|
||||
return {"packages": packages}
|
||||
result = {"packages": packages}
|
||||
if len(_PACKAGE_STATUS_CACHE) >= _PACKAGE_STATUS_CACHE_MAX:
|
||||
oldest_key = min(_PACKAGE_STATUS_CACHE, key=lambda key: _PACKAGE_STATUS_CACHE[key][0])
|
||||
_PACKAGE_STATUS_CACHE.pop(oldest_key, None)
|
||||
_PACKAGE_STATUS_CACHE[package_cache_key] = (time.monotonic(), result)
|
||||
return result
|
||||
|
||||
@router.post("/api/cookbook/packages/install")
|
||||
async def install_package(request: Request):
|
||||
@@ -1802,6 +1901,7 @@ def setup_shell_routes() -> APIRouter:
|
||||
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
_PACKAGE_STATUS_CACHE.clear()
|
||||
if proc.returncode == 0:
|
||||
return {"ok": True, "output": stdout.decode()[-200:]}
|
||||
return {"ok": False, "error": stderr.decode()[-300:]}
|
||||
@@ -1824,7 +1924,7 @@ def setup_shell_routes() -> APIRouter:
|
||||
ssh_port = body.get("ssh_port")
|
||||
# Names users can request — must match canonical names used in the
|
||||
# deps catalog's `system_prereqs` field and on the System rows.
|
||||
ALLOWED = {"cmake", "build-essential", "g++", "gcc", "git", "tmux", "make"}
|
||||
ALLOWED = {"cmake", "build-essential", "g++", "gcc", "git", "tmux", "make", "libreoffice"}
|
||||
pkgs = [str(p).strip() for p in raw if str(p).strip() in ALLOWED]
|
||||
if not pkgs:
|
||||
return {"ok": False, "error": "no installable packages requested (allowlist: " + ", ".join(sorted(ALLOWED)) + ")"}
|
||||
@@ -1854,7 +1954,15 @@ def setup_shell_routes() -> APIRouter:
|
||||
else: out.append(n)
|
||||
return out
|
||||
def _brew(names):
|
||||
return [n for n in names if n not in ("build-essential", "g++", "gcc", "make")]
|
||||
out = []
|
||||
for n in names:
|
||||
if n in ("build-essential", "g++", "gcc", "make"):
|
||||
continue
|
||||
if n == "libreoffice":
|
||||
out += ["--cask", "libreoffice"]
|
||||
else:
|
||||
out.append(n)
|
||||
return out
|
||||
# Build a single shell snippet that detects the package manager and
|
||||
# runs the right install. Non-interactive sudo (-n) only — if sudo
|
||||
# asks for a password the script reports it instead of hanging.
|
||||
@@ -1920,6 +2028,7 @@ def setup_shell_routes() -> APIRouter:
|
||||
combined = err_txt or tail_out or f"exit code {proc.returncode}"
|
||||
else:
|
||||
combined = None
|
||||
_PACKAGE_STATUS_CACHE.clear()
|
||||
return {
|
||||
"ok": ok,
|
||||
"exit_code": proc.returncode,
|
||||
|
||||
Reference in New Issue
Block a user