From c24cef9d6d99bb8eea43195c8898c2ddaf6a5870 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 10 Mar 2026 22:36:07 -0400 Subject: [PATCH] docs: add README with setup instructions and docker-compose.yml Made-with: Cursor --- README.md | 108 +++++++++++++++++++++ chat-summaries/2026-03-10_22-00-summary.md | 37 +++++++ docker-compose.yml | 12 +++ 3 files changed, 157 insertions(+) create mode 100644 README.md create mode 100644 chat-summaries/2026-03-10_22-00-summary.md create mode 100644 docker-compose.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..bcfd93e --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +# owncast-irc-bridge + +Bidirectional chat bridge between [Owncast](https://owncast.online/) and IRC. Messages sent in your Owncast chat appear in an IRC channel and vice versa. + +## Quick Start (Docker Compose) + +**1. Create your config file** + +```bash +cp config.example.toml config.toml +``` + +Edit `config.toml` with your IRC server/channel and Owncast URL. + +**2. Get an Owncast access token** + +In your Owncast admin panel, go to **Integrations > Access Tokens** and create a token with "send messages" permission. + +**3. Set the token** + +```bash +export OWNCAST_ACCESS_TOKEN="your-token-here" +``` + +Or create a `.env` file (git-ignored): + +``` +OWNCAST_ACCESS_TOKEN=your-token-here +``` + +**4. Configure the Owncast webhook** + +In your Owncast admin, go to **Integrations > Webhooks** and add a webhook pointing to: + +``` +http://:9078/webhook +``` + +Select the events: **Chat Message**, **Stream Started**, **Stream Stopped**. + +**5. Run it** + +```bash +docker compose up -d +``` + +Check logs: + +```bash +docker compose logs -f +``` + +## Running Without Docker + +Requires Rust 1.75+. + +```bash +cargo build --release +export OWNCAST_ACCESS_TOKEN="your-token-here" +./target/release/owncast-irc-bridge --config config.toml +``` + +## Configuration + +See [`config.example.toml`](config.example.toml) for all options. The only required sections are `[irc]` (with `server` and `channel`) and `[owncast]` (with `url`). Everything else has defaults. + +| Section | Key | Default | Description | +|---------|-----|---------|-------------| +| `irc` | `server` | *(required)* | IRC server hostname | +| `irc` | `port` | `6667` | IRC server port | +| `irc` | `tls` | `false` | Use TLS for IRC | +| `irc` | `nick` | `owncast-bridge` | IRC nickname | +| `irc` | `channel` | *(required)* | IRC channel to join | +| `owncast` | `url` | *(required)* | Owncast instance URL | +| `owncast` | `webhook_port` | `9078` | Port the webhook server listens on | +| `owncast` | `websocket_enabled` | `false` | Also connect via WebSocket (redundant with webhook, useful as fallback) | +| `owncast` | `health_poll_interval_secs` | `30` | How often to poll Owncast status | +| `bridge` | `irc_prefix` | `[IRC]` | Prefix for IRC messages in Owncast | +| `bridge` | `owncast_prefix` | `[OC]` | Prefix for Owncast messages in IRC | +| `control` | `socket_path` | `/tmp/owncast-irc-bridge.sock` | Unix socket for `bridge-ctl` | + +The access token is always read from the `OWNCAST_ACCESS_TOKEN` environment variable (not the config file). + +## Runtime Control + +Use `bridge-ctl` to interact with a running bridge: + +```bash +bridge-ctl status # Show bridge status as JSON +bridge-ctl irc reconnect # Reconnect to IRC +bridge-ctl owncast reconnect # Reconnect to Owncast +bridge-ctl quit # Shut down the bridge +``` + +Inside Docker: + +```bash +docker compose exec bridge bridge-ctl status +``` + +## How It Works + +- **Owncast → IRC:** Owncast sends webhook events to the bridge. The bridge formats the message and sends it to IRC via PRIVMSG. +- **IRC → Owncast:** The bridge listens for PRIVMSG in the configured channel and posts to Owncast via the integration API. +- **Deduplication:** If both webhook and WebSocket are enabled, duplicate messages are detected by ID and dropped. +- **Echo suppression:** Messages the bridge itself sent are recognized and not re-bridged. +- **Stream events:** Stream start/stop events are announced in IRC. +- **Health polling:** The bridge polls Owncast's `/api/status` endpoint and announces state changes in IRC. diff --git a/chat-summaries/2026-03-10_22-00-summary.md b/chat-summaries/2026-03-10_22-00-summary.md new file mode 100644 index 0000000..cb00dc4 --- /dev/null +++ b/chat-summaries/2026-03-10_22-00-summary.md @@ -0,0 +1,37 @@ +# Owncast–IRC Bridge: Full Implementation + +## Task Description +Executed the full 15-task implementation plan at `docs/plans/2026-03-10-owncast-irc-bridge-impl.md` using the executing-plans skill. Built a complete bidirectional Rust chat bridge between Owncast and IRC. + +## Changes Made (15 commits) +1. **Project scaffolding** — `cargo init`, Cargo.toml with all deps, `.gitignore`, `config.example.toml`, placeholder binaries +2. **Config module** (`src/config.rs`) — TOML parsing with serde defaults, env var support for access token, 3 tests +3. **Events module** (`src/events.rs`) — `BridgeEvent`, `Source`, `OwncastState`, `ControlCommand`, `BridgeStatus` types +4. **HTML stripping** (`src/html.rs`) — Strips HTML tags, extracts emoji `` alt text, decodes entities, 6 tests +5. **Owncast API client** (`src/owncast_api.rs`) — `send_chat_message` with retry, `get_status` for health checks +6. **Health poller** (`src/health.rs`) — Periodic Owncast status polling with state change detection +7. **Webhook server** (`src/webhook.rs`) — Axum HTTP server parsing CHAT/STREAM_STARTED/STREAM_STOPPED events, 5 tests +8. **IRC task** (`src/irc_task.rs`) — `irc` crate client with exponential backoff reconnect +9. **WebSocket task** (`src/websocket.rs`) — tokio-tungstenite client with reconnect, 5 tests +10. **Control socket** (`src/control.rs`) — Unix socket listener with command parsing, 5 tests +11. **Router** (`src/router.rs`) — Central orchestration with dedup tracker, echo suppressor, state handling, 4 tests +12. **Main entry point** (`src/main.rs`) — Wires all tasks together with signal handling (SIGINT/SIGTERM/SIGHUP) +13. **bridge-ctl CLI** (`src/bin/bridge_ctl.rs`) — Clap-based CLI for runtime control via Unix socket +14. **Dockerfile** — Multi-stage build (rust:1.85-slim-bookworm builder, debian:bookworm-slim runtime) +15. **Final fixes** — Added missing `Clone` derive on `IrcConfig`, cleaned up unused imports + +## Verification +- Both binaries (`owncast-irc-bridge`, `bridge-ctl`) compile +- All 28 tests pass across all modules +- Only benign warnings remain (unused struct fields for future API data) + +## Bugs Found in Plan +- Raw string delimiters `r#"..."#` conflicted with TOML `"#channel"` values — fixed with `r##"..."##` +- `strip_html` had peek/consume bug (didn't advance past `<` and `&` before collecting) — fixed +- `reqwest::Response::text()` consumes `self`, so status must be captured first — fixed + +## Follow-up Items +- Integration testing with actual Owncast and IRC instances +- Wire remaining control commands (connect/disconnect/reconnect) through to tasks +- Add `message_buffer_size` buffering logic +- SIGHUP config reload support diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..70f6373 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + bridge: + build: . + container_name: owncast-irc-bridge + restart: unless-stopped + environment: + - OWNCAST_ACCESS_TOKEN=${OWNCAST_ACCESS_TOKEN} + - RUST_LOG=info + volumes: + - ./config.toml:/etc/owncast-irc-bridge/config.toml:ro + ports: + - "9078:9078"