Node configuration
birdd reads TOML configuration from birdd.toml by default. Select another
file with --config or BIRDD_CONFIG:
birdd --config /etc/birdd/birdd.toml run
Unknown fields are rejected in the root, federation, audience, projection, and extensions tables. Signal, Grist, and media tables currently ignore unknown fields, so check those names carefully.
NixOS mappings
The NixOS module generates birdd.toml. Common node settings have dedicated
options; other non-secret values use services.birdd.settings with the same
shape as the TOML.
For example, these are equivalent:
[federation.audiences.neighbors]
transport = "restricted"
recipients = ["<public-key>"]
services.birdd.settings.federation.audiences.neighbors = {
transport = "restricted";
recipients = ["<public-key>"];
};
Secret values should use the dedicated NixOS *File options. Those files are
passed to the required process as systemd credentials and override values from
TOML.
Node identity
node_name
Stable operator-facing name used in logs and telemetry. This is not the flock’s cryptographic identity.
- Type: string
- Required
- NixOS:
services.birdd.nodeName
flock_display_name
Human-readable flock name published in signed metadata.
- Type: string
- Required
- NixOS:
services.birdd.flockDisplayName
time_zone
IANA time zone used to interpret local dates and display observation times.
- Type: string, such as
UTCorAmerica/New_York - Default:
UTC - NixOS:
time.timeZone
The time zone is resolved when Signal formats a command response. An invalid name fails at that point rather than during configuration loading.
database
Path to the node’s SQLite database. Relative paths are resolved from the process working directory. Parent directories are created when needed.
- Type: path string
- Required
- NixOS: generated as
${services.birdd.dataDir}/birdd.sqlite
key_file
Path to the flock’s private Nostr key. An existing key is reused. A standalone
node creates a new mode-0600 key file when the path does not exist.
- Type: path string
- Required
- Environment override:
BIRDD_FLOCK_KEY_FILE - NixOS source:
services.birdd.flockKeyFile
The NixOS module makes the source key available as a per-service systemd
credential, so the effective runtime path is under /run/credentials.
Important
Keep this file secret and backed up. A new key creates a new flock identity.
relays
Nostr-compatible WebSocket relays used to publish and receive federated events. URLs are accepted by the Nostr client when synchronization begins.
- Type: array of URL strings
- Default:
[] - NixOS:
services.birdd.relays
An empty list keeps the node local-only. See Relays for synchronization behavior.
Federation
federation.default_audiences
Audience policies applied to new local mutations. Every entry must name a
policy under federation.audiences, and duplicates are rejected.
- Type: non-empty array of audience names
- Default:
["public"] - NixOS:
services.birdd.settings.federation.default_audiences
federation.audiences
Map of available audience policies. Defining this map replaces the default
map; add the public policy explicitly if you still want it.
- Type: map containing 1-32 policies
- Default: one audience named
public - NixOS:
services.birdd.settings.federation.audiences
Audience names contain 1-32 lowercase letters, digits, or hyphens. Replace
<name> below with an audience name.
federation.audiences.<name>.transport
Determines whether events are published openly or encrypted for named recipients.
- Type: enum
- Values:
public,restricted - Required
- NixOS:
services.birdd.settings.federation.audiences.<name>.transport
federation.audiences.<name>.allowed_senders
Nostr public keys allowed to send events into a restricted audience. Public audiences must leave this empty. This is independent of the local trust store.
- Type: array of unique Nostr public keys
- Default:
[] - NixOS:
services.birdd.settings.federation.audiences.<name>.allowed_senders
federation.audiences.<name>.recipients
Nostr public keys that receive encrypted events for a restricted audience. Restricted audiences require at least one recipient; public audiences must leave this empty. A recipient can belong to only one configured audience.
- Type: array of unique Nostr public keys
- Default:
[] - NixOS:
services.birdd.settings.federation.audiences.<name>.recipients
Audience projection
Projection controls change the representation shared with an audience. Public audiences must use every default below; custom projections are restricted-only.
federation.audiences.<name>.projection.observed_at_precision
Reduces timestamp precision before publication. hour and day use UTC
boundaries.
- Type: enum
- Values:
exact,hour,day - Default:
exact - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.observed_at_precision
federation.audiences.<name>.projection.publication_delay_seconds
Minimum delay before a restricted recipient’s projected event becomes eligible for publication.
- Type: unsigned integer from
0through31536000 - Default:
0 - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.publication_delay_seconds
federation.audiences.<name>.projection.notes
Controls whether observation notes are included.
- Type: boolean
- Default:
true - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.notes
federation.audiences.<name>.projection.tags
Controls whether observation tags are included.
- Type: boolean
- Default:
true - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.tags
federation.audiences.<name>.projection.location
Controls whether location data is included.
- Type: boolean
- Default:
false - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.location
federation.audiences.<name>.projection.media
Controls whether signed media references are included.
- Type: boolean
- Default:
true - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.media
federation.audiences.<name>.projection.extensions
Selects which namespaced extension envelopes are included.
- Type: non-empty array of strings
- Values:
["*"]or an allowlist of namespaced extension IDs - Default:
["*"] - NixOS:
services.birdd.settings.federation.audiences.<name>.projection.extensions
Projection selects complete envelopes, not only fields declared by a local manifest. Review the extension’s actual data before adding its ID to an audience. See Federation and audiences for the disclosure model.
Extensions
Extensions are disabled by default. Birdd loads an ordered list of declarative TOML manifests at startup:
[extensions]
manifests = ["extensions/birds-count.toml"]
extensions.manifests
- Type: array of filesystem paths
- Default:
[] - NixOS:
services.birdd.settings.extensions.manifests
Relative paths are resolved from the directory containing the selected
birdd.toml, not from the process working directory. Use absolute paths when a
manifest is installed separately from the configuration.
All paths are loaded as one registry. A missing or invalid file, invalid Signal template, duplicate extension ID or profile name, generated Grist column collision, or invalid measure reference prevents startup. Restart the node, Signal adapter, Grist webhook, and other long-running consumers after changing the list.
The Nix package includes optional examples under share/birdd/extensions. A
NixOS configuration can reference one directly from the selected package:
services.birdd.settings.extensions.manifests = [
"${inputs.birdd.packages.${pkgs.system}.birdd}/share/birdd/extensions/birds-count.toml"
];
For a source checkout, the equivalent file is
extensions/birds-count.toml. No example manifest is loaded automatically.
See Extensions for the data model and
Authoring extensions for the manifest schema.
Signal
The Signal adapter connects to the loopback HTTP API provided by signal-cli.
All three connection values are required by birdd signal-run.
signal.base_url
Base URL for the Signal event stream and JSON-RPC API. It must be credential-free loopback HTTP with no path, query, or fragment.
- Type: URL string
- Default: unset
- NixOS:
services.birdd.settings.signal.base_url
signal.account
Signal account whose inbound messages are accepted and whose identity sends bot responses.
- Type: secret string
- Default: unset
- Environment file:
BIRDD_SIGNAL_ACCOUNT_FILE - NixOS:
services.birdd.signalAccountFile
signal.group_id
The only Signal group authorized to send commands to this flock.
- Type: secret string
- Default: unset
- Environment file:
BIRDD_SIGNAL_GROUP_ID_FILE - NixOS:
services.birdd.signalGroupIdFile
On NixOS, signalAccountFile and signalGroupIdFile must be configured
together.
Signal search view
signal.views.search optionally customizes !search output. A list view needs
only a layout and header:
[extensions]
manifests = ["/etc/birdd/extensions/birds-count.toml"]
[signal.views.search]
layout = "list"
header = "Found {observation_count} {observation_count:plural(observation)}"
signal.views.search.layout
- Values:
list,grouped - Required when
signal.views.searchis present - NixOS:
services.birdd.settings.signal.views.search.layout
signal.views.search.header
Header template, up to 512 printable bytes. Supported placeholders are
{observation_count} and {observation_count:plural(observation)}.
- Type: string
- Required when
signal.views.searchis present - NixOS:
services.birdd.settings.signal.views.search.header
Grouped search
A grouped view combines case-insensitive subject matches and can show the last date, latest note, and loaded extension measures:
[signal.views.search]
layout = "grouped"
header = "Found {observation_count} {observation_count:plural(observation)}"
[signal.views.search.group]
by = "subject"
normalize = "casefold"
label = "latest"
heading = "{group_label:highlight}"
sort_by = "core.last_seen"
sort_order = "descending"
[[signal.views.search.group.fields]]
label = "Last seen"
measure = "core.last_seen"
format = "date"
[[signal.views.search.group.fields]]
label = "Latest note"
measure = "core.latest_note"
format = "text"
[[signal.views.search.group.fields]]
label = "Seen"
measure = "fyi.birdd.birds/count.seen"
format = "number"
singular = "time"
plural = "times"
signal.views.search.group
Required for layout = "grouped" and forbidden for layout = "list".
- NixOS:
services.birdd.settings.signal.views.search.group
The current grouped implementation requires these exact declarations:
| TOML field | Required value | NixOS suffix under services.birdd.settings |
|---|---|---|
signal.views.search.group.by | subject | signal.views.search.group.by |
signal.views.search.group.normalize | casefold | signal.views.search.group.normalize |
signal.views.search.group.label | latest | signal.views.search.group.label |
signal.views.search.group.sort_by | core.last_seen | signal.views.search.group.sort_by |
signal.views.search.group.sort_order | descending | signal.views.search.group.sort_order |
signal.views.search.group.heading accepts {group_label} or one
{group_label:highlight} placeholder and is limited to 512 printable bytes.
Its NixOS path is
services.birdd.settings.signal.views.search.group.heading.
signal.views.search.group.fields
Non-empty ordered list of values shown under each group. Measures cannot be repeated.
- NixOS:
services.birdd.settings.signal.views.search.group.fields
Each field supports:
| TOML field | Constraint | NixOS suffix under services.birdd.settings |
|---|---|---|
label | Required, 1-64 printable bytes | signal.views.search.group.fields.*.label |
measure | Required measure name | signal.views.search.group.fields.*.measure |
format | date, text, or number, as required by the measure | signal.views.search.group.fields.*.format |
singular | Required for numeric measures and forbidden otherwise | signal.views.search.group.fields.*.singular |
plural | Required for numeric measures and forbidden otherwise | signal.views.search.group.fields.*.plural |
Supported core measure/format pairs are:
core.last_seenwithdatecore.latest_notewithtext
Loaded extension manifests may add numeric measures. The example above requires
the configured birds-count.toml manifest, which declares
fyi.birdd.birds/count.seen. An unknown measure prevents startup.
In Nix, fields is a list of attribute sets; the * above represents one list
element rather than a literal attribute name.
Grist
Grist configuration is optional for the core node. Reconciliation requires
base_url, document_id, and api_key. Handling existing-row webhook edits
requires webhook_secret; handling new rows also requires the reconciliation
settings so Birdd can write generated fields back to Grist.
grist.base_url
Base URL of the Grist API.
- Type: URL string
- Default: unset
- NixOS:
services.birdd.settings.grist.base_url
grist.document_id
ID of the provisioned Grist document.
- Type: secret string
- Default: unset
- Environment file:
BIRDD_GRIST_DOCUMENT_ID_FILE - NixOS:
services.birdd.gristDocumentIdFile
grist.api_key
Document-scoped API key used by reconciliation and new-row webhook writeback.
- Type: secret string
- Default: unset
- Environment file:
BIRDD_GRIST_API_KEY_FILE - NixOS:
services.birdd.gristApiKeyFile
grist.webhook_secret
Bearer secret required to authenticate the Grist edit webhook.
- Type: secret string
- Default: unset
- Environment file:
BIRDD_GRIST_WEBHOOK_SECRET_FILE - NixOS:
services.birdd.gristWebhookSecretFile
The NixOS reconciliation timer is created when both gristDocumentIdFile and
gristApiKeyFile are configured. The webhook service is created independently
when gristWebhookSecretFile is configured. That independent service can
process corrections and deletions, but new-row creation cannot complete its
Grist writeback unless the document ID and API key credentials are also
available.
Media
Media storage is disabled by default. When enabled, birdd uses static
credentials with an S3 or S3-compatible object store.
media.enabled
- Type: boolean
- Default:
false - NixOS:
services.birdd.settings.media.enabled
When disabled, other media settings are not validated.
media.endpoint
Private S3 API endpoint. Leave unset for the normal regional AWS endpoint. Plain HTTP is allowed only for loopback hosts.
- Type: optional HTTP or HTTPS URL
- Default: unset
- NixOS:
services.birdd.settings.media.endpoint
media.public_endpoint
Public birdd media route used by other flocks, not the S3 endpoint. It must be
HTTPS, end in /_birdd/media, and contain no credentials, query, or fragment.
- Type: optional HTTPS URL
- Default: unset
- NixOS:
services.birdd.settings.media.public_endpoint
media.region
- Type: non-empty string
- Default:
us-east-1 - NixOS:
services.birdd.settings.media.region
media.bucket
- Type: string
- Required when media is enabled
- NixOS:
services.birdd.settings.media.bucket
media.access_key
- Type: secret string
- Required when media is enabled
- Environment file:
BIRDD_MEDIA_ACCESS_KEY_FILE - NixOS:
services.birdd.mediaAccessKeyFile
media.secret_key
- Type: secret string
- Required when media is enabled
- Environment file:
BIRDD_MEDIA_SECRET_KEY_FILE - NixOS:
services.birdd.mediaSecretKeyFile
The NixOS media credential options must be configured together.
media.max_attachments
- Type: unsigned integer from
1through16 - Default:
4 - NixOS:
services.birdd.settings.media.max_attachments
media.max_attachment_bytes
Maximum input, sanitized output, stored object, and remote download size for one attachment.
- Type: positive unsigned integer
- Default:
15728640(15 MiB) - Must not exceed
media.max_total_bytes - NixOS:
services.birdd.settings.media.max_attachment_bytes
media.max_total_bytes
Maximum combined attachment size for one Signal message.
- Type: unsigned integer
- Default:
31457280(30 MiB) - Range:
media.max_attachment_bytesthrough268435456(256 MiB) - NixOS:
services.birdd.settings.media.max_total_bytes
media.max_image_dimension
Maximum width and height used when sanitizing images.
- Type: unsigned integer from
256through8192 - Default:
4096 - NixOS:
services.birdd.settings.media.max_image_dimension
media.processing_timeout_seconds
Timeout applied separately to each image or audio processor invocation.
- Type: unsigned integer from
1through300 - Default:
30 - NixOS:
services.birdd.settings.media.processing_timeout_seconds
See Media storage for supported formats, delivery authorization, and operational behavior.
Credential files
Each environment variable below contains a path to a credential file, not the credential value itself. File contents are trimmed and empty files are rejected.
| Environment variable | Value loaded |
|---|---|
BIRDD_FLOCK_KEY_FILE | Private flock key path override |
BIRDD_SIGNAL_ACCOUNT_FILE | signal.account |
BIRDD_SIGNAL_GROUP_ID_FILE | signal.group_id |
BIRDD_GRIST_DOCUMENT_ID_FILE | grist.document_id |
BIRDD_GRIST_API_KEY_FILE | grist.api_key |
BIRDD_GRIST_WEBHOOK_SECRET_FILE | grist.webhook_secret |
BIRDD_MEDIA_ACCESS_KEY_FILE | media.access_key |
BIRDD_MEDIA_SECRET_KEY_FILE | media.secret_key |
Secret values are protected in memory with the
secrecy crate and omitted when Rust
serializes the effective configuration.
NixOS-only options
These service options affect deployment but are not written as Rust TOML settings:
services.birdd.enable: enables the node serviceservices.birdd.package: package containing thebirddbinaryservices.birdd.dataDir: state directory; default/var/lib/birddservices.birdd.syncIntervalSeconds: sync interval; default5services.birddGarage.enable: enables the reference Garage object store and configures Birdd media storageservices.birddGarage.capacity: capacity assigned to the local Garage node; default10G
The generated base values for node_name, flock_display_name, time_zone,
database, key_file, and relays take precedence over values placed in
services.birdd.settings.