A small Rails app that helps you post to multiple social networks at once. It started as a single-user tool and is being built so it can grow into a multi-user SaaS later.
Networks: Mastodon, Bluesky, Threads, and Nostr (compose/sign/publish via NIP-07 in the browser; relays are configured in code).
- Composer with file uploads and alt text
- Pick the networks you want (including “Select all”)
- Background deliveries with per-provider status
- Unified timeline across connected accounts (auto-refresh, like/repost)
- Encrypted token storage (Lockbox + Blind Index)
- Sign up / sign in (Devise)
- Multi-user mode with an admin area (promote admins, manage users)
- Ruby 3.3, Rails 8
- PostgreSQL (app plus separate DBs for Solid Cache, Solid Queue, Solid Cable in the default Docker setup)
- Redis (included in Docker Compose; optional depending on how you wire features)
- Solid Queue for background jobs (database-backed in the default configuration)
- Tailwind CSS, esbuild, Hotwire (Turbo, Stimulus)
- Faraday for HTTP calls
- Security: Devise, Rack::Attack, Secure Headers, Lockbox, Blind Index, SSRF checks on user-supplied instance URLs
The repo ships one compose file: docker-compose.yml. It runs Rails in production mode with a source bind mount (good for local iteration), bundled web and worker services, Postgres, and Redis.
Requirements: Docker and Docker Compose.
git clone https://github.com/gummipunkt/crossy.git
cd crossy
cp env/.env.production.example env/.env.production
# Edit env/.env.production: SECRET_KEY_BASE, LOCKBOX_MASTER_KEY, BLIND_INDEX_MASTER_KEY,
# PUBLIC_BASE_URL, SMTP, Threads keys, etc.Use a strong POSTGRES_PASSWORD (and matching credentials in DATABASE_URL if you override it). See comments at the top of docker-compose.yml for Redis password / deploy hygiene.
docker compose up -d --buildThe web container runs db:prepare, builds JS/CSS, precompiles assets, then starts Puma on port 3000 inside the container.
- From your machine: http://localhost:3022 (host port 3022 is mapped to container port 3000)
- Health check:
GET /up - Root / composer:
/ - Timeline:
/timeline - Your posts:
/my - Provider accounts:
/provider_accounts
If assets are missing:
docker compose exec -w /app/server web bash -lc "bin/rails javascript:build && bin/rails css:build && bin/rails assets:precompile"docker compose exec -w /app/server web bash -lc "bin/rails db:migrate"Environment variables are loaded from env/.env.production (see env/.env.production.example). Additional examples live in .env.production.example and .env.development.example.
Important
PUBLIC_BASE_URL— OAuth redirects, mailer links, host authorization (withlocalhost/127.0.0.1allowed for internal checks where configured).- Threads — Whitelist redirect URI:
https://<your-domain>/auth/threads/callback. Use threads.net OAuth/Graph URLs, not threads.com. - Lockbox / Blind Index —
LOCKBOX_MASTER_KEY,BLIND_INDEX_MASTER_KEY(64 hex chars for blind index; keep quoted in env files).
SMTP (password reset): MAILER_SENDER, SMTP_ADDRESS, SMTP_PORT, SMTP_DOMAIN, SMTP_USERNAME, SMTP_PASSWORD, SMTP_AUTH, SMTP_STARTTLS, optional SMTP_OPENSSL_VERIFY_MODE.
Database: DATABASE_URL required in production; optional CACHE_DATABASE_URL, QUEUE_DATABASE_URL, CABLE_DATABASE_URL (Compose sets separate DB URLs by default). Tuning: DB_POOL, DB_SSLMODE, DB_CONNECT_TIMEOUT, etc.
Bluesky: optional BLUESKY_BASE (default https://bsky.social). Connect via Provider accounts in the UI (handle + app password).
After changing env:
docker compose down && docker compose up -d --force-recreateIf you have Ruby/Node/Postgres locally:
cd server
bundle install
bin/rails db:prepare
bin/devUse config/database.yml and env vars as usual for development.
- Mastodon — Instance URL (https) + access token; scopes should include
write:statuses(and media if you upload). - Bluesky — Handle + app password under Provider accounts.
- Threads — “Connect Threads” →
/auth/threads. - Nostr — Add a Nostr provider account; on the post page use prepare / sign / publish with a NIP-07 extension.
GitHub Actions (.github/workflows/ci.yml) runs Brakeman, bundler-audit, RuboCop, and tests from the server/ directory.
- Assets / esbuild — Run the asset build commands above inside the
webcontainer; hard-reload the browser. - Images blocked — CSP is configured in Secure Headers; remote timeline images use broad
img_srcfor provider CDNs. - Threads token (e.g. 190) — Reconnect via
/auth/threads. - Mastodon uploads — Check token scopes and that the instance URL uses
https://. - Gems reinstalling every boot — Normal if the container is recreated without a persistent bundle volume; Compose uses a
bundle-datavolume to cache gems between restarts.
- Keep secrets in environment or a secrets manager, not in git.
- Access tokens are encrypted at rest (Lockbox).
- Rate limiting (Rack::Attack) and security headers are enabled; user-supplied federation URLs are validated before server-side HTTP requests.
- Richer Nostr relay configuration (UI / per account)
- Provider webhooks / streaming
- Better media (video, carousels)
- Profile management
Licensed under EUPL-1.2. Official text: EUPL-1.2



