# Copyright 1999-2026 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 # CRATES is deliberately empty: the 490 crates come from a single tarball in # SRC_URI instead. Listing them inline works -- it is what this package did # first, and it built -- but Portage answers it with # # QA Notice: This package uses a very large number of CRATES. Please provide # a crate tarball instead and fetch it via SRC_URI. # # and it puts 490 DIST lines (151 KiB) in the Manifest. Same precedent as # dev-util/codex in this overlay. # # REGENERATING IT ON A BUMP -- the tarball is NOT produced by upstream and no # CI makes it, so a bump that skips this fails at fetch time. From the unpacked # source, and from the workspace MEMBER, never the workspace root (pycargoebuild # refuses the root with "The specified directory is a workspace root"): # # cd crates/ai-memory-cli # name="${PN}-${CRATES_PV}-crates.tar.xz" # pycargoebuild -c --crate-tarball-path "${DISTDIR}/${name}" \ # -d "${DISTDIR}" -o /dev/null . # npx --yes wrangler@latest r2 object put "obentoo-distfiles/${name}" \ # --file="${DISTDIR}/${name}" --content-type=application/x-xz --remote # # The --remote is load-bearing: without it wrangler writes to local dev storage, # prints "Upload complete" and the object never reaches the bucket. Run it from # /home/otaku/Projetos/git/bentoo so the wrangler profile resolves to "bentoo"; # the default profile is a different account entirely. # # Verified for 2.1.0: the member-scoped run covers all 490 external crates the # workspace resolves -- generated list and the previous inline CRATES list were # compared entry by entry, with no difference in either direction. CRATES=" " # Version of the crates tarball to fetch, which is NOT always ${PV}. The 2.1.1 # bump changes nothing in the dependency graph: the whole Cargo.lock diff # against 2.1.0 is the twelve workspace members' own `version =` fields, and # the external set is the same 490 crates, name and version for name and # version -- verified by comparing every `source = "registry+..."` entry in # 2.1.1's lock against the cargo_home/gentoo/-/ directories in the # published 2.1.0 tarball, with no difference in either direction. So the # 2.1.0 artifact is reused verbatim instead of regenerating and re-uploading a # byte-identical 36 MiB file under a new name; both versions then share one # distfile rather than duplicating it. # # BUMP THIS to ${PV} (and run the recipe above) the moment the lock's external # packages change -- a stale tarball still FETCHES, so the failure would land # in src_compile as a missing crate rather than here. CRATES_PV="2.1.0" # Upstream pins channel 1.95 in rust-toolchain.toml; the workspace is # edition 2024 and declares rust-version = "1.95". RUST_MIN_VER="1.95" inherit cargo shell-completion systemd tmpfiles toolchain-funcs DESCRIPTION="Local-first long-term memory MCP server for AI coding agents" HOMEPAGE="https://github.com/akitaonrails/ai-memory" SRC_URI=" https://github.com/akitaonrails/ai-memory/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz https://distfiles.obentoo.org/${PN}-${CRATES_PV}-crates.tar.xz ${CARGO_CRATE_URIS} " # License for the package itself LICENSE="MIT" # Dependent crate licenses LICENSE+=" Apache-2.0 BSD CC0-1.0 CDLA-Permissive-2.0 ISC MIT MPL-2.0 Unicode-3.0 ZLIB " SLOT="0" KEYWORDS="~amd64 ~arm64" # No USE flag for the LLM crate's `local-embeddings` feature, and that is a # measurement rather than an omission. The feature is default-on in # crates/ai-memory-llm (`default = ["local-embeddings"]`), which is what the # official x86_64/aarch64 release binaries ship, and it is pure-Rust CPU BERT # inference: no CUDA, ROCm, SYCL or Metal feature is enabled anywhere in the # graph, so nobody is being handed an accelerator dependency by leaving it on. # # It cannot be turned off from a `-p ai-memory-cli` build either. # crates/ai-memory-cli/Cargo.toml takes the dependency as # `ai-memory-llm.workspace = true` with no feature spec, and cargo has no # command-line way to clear a *dependency's* default features. Adding # `default-features = false` there alone is rejected outright: # # error inheriting `ai-memory-llm` from workspace root manifest's # `workspace.dependencies.ai-memory-llm` # `default-features = false` cannot override workspace's `default-features` # # and even past that the CLI would not compile: config.rs and commands/serve.rs # name `EmbedderChoice::Local`, `LOCAL_MODEL`, `model_present` and `fetch_model` # with no `#[cfg(feature = ...)]` around them, and "local" is the default # embedding provider when none is configured. # # CONDITION TO REVISIT: expose the flag once upstream adds a feature # passthrough in crates/ai-memory-cli (or once those call sites are cfg-gated). # Until then a flag here would either be a no-op or an FTBFS. IUSE="+systemd" # app-misc/ca-certificates because reqwest is built with # `rustls-tls-native-roots`: it reads the PLATFORM trust store rather than a # bundled webpki root set, so an empty store means every provider call fails # with UnknownIssuer. # # The account is NOT created from upstream's sysusers.d drop-in (which this # ebuild deliberately does not install); it comes from acct-user/ai-memory and # acct-group/ai-memory, whose home is the /var/lib/ai-memory the unit declares # as its StateDirectory. # The acct-* pair is in DEPEND as well as RDEPEND, and that is load-bearing # rather than belt-and-braces: src_install runs `fowners root:ai-memory` on # /etc/ai-memory/env, and fowners is a chown against the BUILD host's account # database, which only DEPEND guarantees is populated. With them in RDEPEND # alone the install phase dies with "chown: invalid group: root:ai-memory" on # any machine that does not already have the account. Same shape as # net-misc/apt-cacher-ng and net-misc/asterisk in ::gentoo. DEPEND=" acct-group/ai-memory acct-user/ai-memory " RDEPEND=" ${DEPEND} app-misc/ca-certificates " DOCS=( README.md docs/install.md ) src_prepare() { default # [profile.release] sets strip = "symbols", which hands Portage an # already-stripped binary: that trips the pre-stripped QA check and # makes FEATURES=splitdebug produce empty debug objects. Let Portage # strip. Guarded because a sed that matches nothing exits 0 in silence, # and the fix would then die unnoticed at the next bump. grep -qF 'strip = "symbols"' Cargo.toml \ || die "Cargo.toml no longer sets strip = \"symbols\"; drop this sed" sed -i '/^strip = "symbols"$/d' Cargo.toml || die # crates/ai-memory-web/build.rs copies the VENDORED stylesheet into # OUT_DIR and only reaches the network when TAILWIND_BUILD=1, which this # ebuild never sets. If upstream ever stops vendoring the file, the build # script panics with a message about a network regeneration mode -- which # reads like a sandbox failure and is not one. Fail here instead, where # the cause is stated. [[ -f crates/ai-memory-web/static/tailwind.css ]] \ || die "crates/ai-memory-web/static/tailwind.css is gone; upstream stopped vendoring the stylesheet and the build now needs TAILWIND_BUILD=1 (network)" } src_configure() { # The workspace has eleven members plus an evals harness; only # ai-memory-cli produces the shipped `ai-memory` binary. `-p` lands in # ECARGO_ARGS, so src_compile and src_test both inherit it. cargo_src_configure -p ai-memory-cli } src_test() { # Upstream's own PKGBUILD points HOME at a scratch directory for the # suite; several tests write under $HOME and the Portage HOME is # read-only in some setups. local -x HOME="${T}/test-home" mkdir -p "${HOME}" || die # --lib --tests, and each half of that is deliberate. # # Upstream's PKGBUILD runs `--bin ai-memory`, which looks like it tests # the shipped binary and does not: crates/ai-memory-cli/Cargo.toml sets # `test = false` on that target because main.rs is a shim with no tests # of its own. cargo still builds a harness when the target is named # explicitly, so the phase passes -- having run zero tests. A test phase # that cannot fail is worse than none, because it reads as coverage. # # --lib --tests runs what actually exists: 846 unit tests in the library # plus 110 in tests/suite, all passing here against 2.1.0. # # Doctests are excluded because one of them is not Rust. The example in # commands/setup_agent.rs is a `docker run ...` shell snippet in a plain # ``` block, so rustdoc compiles it and dies with "unknown start of # token: \". That is an upstream defect in a doc comment, not a fault in # this package, and it fails identically on a plain `cargo test` outside # Portage. Revisit when upstream tags the block `text` or `ignore`. cargo_src_test --lib --tests } src_install() { dobin target/release/${PN} einstalldocs # Agent hook scripts: shell plus one subdirectory per vendor # (claude-code, codex, cursor, ...). Copied with `cp -a` rather than # `doins -r` on purpose: the agents EXEC these files, and 80 of the 162 # ship 0755 upstream while doins forces 0644 on everything it touches. # A 0644 hook is a hook that silently never runs, and nothing in the # install would have looked wrong. Same call upstream's PKGBUILD makes. dodir /usr/share/${PN} cp -a hooks "${ED}"/usr/share/${PN}/ || die # --- configuration ----------------------------------------------------- insinto /etc/${PN} newins crates/ai-memory-cli/templates/config.default.toml config.toml # The env file is meant to hold API keys (ANTHROPIC_API_KEY, ...), so it # is readable by the service account and by nobody else. 0640 root:ai-memory # matches what upstream ships (0640) while making the group the one that # actually needs to read it. newins packaging/env/${PN}.env env fowners root:${PN} /etc/${PN}/env fperms 0640 /etc/${PN}/env # --- state directory --------------------------------------------------- # StateDirectory=/StateDirectoryMode= only exist inside systemd. The # tmpfiles entry is what creates /var/lib/ai-memory 0750 ai-memory:ai-memory # on a non-systemd host too (opentmpfiles/systemd-tmpfiles both read it), # and the OpenRC service repeats it in start_pre so the daemon does not # depend on tmpfiles having run. newtmpfiles packaging/tmpfiles/${PN}.conf ${PN}.conf # --- service files ----------------------------------------------------- # One service per scope, mirroring the two units upstream ships. The # OpenRC scripts are installed UNCONDITIONALLY: they cost a systemd user # nothing, and gating them would leave someone without systemd no way to # run the daemon at all. Only the units are behind USE=systemd. newinitd "${FILESDIR}"/${PN}.initd ${PN} newconfd "${FILESDIR}"/${PN}.confd ${PN} # User scope. newinitd has no user-scope variant, so the script goes in # as a plain executable, following sys-apps/xdg-desktop-portal and # sci-ml/lemonade-bin. exeinto /etc/user/init.d newexe "${FILESDIR}"/${PN}-user.initd ${PN} if use systemd; then systemd_dounit packaging/systemd/${PN}.service # Upstream's file is ai-memory-user.service; the user manager looks # for ai-memory.service in the user directory, hence the rename. systemd_newuserunit packaging/systemd/${PN}-user.service ${PN}.service fi # packaging/sysusers/ai-memory.conf is deliberately NOT installed: the # account comes from acct-user/ai-memory + acct-group/ai-memory. # --- shell completions ------------------------------------------------- # Generated from the binary just built. `ai-memory completions ` # renders the same derived clap Command the parser uses, so the scripts # cannot drift from the real CLI surface; a checked-in script could. # The subcommand takes no config and touches no data directory, so it is # safe to run here -- but it is still running a freshly built target # binary, which a cross build cannot do. if ! tc-is-cross-compiler; then local shell for shell in bash zsh fish; do target/release/${PN} completions ${shell} > "${T}"/${PN}.${shell} \ || die "generating ${shell} completions failed" done newbashcomp "${T}"/${PN}.bash ${PN} newzshcomp "${T}"/${PN}.zsh _${PN} newfishcomp "${T}"/${PN}.fish ${PN}.fish else ewarn "Cross-compiling: shell completions were not generated." fi } pkg_postinst() { tmpfiles_process ${PN}.conf elog "Start the system-wide server with one of:" elog " rc-service ${PN} start" elog " systemctl enable --now ${PN}.service" elog elog "Or run it in your own session (data under ~/.local/share/${PN}):" elog " rc-service --user ${PN} start" elog " systemctl --user enable --now ${PN}.service" elog elog "API keys and other secrets belong in /etc/${PN}/env (0640" elog "root:${PN}) for the system service, or ~/.config/${PN}/env for the" elog "user one. Server settings live in /etc/${PN}/config.toml." elog elog "Agent hook scripts are installed under /usr/share/${PN}/hooks." }