ai: package llama-server for all three deployments, with idle unload

install_llama_cpp.sh builds llama-server from the SHA-pinned v0.3.0
source tarball (single source of truth: on-device upgrade, Pi image,
Live ISO, and the new docker/llm container all call it; version-marker
makes re-runs no-ops).  start_llama_server.sh refuses to start unless
ai.yaml enables AI and the active model file exists — which also covers
unmounted media.  wrolpi-llm.service is installed but deliberately
never enabled at boot: the Controller starts it on demand (registered
in managed_services; the llm compose service follows the samba
pattern), and the API's perpetual idle-unload worker asks the
Controller to stop it after the configured idle minutes.  llama-server
is localhost-only natively, internal-network-only in docker, and never
proxied by Caddy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-08-25 21:36:00 -06:00
parent 256750e538
commit d9924b6b53
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
18 changed files with 406 additions and 4 deletions

View file

@ -78,6 +78,16 @@ DEFAULT_CONFIG = {
"use_https": True,
"description": "Kiwix/Zim server",
},
{
"name": "wrolpi-llm",
"systemd_name": "wrolpi-llm",
"port": 11435,
"viewable": False,
"use_https": False,
"description": "AI assistant (llama-server)",
# On-demand: started by the Controller when AI is enabled, stopped by the API when idle.
"kind": "persistent",
},
# System services
{
"name": "caddy",

View file

@ -27,7 +27,7 @@ except ImportError:
CONTAINER_PREFIX = os.environ.get("COMPOSE_PROJECT_NAME", "wrolpi")
# Containers that should not have a "View" button (non-HTTP services)
NON_VIEWABLE_CONTAINERS = {"redis", "memcached", "samba"}
NON_VIEWABLE_CONTAINERS = {"redis", "memcached", "samba", "llm"}
# Containers that should use HTTPS
HTTPS_CONTAINERS = {"web"}

View file

@ -57,7 +57,7 @@ class TestManagedServicesConfig:
def test_managed_services_count(self):
"""Should have expected number of managed services."""
services = DEFAULT_CONFIG["managed_services"]
assert len(services) == 11
assert len(services) == 12
@pytest.mark.parametrize("name,expected", [
("wrolpi-api",
@ -68,6 +68,8 @@ class TestManagedServicesConfig:
("wrolpi-app-dev", {"port": 3000, "viewable": False, "show_only_when_running": True}),
("caddy", {"port": 443}),
("wrolpi-upgrade", {"port": None, "viewable": False, "show_only_when_running": True}),
("wrolpi-llm",
{"systemd_name": "wrolpi-llm", "port": 11435, "viewable": False, "kind": "persistent"}),
])
def test_service_config(self, name, expected):
"""Should have service configured with expected values."""