# Copyright 1999-2026 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 inherit go-env go-module # Three distfiles below are generated by bentoo and hosted on # distfiles.obentoo.org. They carry three independent versions because they # have three different triggers -- pinning them all to ${PV} would mean # republishing 45 MB of unchanged node_modules on every LocalAI release. # # 1. ${P}-vendor.tar.xz -- REGENERATE ON EVERY BUMP (go.mod moves with the # release). pkg/grpc/proto is generated rather than shipped, and # backend/go/* import it, so `go mod vendor` FAILS until protoc has run: # # tar xzf LocalAI-v${PV}-source.tar.gz -C src && cd src # mkdir -p pkg/grpc/proto # protoc --experimental_allow_proto3_optional -Ibackend/ \ # --go_out=pkg/grpc/proto/ --go_opt=paths=source_relative \ # --go-grpc_out=pkg/grpc/proto/ --go-grpc_opt=paths=source_relative \ # backend/backend.proto # go mod vendor # tar caf ${P}-vendor.tar.xz vendor # # Do NOT reach for `go get -tool` to pull the protoc plugins into the # module graph: it downgrades grpc 1.80.0 -> 1.69.0-dev, protobuf # 1.36.11 -> 1.34.2 and libp2p 0.48.0 -> 0.36.5, and drops cogito and nib # outright. The tree would then be built against dependencies upstream # never tested. # # 2. ${PN}-react-ui-node_modules-${NODE_MODULES_PV}.tar.xz -- REGENERATE WHEN # core/http/react-ui/package-lock.json CHANGES, not every bump: # # cd core/http/react-ui # npm ci --ignore-scripts # --ignore-scripts: @playwright/test's # # postinstall would fetch browsers # # vite resolves its native bits through per-platform optional deps, so # # a tree built here is x86_64-only. Overlay the arm64 ones so one # # distfile serves both KEYWORDS (costs 12 MB): # npm ci --ignore-scripts --cpu=arm64 --os=linux # in a scratch dir # cp -a scratch/node_modules/{lightningcss-linux-arm64-*} node_modules/ # cp -a scratch/node_modules/@rolldown/binding-linux-arm64-* \ # node_modules/@rolldown/ # tar caf ${PN}-react-ui-node_modules-${PV}.tar.xz node_modules # # Upstream's Makefile builds this with `npm install`; `npm ci` is used # instead so the lockfile is honoured rather than re-resolved. # # 3. protoc-gen-go-grpc-${PGGG_PV}-vendor.tar.xz -- REGENERATE WHEN UPSTREAM # REPINS THE PLUGIN (Makefile, install-go-tools). Only the gRPC generator # is bundled; protoc-gen-go comes from LocalAI's own vendor tree instead. # See src_compile. # # mkdir protoc-gen-go-grpc && cd protoc-gen-go-grpc # go mod init bentoo.local/protoc-gen-go-grpc-bundle # go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${PGGG_PV} # go mod edit -tool=google.golang.org/grpc/cmd/protoc-gen-go-grpc # go mod tidy && go mod vendor && cd .. # tar caf protoc-gen-go-grpc-${PGGG_PV}-vendor.tar.xz protoc-gen-go-grpc # Upstream pins protoc-gen-go-grpc by commit SHA # (1958fcbe2ca8bd93af633f11e97d44e567e945af); that SHA is release v1.4.0. PGGG_PV="1.4.0" # Bump only when core/http/react-ui/package-lock.json actually changes. NODE_MODULES_PV="4.9.0" MY_P="LocalAI-v${PV}" DESCRIPTION="Self-hosted OpenAI-compatible inference API with a model gallery" HOMEPAGE="https://localai.io/ https://github.com/mudler/LocalAI" SRC_URI=" https://github.com/mudler/LocalAI/releases/download/v${PV}/${MY_P}-source.tar.gz https://distfiles.obentoo.org/${P}-vendor.tar.xz https://distfiles.obentoo.org/${PN}-react-ui-node_modules-${NODE_MODULES_PV}.tar.xz https://distfiles.obentoo.org/protoc-gen-go-grpc-${PGGG_PV}-vendor.tar.xz " # The curated release tarball has NO top-level directory: it extracts straight # into backend/ cmd/ core/ ... Getting this wrong produces a confusing # "file not found" much later, in src_compile. S="${WORKDIR}" # MIT is LocalAI's own. The rest are the licences actually present in the # vendored tree: Apache-2.0 (107 modules), MIT (151), BSD-3 (12), MPL-2.0 (6), # ISC (2), BSD-2 (1). LICENSE="MIT Apache-2.0 BSD BSD-2 ISC MPL-2.0" SLOT="0" KEYWORDS="~amd64 ~arm64" # No acceleration USE flags, and that is deliberate rather than an omission: # LocalAI resolves its inference backends as OCI artifacts at RUNTIME, so no # backend is ever compiled in and a CPU-only install is the default. The # acceleration matrix is optional by construction. IUSE is therefore not set # at all -- an empty IUSE="" is a QA error, and the absence is the statement. # The build reaches no network at all -- verified by building inside a network # namespace with every interface down. Three upstream fetches are removed: # the prebuilt protoc download (dev-libs/protobuf instead), install-go-tools' # two `go install ...@ver` (see src_compile), and the react-ui `npm install` # (the node_modules distfile). Hence no RESTRICT="network-sandbox". # The test suite does download models, so it stays restricted. PROPERTIES="test_network" RESTRICT="test strip" # No dev-go/protobuf-go here on purpose -- see src_compile: protoc-gen-go is # built from LocalAI's own vendor tree so that the generator and the protobuf # runtime are the same version. # pkgcheck reports NonsolvableDepsInDev for the x32 dev profile # (default/linux/amd64/23.0/x32) against the nodejs dependency. That is not a # defect here and cannot be fixed here: net-libs/nodejs carries # "~amd64 ~arm ~arm64 ~loong ~ppc64 ~riscv ~x86 ~x64-macos" and no x32 keyword # at all, so every package that needs npm reports the same. Narrowing KEYWORDS # to silence it would drop arches where this builds fine. BDEPEND=" >=dev-lang/go-1.26.0 dev-libs/protobuf >=net-libs/nodejs-24:*[npm] " # The service account only. Nothing else is linked in: CGO_ENABLED=0 makes the # binary static, and the inference backends are OCI artifacts fetched at # runtime rather than libraries resolved at build time. RDEPEND=" acct-group/localai acct-user/localai " # NOTE: src_unpack is deliberately NOT defined. go-module.eclass exports it, # and go-module_src_unpack ends by calling go-env_set_compile_environment; # a local `src_unpack() { default; }` would override the eclass and drop that # call silently. The eclass's own version does the right thing here: it # unpacks everything and then skips `ego mod verify` because ${S}/vendor # exists. The three bentoo distfiles were packed with distinct top-level # entries (vendor/, node_modules/, protoc-gen-go-grpc/) precisely so that # none collides with the source tarball's own go.mod, go.sum or vendor/ when # they all land in ${WORKDIR}. src_prepare() { # vite resolves dependencies relative to the project directory, so the # staged tree has to sit next to package.json rather than at ${WORKDIR}. mv "${WORKDIR}"/node_modules core/http/react-ui/ || die # core/config/inference_defaults.json ships in the tarball, so the # generator that would fetch it from unsloth is dead weight -- but a # `go generate` would still run it and reach the network. Drop it. # The grep guard matters: sed exits 0 when it matches nothing, so without # it an upstream rename would silently restore the fetch. grep -q '^//go:generate go run \./gen_inference_defaults/' \ core/config/inference_defaults.go \ || die "the go:generate anchor moved; recheck core/config/inference_defaults.go" sed -i '/^\/\/go:generate/d' core/config/inference_defaults.go || die # .gitmodules names docs/themes/hugo-theme-relearn and # backend/rust/kokoros/sources/Kokoros. The curated tarball ships both as # EMPTY directories, so there is nothing to strip and nothing that can # reach the network -- asserted rather than assumed, because a future # tarball that did embed them would change the licence set. local sub for sub in docs/themes/hugo-theme-relearn backend/rust/kokoros/sources/Kokoros; do if [[ -n $(find "${sub}" -mindepth 1 -print -quit 2>/dev/null) ]]; then die "${sub} is no longer empty; re-check the submodule handling" fi done default } src_compile() { # protoc resolves its code generators through PATH, so both must exist as # executables before it runs. # # protoc-gen-go is built from LocalAI's OWN vendor tree, which beats both # alternatives: it is v1.36.11, the very version of the protobuf runtime # the binary links against. Upstream's Makefile pins the generator at # v1.34.2 and dev-go/protobuf-go follows ::gentoo; either can drift AHEAD # of the vendored runtime, and generated code then demands a runtime newer # than the one vendored -- a failure that reads like an upstream bug. # Asserted rather than assumed: `go mod vendor` includes this package # today, and a bump where it stops being included must fail here, loudly. [[ -d vendor/google.golang.org/protobuf/cmd/protoc-gen-go ]] || die "protoc-gen-go left the vendor tree; regenerate the vendor distfile or move the plugin into the bundle" mkdir -p "${T}"/bin || die ego build -o "${T}"/bin/protoc-gen-go \ google.golang.org/protobuf/cmd/protoc-gen-go # protoc-gen-go-grpc, by contrast, has no Gentoo package AND is not # reachable from LocalAI's module graph -- cmd/protoc-gen-go-grpc is a # separate module, so `go mod vendor` never brings it -- which is why it # ships as its own self-contained vendored bundle. pushd protoc-gen-go-grpc > /dev/null || die GOFLAGS="-mod=vendor" ego build -o "${T}"/bin/protoc-gen-go-grpc \ google.golang.org/grpc/cmd/protoc-gen-go-grpc popd > /dev/null || die # pkg/grpc/proto is generated, never shipped; backend/go/* import it. mkdir -p pkg/grpc/proto || die PATH="${T}/bin:${PATH}" protoc --experimental_allow_proto3_optional \ -Ibackend/ \ --go_out=pkg/grpc/proto/ --go_opt=paths=source_relative \ --go-grpc_out=pkg/grpc/proto/ --go-grpc_opt=paths=source_relative \ backend/backend.proto || die "protoc code generation failed" # core/http/app.go carries //go:embed react-ui/dist/*, so the UI is # mandatory: without dist/ the Go build fails outright rather than # producing a UI-less binary. pushd core/http/react-ui > /dev/null || die npm run build || die "react-ui vite build failed" popd > /dev/null || die # CGO_ENABLED=0 with an empty BUILD_TYPE is the CPU-only configuration: # no accelerator is linked in, matching the runtime-OCI backend model. # -trimpath keeps ${WORKDIR} out of the binary. Version and Commit are # normally filled from `git describe`/`git rev-parse`, neither of which # can work from a release tarball. local -x CGO_ENABLED=0 ego build -trimpath \ -ldflags "-X github.com/mudler/LocalAI/internal.Version=v${PV} -X github.com/mudler/LocalAI/internal.Commit=v${PV}" \ -o local-ai ./cmd/local-ai } src_install() { dobin local-ai einstalldocs # Upstream ships no service files of any kind, so the project rule about # mirroring a systemd unit with an OpenRC script does not apply -- there is # no unit to mirror. This installs the OpenRC pair anyway, because local-ai # IS a daemon and without it the only way to run one is by hand, as root, # in whatever directory happens to be current. newinitd "${FILESDIR}"/local-ai.initd local-ai newconfd "${FILESDIR}"/local-ai.confd local-ai # 0640 root:root, not the 0644 newconfd leaves behind: this file is where # LOCALAI_API_KEY, the OIDC client secret and HF_TOKEN belong. root:root # rather than root:localai is deliberate and sufficient -- OpenRC sources # conf.d as root and only then drops to the localai user, so the daemon # never reads the file, and naming the group here would need the account to # exist on the BUILD host, which it need not. fperms 0640 /etc/conf.d/local-ai # Created by the init script at first start rather than here: fowners by # name would need the localai account on the build host. keepdir /var/lib/localai } pkg_postinst() { elog "Start the server with:" elog " rc-service local-ai start" elog elog "It binds 127.0.0.1:8080 by default. That is not just hardening:" elog "local-ai's own default is the wildcard \":8080\", and it REFUSES to" elog "start on a wildcard or public address unless" elog "--allow-insecure-public-bind is passed. Configure authentication" elog "(LOCALAI_API_KEY or OIDC) in /etc/conf.d/local-ai before widening" elog "LOCALAI_ADDRESS." elog elog "Models, backends and generated content live under /var/lib/localai." elog "Inference backends are NOT built here: local-ai downloads them as OCI" elog "artifacts at runtime, picking one that matches the hardware it finds," elog "so the server needs outbound network the first time a model is used" elog "and a CPU-only machine needs nothing extra." }