# Copyright 1999-2026 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 PYTHON_COMPAT=( python3_{12..15} ) inherit cmake desktop git-r3 python-any-r1 systemd DESCRIPTION="Local AI server: optimized LLM inference on AMD NPU + GPU" HOMEPAGE=" https://lemonade-server.ai/ https://github.com/lemonade-sdk/lemonade " EGIT_REPO_URI="https://github.com/lemonade-sdk/lemonade.git" LICENSE="Apache-2.0" SLOT="0" KEYWORDS="~amd64" IUSE="+system-fastflowlm +system-llamacpp +system-rocm tauri webui" # Missing cpp-httplib pkg-config metadata or optional dependencies make # upstream fall back to FetchContent downloads at configure time; webui and # tauri fetch npm modules / Rust crates while building. PROPERTIES="live" RESTRICT="network-sandbox" # Linux always links libdrm_amdgpu. brotli is macOS-only. Digest verification # links mbedtls; only slot 0 matches the probe, otherwise a static copy is fetched. RDEPEND=" >=app-arch/zstd-1.5.5:= >=dev-cpp/cli11-2.4.2 >=dev-cpp/cpp-httplib-0.26.0 >=dev-cpp/nlohmann_json-3.11.3 >=net-libs/libwebsockets-4.3.3 >=net-misc/curl-8.5.0 net-libs/mbedtls:0= sys-libs/libcap x11-libs/libdrm[video_cards_amdgpu] acct-group/lemonade acct-user/lemonade system-fastflowlm? ( sci-ml/fastflowlm ) system-llamacpp? ( sci-misc/llama-cpp ) system-rocm? ( dev-util/hip ) webui? ( app-misc/jq x11-misc/xdg-utils ) tauri? ( net-libs/libsoup:3.0 net-libs/webkit-gtk:4.1 x11-libs/gtk+:3 ) " DEPEND="${RDEPEND}" BDEPEND=" ${PYTHON_DEPS} >=dev-build/cmake-3.12 app-misc/jq virtual/pkgconfig webui? ( net-libs/nodejs[npm] ) tauri? ( net-libs/nodejs[npm] || ( dev-lang/rust dev-lang/rust-bin ) ) " LEMONADE_MODELS_DIR="/var/lib/lemonade/models" src_configure() { # BUILD_TAURI_APP builds during configure, before CMakeCache exists; build # its standalone target later and install it manually. # Upstream's resource lookup does not support /opt, so install to /usr. # BUILD_TESTING=OFF is upstream's distro-packaging path. local mycmakeargs=( -DCMAKE_INSTALL_PREFIX="/usr" -DBUILD_WEB_APP=$(usex webui ON OFF) -DBUILD_TAURI_APP=OFF -DBUILD_TESTING=OFF ) cmake_src_configure } src_compile() { cmake_src_compile # This standalone target is not part of all. if use tauri; then cmake_src_compile tauri-app fi } src_install() { cmake_src_install # Do not ship the FetchContent dependency's private static archive. find "${ED}" -name 'libwebsockets.a' -delete || die # OpenRC service; upstream already installs the systemd units # (system + user lemond.service, sysusers.d, /etc/default/lemond). newinitd "${FILESDIR}/${PN}.initd" "${PN}" newconfd "${FILESDIR}/${PN}.confd" "${PN}" # Shared model store, group writable for manual launches by members of # the lemonade group; the services already run as the lemonade user. keepdir "${LEMONADE_MODELS_DIR}" fowners -R lemonade:lemonade /var/lib/lemonade fperms 2770 "${LEMONADE_MODELS_DIR}" # systemd drop-ins for both the system and the user unit. # flm (NPU backend) mlocks its buffers; upstream's unit only grants # CAP_SYS_RESOURCE, it does not raise the limit (the OpenRC init does). local -a dropins=( 10-memlock.conf ) printf '[Service]\nLimitMEMLOCK=infinity\n' > "${T}/10-memlock.conf" || die if use system-fastflowlm; then # lemond spawns flm; point it at fastflowlm's shared model store. printf '[Service]\nEnvironment=FLM_MODEL_PATH=/var/lib/fastflowlm\n' \ > "${T}/10-fastflowlm.conf" || die dropins+=( 10-fastflowlm.conf ) fi # Point the services at the system ROCm instead of downloading TheRock. if use system-rocm; then sed -i -e 's|^#\?LEMONADE_ROCM_PATH=.*|LEMONADE_ROCM_PATH="/usr"|' \ "${ED}/etc/conf.d/${PN}" || die printf '[Service]\nEnvironment=ROCM_PATH=/usr\n' \ > "${T}/10-rocm-path.conf" || die dropins+=( 10-rocm-path.conf ) fi local unitdir dropin for unitdir in "$(systemd_get_systemunitdir)" "$(systemd_get_userunitdir)"; do insinto "${unitdir#"${EPREFIX}"}/lemond.service.d" for dropin in "${dropins[@]}"; do doins "${T}/${dropin}" done done # Seed every new config with the shared model store instead of the # per-user HuggingFace cache, and route fetch-by-default backends to # packaged binaries (set both backend and its matching *_bin: "auto" # otherwise ignores the path). Guard each section so jq cannot create a # misplaced key. local -a jqf=( ".models_dir=\"${LEMONADE_MODELS_DIR}\"" ) sections=() if use system-llamacpp; then # Always route through the "vulkan" slot: it is the only one lemond # launches without its own runtime bookkeeping (the rocm slot wants a # backend_versions.json entry and TheRock). Which GPU backend actually # runs is decided by the binary: a ROCm-built llama-server runs ROCm. jqf+=( '.llamacpp.backend="vulkan" | .llamacpp.vulkan_bin="/usr/bin/llama-server"' ) sections+=( llamacpp ) fi if use system-fastflowlm; then # PATH is ignored without prefer_system; pin npu_bin to prevent a download. jqf+=( '.flm.npu_bin="/usr/bin/flm"' ) sections+=( flm ) fi # Patch the config seed (lemond merges /usr/share/lemonade/defaults.json # over its built-in defaults for every new config.json). local def="${ED}/usr/share/lemonade/defaults.json" local s for s in models_dir "${sections[@]}"; do jq -e "has(\"${s}\")" "${def}" >/dev/null \ || die "defaults.json has no '${s}' key; re-audit the pins" done local filter printf -v filter '%s | ' "${jqf[@]}" jq "${filter% | }" "${def}" > "${T}/defaults.json" || die mv "${T}/defaults.json" "${def}" || die if use tauri; then # Upstream's configure-time install rules are unusable; install manually. dobin "${BUILD_DIR}/app/lemonade-app" domenu "${S}/data/lemonade-app.desktop" newicon -s scalable "${S}/src/app/assets/logo.svg" lemonade-app.svg fi } pkg_postinst() { elog "lemond binds 127.0.0.1:13305 by default; expose it beyond localhost" elog "only behind API-key auth (LEMONADE_API_KEY) or a VPN/SSH tunnel." elog "" ewarn "Privacy: at startup lemond sends a UDP presence broadcast on LAN" ewarn "interfaces, even with the loopback bind. Disable it by setting" ewarn "broadcast to false in ~/.cache/lemonade/config.json." elog "" elog "Models are stored in the shared store ${LEMONADE_MODELS_DIR}" elog "(models_dir in every new config.json), owned by the lemonade user." elog "Add users that run lemond by hand to the lemonade group:" elog " usermod -aG lemonade " elog "Per-user state (config.json, downloaded backends) stays under" elog "~/.config/lemonade and ~/.cache/lemonade for manual launches;" elog "the services use /var/lib/lemonade and /var/cache/lemonade." elog "" elog "Quick start:" elog " lemond # start the server" elog " lemonade run # CLI client" elog "OpenRC (runs as the lemonade user, tunables in /etc/conf.d/lemonade):" elog " rc-service lemonade start" elog "systemd (runs as the dedicated 'lemonade' user):" elog " systemctl enable --now lemond # system-wide, or" elog " systemctl --user enable --now lemond # per-user" elog " host/port live in config.json; HF_TOKEN and LEMONADE_API_KEY" elog " go in /etc/default/lemond" elog " the system unit gets LimitMEMLOCK=infinity for the NPU backend; a" elog " --user unit needs the same from /etc/security/limits.d" elog "" if use webui; then elog "Web UI: lemond serves the bundled React app at http://127.0.0.1:13305/" elog "and a 'lemonade-web-app' launcher opens it in your browser." elog "" fi if use tauri; then elog "Desktop app: 'lemonade-app' is a client for a running lemond." elog "" fi if use system-llamacpp; then elog "system-llamacpp: lemond reuses /usr/bin/llama-server (sci-misc/llama-cpp)" elog "through the 'vulkan' routing pinned in the shipped defaults; the GPU" elog "backend that actually runs is the one llama-cpp was built with." elog "An existing config is not touched; set it by hand:" elog " lemonade config set llamacpp.backend=vulkan llamacpp.vulkan_bin=/usr/bin/llama-server" elog "" fi if use system-rocm; then elog "system-rocm: lemond is pointed at /usr (ROCM_PATH) by the OpenRC confd" elog "and a systemd drop-in, so its ROCm backends reuse the system ROCm" elog "instead of downloading AMD's ~3 GB TheRock runtime. For a manual" elog "launch: export ROCM_PATH=/usr" elog "" fi if use system-fastflowlm; then elog "system-fastflowlm: the shipped defaults pin flm.npu_bin=/usr/bin/flm." elog "An existing ~/.cache/lemonade/config.json is not touched; set it with:" elog " lemonade config set flm.npu_bin=/usr/bin/flm" elog "Confirm 'flm validate' passes before lemonade drives the NPU." else ewarn "Without USE=system-fastflowlm, lemond downloads the FastFlowLM (flm)" ewarn "NPU runtime into ~/.cache/lemonade on first NPU use. A bare flm on" ewarn "PATH is not used unless flm.prefer_system is set." fi }