Directory layout and ownership contract

Updated Oct 19, 2026

The appliance provisions a private on-disk tree owned by the repovec service account. repovec-provision.service creates and enforces these directories at target start, and both daemons verify them again at startup:

Path Owner Mode
/var/lib/repovec repovec:repovec 0700
/var/lib/repovec/git-mirrors repovec:repovec 0700
/var/lib/repovec/worktrees repovec:repovec 0700
/var/lib/repovec/.grepai repovec:repovec 0700
/var/lib/repovec/qdrant-storage root:root 0700
/etc/repovec root:repovec 0750

The data tree holds private repository mirrors, worktrees, and derived indexes. These directories are intentionally not world- or group-readable: only the repovec service account (via owner access), root, and — for qdrant-storage — the rootful Podman Qdrant container may touch them. qdrant-storage stays root:root because Qdrant runs as uid 0 under rootful Podman and is the only filesystem accessor; the daemons reach Qdrant over loopback gRPC only.

/etc/repovec holds secrets. It is 0750 root:repovec: root owns the directory so the repovec user can read but cannot change directory permissions or replace secret files; the files themselves are 0400 repovec:repovec. The directory is provisioned by the repovec-qdrant-api-key helper, not by the tmpfiles.d asset — /etc/repovec has exactly one provisioning authority.

Operators must not loosen these modes or change the ownership or group. A loosened mode or a re-owned directory is a confidentiality violation, and the daemons refuse to start when the live ownership, group, or confidentiality check on any of these directories fails. Restore the documented mode and owner/group (or recreate the directory) and restart the target; see the startup logging section below for the diagnostic shape.

The repovec system user itself is declared by the sysusers asset with home /var/lib/repovec and shell /usr/sbin/nologin (a locked, non-interactive shell). No numeric uid/gid is pinned; systemd-sysusers allocates the account.

Startup validation logging

Both repovecd and repovec-mcpd validate their checked-in systemd unit contracts, then the live directory-layout contract, then Qdrant liveness at startup before starting any async work. The outcomes are observable in the systemd journal:

Success — a TRACE-level event followed by a DEBUG-level confirmation:

TRACE systemd unit contract validated
DEBUG systemd unit contract validated at daemon startup

Failure — an ERROR-level event with structured fields identifying the violating unit and the nature of the failure, followed by a non-zero process exit:

ERROR systemd unit contract violation — aborting startup
  unit=repovecd.service
  error=repovecd.service is missing [Service]

The unit field contains the logical systemd unit name (e.g. repovecd.service) and the error field contains the human-readable description of the contract violation. The Qdrant Quadlet validator emits INFO, WARN, and ERROR events; use a target-scoped filter such as RUST_LOG=repovec_core::qdrant_quadlet=info to inspect those validation events. The systemd unit validator emits TRACE, DEBUG, and ERROR events; use RUST_LOG=repovec_core::systemd_units=trace when startup ordering or unit-contract diagnostics require the lower-level trace.

Directory-layout failure — an ERROR-level event with the violating path, followed by a non-zero process exit:

ERROR live directory layout violation - aborting startup
  path=/var/lib/repovec/worktrees
  error=/var/lib/repovec/worktrees must be owned by repovec (uid 998): uid 0

The path field names the directory whose ownership, group, mode, type, or presence violates the contract; the error field states the expected value and the account or mode that is actually present. The live pre-flight runs after the static unit-contract validation and before the Qdrant liveness wait, so a misprovisioned host is rejected before any daemon-side network work begins.

Concrete grepai indexer instances also start after and require both Qdrant and repovecd.