Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 UTC or America/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 0 through 31536000
  • 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.search is 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.search is present
  • NixOS: services.birdd.settings.signal.views.search.header

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 fieldRequired valueNixOS suffix under services.birdd.settings
signal.views.search.group.bysubjectsignal.views.search.group.by
signal.views.search.group.normalizecasefoldsignal.views.search.group.normalize
signal.views.search.group.labellatestsignal.views.search.group.label
signal.views.search.group.sort_bycore.last_seensignal.views.search.group.sort_by
signal.views.search.group.sort_orderdescendingsignal.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 fieldConstraintNixOS suffix under services.birdd.settings
labelRequired, 1-64 printable bytessignal.views.search.group.fields.*.label
measureRequired measure namesignal.views.search.group.fields.*.measure
formatdate, text, or number, as required by the measuresignal.views.search.group.fields.*.format
singularRequired for numeric measures and forbidden otherwisesignal.views.search.group.fields.*.singular
pluralRequired for numeric measures and forbidden otherwisesignal.views.search.group.fields.*.plural

Supported core measure/format pairs are:

  • core.last_seen with date
  • core.latest_note with text

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 1 through 16
  • 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_bytes through 268435456 (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 256 through 8192
  • 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 1 through 300
  • 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 variableValue loaded
BIRDD_FLOCK_KEY_FILEPrivate flock key path override
BIRDD_SIGNAL_ACCOUNT_FILEsignal.account
BIRDD_SIGNAL_GROUP_ID_FILEsignal.group_id
BIRDD_GRIST_DOCUMENT_ID_FILEgrist.document_id
BIRDD_GRIST_API_KEY_FILEgrist.api_key
BIRDD_GRIST_WEBHOOK_SECRET_FILEgrist.webhook_secret
BIRDD_MEDIA_ACCESS_KEY_FILEmedia.access_key
BIRDD_MEDIA_SECRET_KEY_FILEmedia.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 service
  • services.birdd.package: package containing the birdd binary
  • services.birdd.dataDir: state directory; default /var/lib/birdd
  • services.birdd.syncIntervalSeconds: sync interval; default 5
  • services.birddGarage.enable: enables the reference Garage object store and configures Birdd media storage
  • services.birddGarage.capacity: capacity assigned to the local Garage node; default 10G

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.