# Copyright 1999-2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=8
# HYBRID PACKAGING -- DO NOT "SIMPLIFY" THIS ON A VERSION BUMP.
#
# 1) Why the binaries come from the "embeddable" tarball and NOT from the .deb:
# Upstream's .deb (and the Fedora RPMs) link against Debian-13 sonames that
# do not exist on Gentoo:
# libcpp-httplib.so.0.41 -- Gentoo builds cpp-httplib as .so.0.50.1
# libwebsockets.so.19 -- Gentoo ships libwebsockets.so.21
# libmbedcrypto.so.16 -- Gentoo installs this as libmbedcrypto-3.so.16
# The "embeddable" tarball links those statically and only needs libraries
# Gentoo provides under the very same soname (verified with readelf -d):
# libz.so.1 libzstd.so.1 libssl.so.3 libcrypto.so.3 libdrm_amdgpu.so.1
# libstdc++.so.6 libm.so.6 libgcc_s.so.1 libc.so.6
#
# 2) Why the .deb is still fetched (unconditionally, amd64 flavour only):
# Only *arch-independent data* is taken out of it -- never an ELF object.
# The embeddable tarball omits the web UI, the JSON schemas, the examples,
# the man pages, the systemd units and architecture_defaults.json. Those
# files carry no machine code, so the amd64 .deb serves every arch and can
# stay outside the SRC_URI USE-conditionals.
# The six resources/*.json present in BOTH archives were verified
# byte-identical for 11.5.0 (backend_versions, bench_scenarios, defaults,
# server_models, toolDefinitions, vllm_model_config), so the .deb copy is
# used for the whole resources/ tree -- it is a strict superset of the
# tarball's. Re-check that with cmp before trusting it on the next bump.
#
# 3) Why the ELF payload lives in /usr/$(get_libdir)/lemonade-server and NOT
# directly in /usr/bin:
# The embeddable build resolves its data files as <dirname /proc/self/exe>/
# resources/ ONLY. Unlike the .deb build it has no /usr/share/lemonade-server
# fallback compiled in -- grep both binaries: that string is present in the
# .deb one and absent from the embeddable one. Installed straight into
# /usr/bin it dies at startup with
# Error: Failed to open /usr/bin/resources/defaults.json
# So the real executables sit next to a "resources" symlink pointing at the
# arch-independent tree under /usr/share/lemonade-server, and /usr/bin only
# carries symlinks (/proc/self/exe resolves through them to the real path,
# so the lookup still lands in the right directory).
inherit multilib systemd unpacker
DESCRIPTION="Local LLM server with GPU and NPU acceleration (prebuilt binaries)"
HOMEPAGE="https://lemonade-server.ai/
https://github.com/lemonade-sdk/lemonade"
MY_EMB="lemonade-embeddable-${PV}-ubuntu"
LEMONADE_URI="https://github.com/lemonade-sdk/lemonade/releases/download/v${PV}"
SRC_URI="
amd64? ( ${LEMONADE_URI}/${MY_EMB}-x64.tar.gz -> ${P}-amd64.tar.gz )
arm64? ( ${LEMONADE_URI}/${MY_EMB}-arm64.tar.gz -> ${P}-arm64.tar.gz )
${LEMONADE_URI}/lemonade-server_${PV}-debian13_amd64.deb -> ${P}-data.deb
"
S="${WORKDIR}"
# resources/web-app/renderer.bundle.js is the webpack bundle upstream ships
# prebuilt: React and KaTeX (MIT), highlight.js (BSD-3-Clause) and some
# Apache-2.0 pieces, per the .LICENSE.txt installed next to it.
LICENSE="Apache-2.0 BSD MIT"
SLOT="0"
KEYWORDS="-* ~amd64 ~arm64"
IUSE="+systemd"
RESTRICT="bindist mirror strip"
RDEPEND="
!!sci-ml/lemonade
acct-group/lemonade
acct-user/lemonade
app-arch/unzip
app-arch/zstd:=
dev-libs/openssl:0/3
sys-libs/zlib:=
x11-libs/libdrm[video_cards_amdgpu]
"
# ar(1) comes from binutils (@system); xz-utils decompresses the deb data.tar.xz.
BDEPEND="app-arch/xz-utils"
QA_PREBUILT="*"
# src_unpack comes from unpacker.eclass: it routes the .tar.gz through the
# normal unpacker and the .deb through unpack_deb (ar + data.tar.xz), so no
# override is needed here.
src_install() {
local libdir="/usr/$(get_libdir)/lemonade-server"
local datadir="/usr/share/lemonade-server"
local emb
if use amd64; then
emb="${WORKDIR}/${MY_EMB}-x64"
elif use arm64; then
emb="${WORKDIR}/${MY_EMB}-arm64"
else
die "unsupported ARCH=${ARCH}: no embeddable tarball for it"
fi
# --- ELF payload (arch dependent, from the embeddable tarball) ---------
exeinto "${libdir}"
doexe "${emb}/lemonade"
doexe "${emb}/lemond"
# --- arch-independent resources (from the .deb) ------------------------
insinto "${datadir}"
doins -r usr/share/lemonade-server/resources
# The binaries look for "<exedir>/resources"; point that at the shared
# tree so the data is installed FHS-correctly but still found at runtime.
dosym -r "${datadir}/resources" "${libdir}/resources"
# User-facing commands.
dosym -r "${libdir}/lemonade" /usr/bin/lemonade
dosym -r "${libdir}/lemond" /usr/bin/lemond
# lemond additionally probes this absolute path as a system-wide defaults
# file (hardcoded string "/usr/share/lemonade/defaults.json"). It is
# byte-identical to resources/defaults.json; ship it so it resolves.
insinto /usr/share/lemonade
doins usr/share/lemonade/defaults.json
# Examples keep upstream's layout: they are referenced from the docs and
# some are meant to be run, so no dodoc compression.
insinto "${datadir}"
doins -r usr/share/lemonade-server/examples
fperms +x "${datadir}/examples/migrate-to-systemd.sh"
# --- man pages ---------------------------------------------------------
# The .deb ships them pre-gzipped; portage does its own compression and
# warns about compressed files in docompress-ed directories, so unpack
# them first.
gunzip usr/share/man/man1/lemonade.1.gz usr/share/man/man1/lemond.1.gz || die
doman usr/share/man/man1/lemonade.1
doman usr/share/man/man1/lemond.1
# --- systemd units -----------------------------------------------------
# USE=systemd gates only the unit files: upstream ships no OpenRC service
# and the units rely on systemd-only directives (StateDirectory,
# AmbientCapabilities=CAP_SYS_RESOURCE), so they are dead weight elsewhere.
# acct-user/acct-group stay unconditional -- the daemon is meant to run
# under a dedicated account however it is started.
if use systemd; then
systemd_dounit usr/lib/systemd/system/lemond.service
systemd_douserunit usr/lib/systemd/user/lemond.service
fi
# upstream's sysusers.d drop-in is deliberately not installed: the account
# comes from acct-user/lemonade and acct-group/lemonade, same as in
# sci-ml/lemonade.
# --- configuration -----------------------------------------------------
# Read by the unit via EnvironmentFile=-/etc/lemonade/conf.d/*.conf
insinto /etc/lemonade/conf.d
doins etc/lemonade/conf.d/zz-secrets.conf
}
pkg_postinst() {
elog "Start the system-wide server with:"
elog " systemctl enable --now lemond.service"
elog
elog "Or run it as your own user (models land in ~/.cache/lemonade):"
elog " systemctl --user enable --now lemond.service"
elog
elog "The web UI is served at http://localhost:13305/ once lemond is up."
elog "API keys and HF_TOKEN belong in /etc/lemonade/conf.d/zz-secrets.conf."
elog
elog "Inference backends (llama.cpp, ROCm, vLLM, ...) are downloaded by"
elog "lemond at runtime into its cache directory; they are not packaged."
}