this repo has no description
Go 92.8%
Dockerfile 3.6%
Makefile 3.3%
Shell 0.3%
13 1 0

Clone this repository

https://tangled.org/brookie.blog/brooke-spin
git@tangled.org:brookie.blog/brooke-spin

For self-hosted knots, clone URLs may differ based on your setup.

README.md

brooke-spin#

A background service that automatically rotates your Bluesky profile picture by a configurable number of degrees each time you receive a notification.

Features#

  • Automated Profile Picture Rotation: Rotates your avatar every time you get likes, reposts, replies, follows, mentions, or quotes
  • Configurable Rotation: Set your preferred rotation angle (default: 2 degrees)
  • Selective Notifications: Choose which notification types trigger rotation
  • State Persistence: Tracks processed notifications to avoid duplicates
  • Docker Support: Runs as a lightweight container on Raspberry Pi (ARM64/ARMv7)
  • Graceful Shutdown: Handles SIGTERM/SIGINT properly
  • Secure: App password can be set via environment variable

Prerequisites#

  • A Bluesky account
  • An app password from Bluesky Settings > App Passwords
  • Docker and Docker Compose (for containerized deployment)
  • OR Go 1.22+ (for local development)

Quick Start#

1. Clone the Repository#

git clone <repository-url>
cd brooke-spin

2. Create Configuration#

Copy the example configuration and edit it:

cp config.example.yaml config.yaml
nano config.yaml

Update the following fields:

  • bluesky.handle: Your Bluesky handle (e.g., username.bsky.social)
  • bluesky.app_password: Your app password (or set via environment variable)
  • rotation.degrees: Rotation angle per notification (default: 2.0)

3. Deploy with Docker#

# Build and start the service
docker compose up -d

# View logs
docker compose logs -f

# Stop the service
docker compose down

4. Monitor#

Watch the logs to see it working:

docker compose logs -f brooke-spin

You should see messages like:

Starting brooke-spin service...
Authenticating with Bluesky...
Authentication successful!
Polling for notifications every 30s
Found 2 new relevant notification(s)
Downloading current avatar...
Rotating avatar by 2.00 degrees...
Uploading rotated avatar...
Updating profile...
✓ Profile picture rotated successfully! (Total rotation: 42.00°)

Configuration#

Configuration File (config.yaml)#

bluesky:
  handle: "yourhandle.bsky.social"
  app_password: "xxxx-xxxx-xxxx-xxxx"

rotation:
  degrees: 2.0  # Rotation per notification

polling:
  interval: "30s"  # How often to check for notifications

notifications:
  types:
    like: true
    repost: true
    reply: true
    follow: true
    mention: true
    quote: true

state:
  storage: "sqlite"  # or "json"
  path: "./data/state.db"

Environment Variables#

You can override configuration values with environment variables:

  • BLUESKY_APP_PASSWORD: Override the app password (recommended for Docker)

Example:

export BLUESKY_APP_PASSWORD="your-app-password"
docker compose up -d

Or in docker compose.yml:

environment:
  - BLUESKY_APP_PASSWORD=${BLUESKY_APP_PASSWORD}

Advanced Usage#

Running Locally (Without Docker)#

# Install dependencies
go mod download

# Build
go build -o brooke-spin ./cmd/brooke-spin

# Run
./brooke-spin -config config.yaml

Building for Raspberry Pi#

# Using Docker buildx for ARM64
docker buildx build --platform linux/arm64 -t brooke-spin:latest .

# Or build natively on Raspberry Pi
go build -o brooke-spin ./cmd/brooke-spin

Custom Polling Interval#

Adjust how often the service checks for new notifications:

polling:
  interval: "1m"  # Check every minute
  # interval: "30s"  # Check every 30 seconds (default)
  # interval: "5m"   # Check every 5 minutes

Selective Notification Types#

Only rotate on specific interactions:

notifications:
  types:
    like: true      # Rotate on likes
    repost: false   # Ignore reposts
    reply: true     # Rotate on replies
    follow: true    # Rotate on follows
    mention: false  # Ignore mentions
    quote: true     # Rotate on quotes

Change Rotation Direction#

Use negative degrees to rotate counter-clockwise:

rotation:
  degrees: -2.0  # Rotate 2 degrees counter-clockwise

State Storage Options#

Choose between SQLite (default) or JSON:

state:
  storage: "sqlite"  # Recommended: atomic writes, queryable
  path: "./data/state.db"

  # OR use JSON (simpler, but less robust)
  # storage: "json"
  # path: "./data/state.json"

Deployment on Raspberry Pi#

1. Copy Files to Pi#

# From your local machine
scp -r brooke-spin/ pi@raspberrypi.local:~/

2. SSH to Pi and Deploy#

ssh pi@raspberrypi.local
cd ~/brooke-spin

# Edit configuration
nano config.yaml

# Start the service
docker compose up -d

# Check logs
docker compose logs -f

3. Auto-Start on Boot#

The service will automatically restart with restart: unless-stopped in docker compose.yml.

To ensure Docker Compose starts on boot:

# Enable Docker service
sudo systemctl enable docker

# Add to crontab
crontab -e

# Add this line:
@reboot cd /home/pi/brooke-spin && docker compose up -d

Troubleshooting#

Authentication Failed#

  • Double-check your handle and app password
  • Make sure you're using an app password, not your main account password
  • Generate a new app password from Bluesky Settings > App Passwords

Profile Picture Not Rotating#

  • Check logs: docker compose logs -f
  • Verify you're receiving notifications (test by liking one of your own posts from another account)
  • Ensure notification types are enabled in config
  • Check that your profile has an avatar set

Container Won't Start#

# Check container status
docker compose ps

# View full logs
docker compose logs

# Rebuild the container
docker compose down
docker compose build --no-cache
docker compose up -d

State Not Persisting#

  • Ensure the ./data directory exists and is writable
  • Check volume mounts in docker compose.yml
  • Verify the state path in config.yaml matches the mounted volume

Development#

Project Structure#

brooke-spin/
├── cmd/
│   └── brooke-spin/
│       └── main.go              # Entry point
├── internal/
│   ├── config/
│   │   └── config.go            # Configuration loading
│   ├── client/
│   │   └── bluesky.go           # Bluesky API client
│   ├── image/
│   │   └── processor.go         # Image rotation
│   └── state/
│       └── manager.go           # State persistence
├── config.example.yaml          # Example configuration
├── docker compose.yml           # Docker Compose config
├── Dockerfile                   # Docker image definition
├── Makefile                     # Build helpers
└── README.md                    # This file

Running Tests#

make test

Building#

# Local build
make build

# Docker build
make docker-build

# Docker build for ARM (Raspberry Pi)
make docker-build-arm

How It Works#

  1. Polling: Every N seconds (configurable), the service polls the Bluesky notifications API
  2. Filtering: Unread notifications are filtered by configured types (like, repost, etc.)
  3. Detection: If new relevant notifications are found, the rotation process begins
  4. Download: Current profile picture is downloaded from Bluesky
  5. Rotation: Image is rotated by configured degrees using Go image libraries
  6. Upload: Rotated image is uploaded as a new blob
  7. Update: Profile is updated with the new avatar blob reference
  8. State: Notification cursor and cumulative rotation are saved to disk

Security#

  • Never commit config.yaml: It's in .gitignore by default
  • Use environment variables: Set BLUESKY_APP_PASSWORD instead of storing in config
  • App passwords: Always use app passwords, never your main account password
  • Non-root container: Docker image runs as non-root user for security

License#

MIT License - See LICENSE file for details

Contributing#

Contributions welcome! Please open an issue or pull request.

Credits#

Built with:

Support#

Having issues? Please open an issue on GitHub with:

  • Your configuration (without passwords!)
  • Log output
  • Expected vs actual behavior