goose.art honkbot
Bluesky bot that replies to animal commands with random Flickr images
docker pull atcr.io/goose.art/honkbot:latest
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.