diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 931d0ea..181a577 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -6,6 +6,24 @@ on: pull_request: jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install package with dev extras + run: pip install -e ".[dev]" + + - name: Ruff (lint) + run: ruff check src/ tests/ + + - name: Mypy (type check) + run: mypy + test: runs-on: ubuntu-latest strategy: diff --git a/docs/SECURITY_AND_OPERATIONS.md b/docs/SECURITY_AND_OPERATIONS.md index 6d1d2b6..62b1230 100644 --- a/docs/SECURITY_AND_OPERATIONS.md +++ b/docs/SECURITY_AND_OPERATIONS.md @@ -34,7 +34,8 @@ testbar von der Orchestrierung. 1. **Eingebaute Defaults** (`config.builtin_defaults()`) — spiegeln die Standardwerte der ursprünglichen Shell-Skripte wider (Image - `ghcr.io/ggml-org/llama.cpp:server-cuda`, `host_port=8001`, + `ghcr.io/ggml-org/llama.cpp` per Digest gepinnt für Reproduzierbarkeit, + `host_port=8001`, `container_name=va_llm`, `gpu_device=1`, `jinja/fa/kv_unified/ cont_batching/no_context_shift=true`, `reasoning=on`, `cache_type_k/v=q4_0`, `batch_size=1024`, `ubatch_size=512`, diff --git a/llama.cpp.config.example b/llama.cpp.config.example index f9f5e77..628da02 100644 --- a/llama.cpp.config.example +++ b/llama.cpp.config.example @@ -14,7 +14,9 @@ # locking (--change), and --stop/--check targeting. [default] -image = ghcr.io/ggml-org/llama.cpp:server-cuda +# Pinned by digest for reproducibility. The :server-cuda tag is a moving target; +# to update, pull it, read the new digest, and replace the pin below. +image = ghcr.io/ggml-org/llama.cpp@sha256:5535de118ed457f761cbfeacd7e10fef31cb391ca7cac1d5c78b11d28fcf88e6 # hf_home unterstützt Environment-Variablen und ~, z. B. hf_home = ${HF_HOME} hf_home = /srv/models model_path = qwen3/default.gguf diff --git a/pyproject.toml b/pyproject.toml index 4a3e8d0..55eea2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,9 @@ dependencies = [ [project.optional-dependencies] dev = [ "pytest>=7.4", + "ruff>=0.6", + "mypy>=1.11", + "types-requests", ] [project.scripts] @@ -27,3 +30,13 @@ where = ["src"] [tool.setuptools.package-data] llamacppctl = ["py.typed"] + +[tool.ruff] +target-version = "py310" +line-length = 100 + +[tool.mypy] +python_version = "3.10" +files = ["src/llamacppctl"] +warn_unused_ignores = true +warn_redundant_casts = true diff --git a/requirements-dev.txt b/requirements-dev.txt index 167e7b9..697d674 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,6 @@ # in pyproject.toml). -r requirements.txt pytest>=7.4 +ruff>=0.6 +mypy>=1.11 +types-requests diff --git a/src/llamacppctl/config.py b/src/llamacppctl/config.py index 353f910..301ba9a 100644 --- a/src/llamacppctl/config.py +++ b/src/llamacppctl/config.py @@ -36,7 +36,10 @@ class ConfigError(ValueError): def builtin_defaults() -> dict: """Mirrors the original start-llm-server.sh / status-llm-server.sh defaults.""" return { - "image": "ghcr.io/ggml-org/llama.cpp:server-cuda", + # Pinned by digest for reproducibility (the :server-cuda tag is a moving + # target). To update: docker pull ghcr.io/ggml-org/llama.cpp:server-cuda, + # read the new digest, and bump it here (or override `image` in the config). + "image": "ghcr.io/ggml-org/llama.cpp@sha256:5535de118ed457f761cbfeacd7e10fef31cb391ca7cac1d5c78b11d28fcf88e6", "hf_home": "/models", "model_path": "qwen3/default.gguf", "container_name": "va_llm", diff --git a/src/llamacppctl/docker_ops.py b/src/llamacppctl/docker_ops.py index 08e8f44..40c0669 100644 --- a/src/llamacppctl/docker_ops.py +++ b/src/llamacppctl/docker_ops.py @@ -11,7 +11,6 @@ import shlex import subprocess from dataclasses import dataclass from pathlib import Path -from typing import Optional from .schema import ServerConfig diff --git a/src/llamacppctl/lock_ops.py b/src/llamacppctl/lock_ops.py index dd177d4..c3b7d57 100644 --- a/src/llamacppctl/lock_ops.py +++ b/src/llamacppctl/lock_ops.py @@ -8,6 +8,7 @@ from __future__ import annotations import fcntl from pathlib import Path +from typing import TextIO class LockError(RuntimeError): @@ -17,7 +18,7 @@ class LockError(RuntimeError): class FileLock: def __init__(self, path: Path): self.path = Path(path) - self.fd = None + self.fd: TextIO | None = None def __enter__(self) -> "FileLock": self.path.parent.mkdir(parents=True, exist_ok=True) diff --git a/src/llamacppctl/prompt_io.py b/src/llamacppctl/prompt_io.py index ee04ac5..32a8b67 100644 --- a/src/llamacppctl/prompt_io.py +++ b/src/llamacppctl/prompt_io.py @@ -202,7 +202,10 @@ def _pin_dns(host: str, allowed_ips: list): ) return results - socket.getaddrinfo = pinned + # Intentional monkeypatch: pin DNS to the pre-validated addresses for the + # duration of the request (SSRF/rebinding defense). Signature differs from + # the stdlib function, hence the targeted ignore. + socket.getaddrinfo = pinned # type: ignore[assignment] try: yield finally: