pub struct Config {Show 19 fields
pub addr: String,
pub token: String,
pub db_path: String,
pub git_commit: String,
pub backup_dir: String,
pub backup_interval: Duration,
pub backup_keep: usize,
pub rate_limit_window: Duration,
pub rate_limit_max: u32,
pub trusted_ip_header: String,
pub merge_enabled: bool,
pub merge_timeout: Duration,
pub claude_bin: String,
pub claude_status_interval: Duration,
pub ephemeral_device_ttl: Duration,
pub eval_interval: Option<Duration>,
pub public_url: String,
pub tls: TlsMode,
pub tls_max_connections: usize,
}Expand description
What recall-server needs.
Every field has a default that is safe to run with, except token,
which has none — see ConfigError::MissingToken.
| Field | Variable | Default |
|---|---|---|
addr | RECALL_PORT (the host is always 0.0.0.0) | 0.0.0.0:8787 |
token | RECALL_TOKEN | required |
db_path | RECALL_DB_PATH | data/recall.db |
git_commit | RECALL_GIT_COMMIT | the commit the binary was built from, else unknown |
backup_dir | RECALL_BACKUP_DIR | off |
backup_interval | RECALL_BACKUP_INTERVAL_HOURS | 24h |
backup_keep | RECALL_BACKUP_KEEP | 7 |
rate_limit_window | RECALL_RATE_LIMIT_WINDOW_MS | 60s |
rate_limit_max | RECALL_RATE_LIMIT_MAX | 60 |
trusted_ip_header | RECALL_TRUSTED_IP_HEADER | cf-connecting-ip, forced empty when tls is on |
merge_enabled | RECALL_MERGE_ENABLED | on |
merge_timeout | RECALL_MERGE_TIMEOUT_MS | 45s |
claude_bin | RECALL_CLAUDE_BIN | claude |
claude_status_interval | RECALL_CLAUDE_STATUS_INTERVAL_MS | 30m |
ephemeral_device_ttl | RECALL_EPHEMERAL_DEVICE_TTL_HOURS | 24h |
eval_interval | RECALL_EVAL_INTERVAL_HOURS | off |
public_url | RECALL_PUBLIC_URL | unset: passkey sign-in off |
tls | RECALL_TLS_CERT/RECALL_TLS_KEY, or RECALL_TLS_ACME_DOMAINS/RECALL_TLS_ACME_EMAIL/RECALL_TLS_ACME_DIR/RECALL_TLS_ACME_STAGING | off |
tls_max_connections | RECALL_TLS_MAX_CONNECTIONS | 512 |
RECALL_TLS_REQUIRED is read but not stored: when it is true, a
config with tls off refuses to start (ConfigError::TlsRequired).
Fields§
§addr: StringThe socket to bind: every interface, on RECALL_PORT. What can
reach it is decided by the compose file, not by the address.
token: StringThe single bearer token. There is no second one, by design.
db_path: StringThe SQLite file. Opened, never created from a schema migration — it
is the same file the Node server wrote. Kept in WAL mode, with its
-wal and -shm files beside it, so it must be on a local
filesystem (see Store::open).
git_commit: StringReported by GET /health so a deploy can be confirmed from outside.
A release binary knows its own commit, stamped at build time; the
variable overrides it, for an image built from a checkout.
backup_dir: StringWhere periodic database snapshots go. Empty disables backups.
backup_interval: DurationHow often to take one.
backup_keep: usizeHow many to keep before deleting the oldest.
rate_limit_window: DurationThe window rate limiting counts requests over.
rate_limit_max: u32How many requests one client may make in that window.
trusted_ip_header: StringThe one request header whose value is taken as the client’s address, or empty to trust none and use the socket’s peer address.
Rate limiting keys off this, and rate limiting runs before auth precisely so a flood of invalid tokens is limited too — so a client that can choose its own value here can rotate it and get unlimited attempts at guessing the token.
That makes this a statement about the deployment, not a preference: it names the header the ingress sets, and it is only safe when nothing can reach this server except through that ingress. Exactly one header is read, so a value the client supplies under any other name is ignored.
| Ingress | Set this to |
|---|---|
| Cloudflare Tunnel | cf-connecting-ip (the default) |
| Traefik, nginx, Caddy | x-real-ip |
| None — reached directly | empty |
Deliberately not x-forwarded-for: a proxy appends to it, so the
first entry is whatever the client sent. Reading it as one value is
the classic way to make this setting useless.
merge_enabled: boolWhether to attempt semantic merge at all. Off means last-write-wins.
merge_timeout: DurationHow long a merge may take before it is abandoned — and, like every other merge failure, degraded to last-write-wins.
claude_bin: StringThe claude binary to shell out to. Never the Anthropic API.
claude_status_interval: DurationHow often to re-check that the binary is present and logged in.
ephemeral_device_ttl: DurationHow long an ephemeral device, one a cloud session enrolled with an ephemeral authkey, may go without a signed request before it is removed.
A day by default. A cloud session left open over lunch, a meeting or a night keeps its device; a day’s worth of finished sessions does not pile up in the device list; and the key a finished session left in its container stops working within a day of its last use.
eval_interval: Option<Duration>How often to queue an evaluation of every project by itself, for
the worker to make; None, the default, never does.
A scheduled run never includes the contradiction check, which asks
claude once per project and so spends the owner’s Claude usage:
that runs only when someone asks for it, with
recall eval run --contradictions. The other checks cost nothing.
public_url: StringThe address people reach this server at, such as
https://recall.example.com: an origin, with no path.
Passkeys are bound to a site, and the server cannot learn which one
from a request: behind Traefik it sees plain HTTP, and a Host
header is whatever the client sent. So the site is configured. The
WebAuthn relying party id is its host and the origin a passkey must
be used from is the whole of it. Empty leaves passkey sign-in on
/admin off, and the page says so; nothing else depends on it.
tls: TlsModeWhether this server terminates TLS itself. Off by default: the two
existing deployments (deploy/docker-compose.yml,
docker-compose.traefik.yml) put an ingress in front instead, and
that stays the default. See TlsMode.
tls_max_connections: usizeHow many connections the direct-TLS listener holds open at once, counting ones still in their TLS handshake. Ignored with TLS off, where the ingress in front owns this problem.
With no ingress, every idle or half-open socket an attacker opens
costs this process a file descriptor and a task; this bound turns
“exhaust the process’s descriptors” into “fill these slots until
the timeouts in server/tls.rs close them”. A single owner’s
machines need a handful; the default leaves generous room below the
nofile limit docker-compose.direct.yml sets.
Implementations§
Source§impl Config
impl Config
Sourcepub fn from_env() -> Result<Self, ConfigError>
pub fn from_env() -> Result<Self, ConfigError>
Reads configuration from the real process environment.
Sourcepub fn from_lookup<F>(lookup: F) -> Result<Self, ConfigError>
pub fn from_lookup<F>(lookup: F) -> Result<Self, ConfigError>
Reads configuration through a caller-supplied lookup, applying the same defaults the Node implementation used.
The lookup is injected rather than read from std::env inside so the
clamping below is testable: set_var is process-global, and Rust runs
tests in parallel threads, so an env-reading test races every other
test in the binary.
An unparseable value falls back rather than failing the boot, matching the Node and Go implementations — a typo in one tunable should not be the reason a server won’t start.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Config
impl RefUnwindSafe for Config
impl Send for Config
impl Sync for Config
impl Unpin for Config
impl UnsafeUnpin for Config
impl UnwindSafe for Config
Blanket Implementations§
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more