# Copyright 1999-2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=8
inherit cmake multilib
# The web app (the browser UI served by lemond) is bundled with webpack, and
# upstream drives that through `npm ci` at build time. That needs the network,
# so the dependency tree is shipped as a separate distfile generated from this
# release's src/web-app/package-lock.json with:
# npm ci --ignore-scripts --no-audit --no-fund
# tar caf lemonade-webapp-node_modules-${PV}.tar.xz node_modules
WEBAPP_NODE_MODULES="${PN}-webapp-node_modules-${PV}.tar.xz"
DESCRIPTION="Local LLM server with GPU and NPU acceleration"
HOMEPAGE="https://lemonade-server.ai/ https://github.com/lemonade-sdk/lemonade"
SRC_URI="
https://github.com/lemonade-sdk/lemonade/archive/refs/tags/v${PV}.tar.gz
-> ${P}.tar.gz
webapp? ( https://distfiles.obentoo.org/${WEBAPP_NODE_MODULES} )
"
# The webapp bundle links the JavaScript dependency tree into
# renderer.bundle.js: React and KaTeX (MIT), highlight.js (BSD-3-Clause) and
# a handful of Apache-2.0 pieces, as listed in the .LICENSE.txt webpack emits
# next to the bundle.
LICENSE="Apache-2.0 webapp? ( BSD MIT )"
SLOT="0"
KEYWORDS="~amd64 ~arm64"
IUSE="+caps +systemd test +webapp"
RESTRICT="!test? ( test )"
# Everything upstream cannot find on the system is pulled in with FetchContent
# at configure time, which a sandboxed build cannot do, so all of these are
# hard dependencies rather than optional ones. libdrm is not optional either:
# the Linux branch links drm_amdgpu unconditionally to read AMD GPU memory.
COMMON_DEPEND="
>=app-arch/zstd-1.5.5:=
>=dev-cpp/cpp-httplib-0.26.0:=
>=net-libs/libwebsockets-4.3.3:=
>=net-misc/curl-8.5.0:=
net-libs/mbedtls:3=
x11-libs/libdrm[video_cards_amdgpu]
caps? ( sys-libs/libcap )
systemd? ( sys-apps/systemd:= )
"
# cli11 and nlohmann_json are header-only.
DEPEND="
${COMMON_DEPEND}
>=dev-cpp/cli11-2.4.2
>=dev-cpp/nlohmann_json-3.11.3
"
RDEPEND="
${COMMON_DEPEND}
acct-group/lemonade
acct-user/lemonade
app-arch/unzip
!!sci-ml/lemonade-bin
"
BDEPEND="
virtual/pkgconfig
webapp? ( net-libs/nodejs[npm] )
"
PATCHES=(
"${FILESDIR}"/${P}-webapp-prebuilt-node-modules.patch
)
src_unpack() {
unpack "${P}.tar.gz"
use webapp && unpack "${WEBAPP_NODE_MODULES}"
}
src_prepare() {
cmake_src_prepare
# libsystemd (journald log sink) and libcap (capability inheritance for the
# backend processes lemond spawns) are probed with bare pkg_check_modules()
# calls that have no option() to turn them off, so the detection itself has
# to be neutralised or the build links them behind the USE flag's back.
if ! use systemd; then
sed -i -e 's/pkg_check_modules(SYSTEMD QUIET libsystemd)/set(SYSTEMD_FOUND FALSE)/' \
CMakeLists.txt || die
fi
if ! use caps; then
sed -i -e 's/pkg_check_modules(LIBCAP QUIET libcap)/set(LIBCAP_FOUND FALSE)/' \
CMakeLists.txt || die
fi
# mbedtls (HTTPS and digest verification in the CLI) is looked up under its
# upstream names, but Gentoo suffixes the slot onto everything it installs:
# mbedtls-3.pc / libmbedtls-3.so, headers under /usr/include/mbedtls3. The
# probe therefore misses it and the fallback find_library() misses it too,
# so point pkg-config at the slotted module names instead. Keep this in
# sync with the mbedtls slot in COMMON_DEPEND.
sed -i -e 's/pkg_check_modules(MBEDTLS QUIET mbedtls mbedx509 mbedcrypto)/pkg_check_modules(MBEDTLS QUIET mbedtls-3 mbedx509-3 mbedcrypto-3)/' \
src/cpp/cli/CMakeLists.txt || die
# dev-cpp/cpp-httplib ships a CMake package config (httplib::httplib) but no
# pkg-config file, while upstream only probes httplib through
# pkg_search_module(). With no .pc the probe fails, USE_SYSTEM_HTTPLIB flips
# to OFF and the build tries to FetchContent httplib, which the sandbox
# cannot reach. Synthesize the .pc the probe looks for (see the
# PKG_CONFIG_PATH export in src_configure). No feature macros in Cflags on
# purpose: upstream's system branch skips them too, because they would turn
# on header features needing link deps the interface target does not carry.
local httplib_ver
httplib_ver=$(sed -nE 's/.*CPPHTTPLIB_VERSION[[:space:]]+"([0-9.]+)".*/\1/p' \
"${ESYSROOT}"/usr/include/httplib.h) || die
[[ -n ${httplib_ver} ]] || die "could not read cpp-httplib version"
mkdir -p "${T}"/pkgconfig || die
cat > "${T}"/pkgconfig/httplib.pc <<-EOF || die
prefix=${ESYSROOT}/usr
libdir=\${prefix}/$(get_libdir)
includedir=\${prefix}/include
Name: httplib
Description: cpp-httplib (Gentoo, synthesized by the ebuild)
Version: ${httplib_ver}
Cflags: -I\${includedir}
Libs: -L\${libdir} -lcpp-httplib
EOF
}
src_configure() {
# Let pkg_search_module() find the httplib.pc synthesized in src_prepare.
local -x PKG_CONFIG_PATH="${T}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
local mycmakeargs=(
-DBUILD_TESTING=$(usex test)
-DBUILD_WEB_APP=$(usex webapp)
# The Tauri desktop shell and the AppIndicator tray are separate
# products that would each need their own npm/cargo dependency set.
-DBUILD_TAURI_APP=OFF
-DREQUIRE_LINUX_TRAY=OFF
# The web app bundles its own KaTeX from the node_modules distfile;
# the system-modules path expects Debian's /usr/share/nodejs layout.
-DUSE_SYSTEM_NODEJS_MODULES=OFF
# Turn any dependency upstream failed to find into a configure-time
# error instead of a silent download attempt.
-DFETCHCONTENT_FULLY_DISCONNECTED=ON
)
cmake_src_configure
}
src_compile() {
if use webapp; then
# Consumed by the patch in src/web-app/BuildWebApp.cmake.
local -x LEMONADE_PREBUILT_NODE_MODULES="${WORKDIR}/node_modules"
# `npm run build` only spawns the bundled webpack, but keep npm from
# touching the network or ${HOME} regardless.
local -x npm_config_cache="${T}/npm-cache"
local -x npm_config_offline=true
local -x npm_config_audit=false
local -x npm_config_fund=false
local -x npm_config_update_notifier=false
fi
cmake_src_compile
}
src_install() {
cmake_src_install
# The service account comes from acct-user/lemonade and
# acct-group/lemonade, so the sysusers.d drop-in only duplicates them.
rm -r "${ED}"/usr/lib/sysusers.d || die
newinitd "${FILESDIR}"/lemond.initd lemond
newconfd "${FILESDIR}"/lemond.confd lemond
# /var/lib/lemonade is created at first start, not here: the lemonade
# service account does not exist on the build host, so fowners by name would
# fail. systemd's StateDirectory= and the initd's checkpath both create it
# owned by lemonade:lemonade on startup.
}
pkg_postinst() {
elog "lemond stores models and its config.json under /var/lib/lemonade."
elog "Start it with one of:"
elog " rc-service lemond start"
elog " systemctl start lemond.service"
elog
elog "Inference backends (llama.cpp, whisper.cpp, vLLM, ONNX Runtime,"
elog "Ryzen AI/NPU, ...) are not built here: lemond downloads and manages"
elog "them at runtime, which is why app-arch/unzip is a runtime dependency."
elog
elog "Set LEMONADE_API_KEY and HF_TOKEN in /etc/lemonade/conf.d/ for the"
elog "systemd unit, or in /etc/conf.d/lemond for the OpenRC service."
}