Honkbot (Rust) #
A single Bluesky bot that listens to the full firehose via Jetstream and replies to animal commands with random Flickr images.
Rewrite of bskybots/honkbot in Rust for reliability and performance.
A command only triggers a reply when it starts a post or is preceded by whitespace, so URLs like https://mysite.com/meow are ignored while i want to /hoot still works.
Container image #
Published to ATCR — an OCI registry built on the AT Protocol:
atcr.io/goose.art/honkbot:latest
Single layer, ~48 MB, linux/amd64. .tangled/workflows/build.yaml builds and pushes it on every push to main.
Authentication #
ATCR authenticates pulls, so log in first:
$ docker login atcr.io -u goose.art # ATProto handle + app password
$ docker pull atcr.io/goose.art/honkbot:latest
Or install ATCR's credential helper (curl -fsSL https://atcr.io/static/install.sh | sh), which uses an OAuth device flow instead of storing a password.
Anonymous pulls require the image's storage hold to be public. This image currently lives on ATCR's shared hold (did:plc:wnbpdx4bn5uts5yd2o2g3gnm), which is private; see Bring your own storage to publish from your own public hold.
Running the container #
The image bundles the binary and the fallback_images/ tree, so it needs no volumes — only credentials through environment variables.
| Variable | Required | Default | Purpose |
|---|---|---|---|
BSKY_HANDLE |
yes | — | Bot account handle |
BSKY_PASSWORD |
yes | — | App password for the bot account |
FLICKR_KEY |
yes | — | Flickr API key |
BSKY_SERVICE |
no | https://bsky.social |
PDS / service base URL |
FALLBACK_IMAGE_DIR |
no | /app/fallback_images |
Directory of bundled fallback images |
FOLLOWS_ONLY |
no | false |
Only reply to the bot's followers |
RUST_LOG |
no | info |
Log level (debug for verbose Jetstream output) |
docker run #
$ docker run -d \
--name honkbot \
--restart unless-stopped \
-e BSKY_HANDLE=your-bot.bsky.social \
-e BSKY_PASSWORD=your-app-password \
-e FLICKR_KEY=your-flickr-api-key \
atcr.io/goose.art/honkbot:latest
Docker Compose #
docker-compose.prod.yml runs the bot alongside watchtower, which polls for new images every 60 seconds and restarts the container when latest moves:
$ docker login atcr.io -u goose.art # once, so the pull and watchtower can authenticate
$ docker compose -f docker-compose.prod.yml up -d
Credentials go in a .env file next to the compose file (see .env.example). Watchtower reads the credentials from the Docker config.json that docker login writes — the compose file mounts it into the watchtower container at /config.json, so a single login covers both. Set DOCKER_CONFIG_FILE in .env if the file lives elsewhere (NAS paths often do).
To build the image locally instead of pulling, use docker-compose.yml (docker compose up --build).
Commands #
| Command | Animal | Reply Text |
|---|---|---|
/honk |
Goose | HONK |
/awoo |
Wolf | AWOO |
/baaa |
Goat | BAAA |
/bork |
Dog/Puppy | BORK |
/hiss |
Snake | HISS |
/hoot |
Owl | HOOT |
/meehh |
Sheep | MEEHH |
/meow |
Cat | MEOW |
/mumble |
Marmot | MUMBLE |
/oink |
Pig | OINK |
/ribbit |
Frog | RIBBIT |
/squee |
Capybara | SQUEE |
/yowl |
Bobcat | YOWL |
Running from source #
-
Copy
.env.exampleto.envand fill in your credentials:BSKY_HANDLE— your bot's Bluesky handleBSKY_PASSWORD— an app password for the bot accountFLICKR_KEY— a Flickr API key (get one here)
-
Seed fallback images (used when Flickr is down or rate-limited):
$ ./seed_fallback_images.shThis downloads ~12 images per category into
fallback_images/. They are committed so the container image ships with them; you can also add your own.jpg/.png/.webpfiles. -
Build and run:
$ cargo build --release $ ./target/release/honkbot
Tests live next to the code (cargo test) and cover the command-matching rules.
Features #
- Single binary handles all 13 animal commands
- Jetstream WebSocket for efficient firehose consumption (only receives
app.bsky.feed.postevents) - Automatic reconnection with cursor tracking on disconnect
- Bounded concurrency (10 concurrent reply tasks) to avoid flooding APIs
- Session refresh with automatic retry on auth failures
- Flickr fallback images — if Flickr is down or rate-limited, picks a random image from
fallback_images/<command>/ - Image resizing for images over 1MB (Bluesky blob limit)
- Structured logging via
tracing(setRUST_LOG=debugfor verbose output)
Architecture #
Jetstream (WSS) → match post text against triggers → spawn bounded task:
1. Search Flickr for random animal image
2. Download & resize if needed
3. Upload blob to Bluesky
4. Create reply post with image
Bring your own storage #
The container image lives on ATCR, which stores image manifests as AT Protocol records in the owner's PDS and layer blobs in a hold service (S3-compatible). Anonymous pulls are only served by holds with server.public: true; the shared hold used above is private.
To serve pulls without credentials:
- Deploy a hold service with
server.public: true(BYOS guide). - Point your ATCR profile at it by setting
defaultHoldin yourio.atcr.sailor.profilerecord. - Push again (
git push origin main) so new blobs land on the public hold.
The OCI annotations emitted by the build (org.opencontainers.image.*, io.atcr.readme) let ATCR populate this repository page from this README automatically.
- Rust 86%
- Shell 12%
- Docker 2%