Files
bouncer/docs/USAGE.md
user 1ea72011b7 fix: reduce reconnect backoff to 1s flat
Exponential backoff up to 300s made no sense with rotating Tor exits
where each reconnect gets a fresh IP. Single 1s delay is sufficient.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:39:30 +01:00

711 lines
21 KiB
Markdown

# Usage
## Starting the Bouncer
```bash
bouncer -c config/bouncer.toml -v
```
| Flag | Description |
|------|-------------|
| `-c, --config PATH` | Config file (default: `config/bouncer.toml`) |
| `-v, --verbose` | Debug logging |
| `--version` | Show version |
## Connection Lifecycle
The bouncer goes through several states when connecting to an IRC server:
```
DISCONNECTED -> CONNECTING -> REGISTERING -> PROBATION -> READY
| | |
`--------------+--------------'
(failure = reconnect)
```
### 1. Stealth Registration
On connect, the bouncer registers with a **random identity**:
- **Nick**: pronounceable markov-generated word (e.g., `heliagu`, `crewo`, `midon`)
- **User/Ident**: random pronounceable word
- **Realname**: random capitalized word
No fixed prefix or pattern -- each attempt looks like a different person.
### 2. Probation (15 seconds)
After registration succeeds (001 RPL_WELCOME), the bouncer enters a probation
window (default 45s, configurable via `probation_seconds`). During this time it watches for:
- `ERROR` messages (K-line, ban)
- Server closing the connection
If the connection drops during probation, the bouncer reconnects with a fresh
random identity and tries again.
### 3. Ready
Once probation passes without incident:
1. Bouncer switches to your configured nick (`NICK mynick`)
2. Joins configured channels (if `autojoin = true`)
3. Begins relaying messages to/from connected clients
### 4. Reconnection
On any disconnection, the bouncer reconnects with exponential backoff
(configurable via `backoff_steps`):
Reconnection delay is **1 second** (flat, no escalation). Each attempt gets a
fresh random identity and potentially a different exit IP.
Each reconnection uses a fresh random identity.
## DNS Resolution
Hostnames are resolved locally before being passed to the SOCKS5 proxy. If a
hostname resolves to multiple IPs, the bouncer tries each one until a connection
succeeds. This handles proxies that don't support remote DNS and avoids IPs
that are unreachable through the proxy.
## Connecting with an IRC Client
Configure your IRC client to connect to the bouncer:
| Setting | Value |
|---------|-------|
| Server | `127.0.0.1` |
| Port | `6667` (or as configured) |
| Password | `yourpassword` |
### Password Format
```
PASS <password>
```
The password is the `bouncer.password` value from config. A single connection
automatically attaches to **all** configured networks.
### Client Examples
**irssi:**
```
/connect -password mypassword 127.0.0.1 6667
```
**weechat:**
```
/server add bouncer 127.0.0.1/6667 -password=mypassword
/connect bouncer
```
**hexchat:**
Set server password to `mypassword` in the network settings.
## Client TLS
The bouncer can accept TLS-encrypted connections from IRC clients. This
encrypts the password and all traffic between your client and the bouncer.
### Setup
```toml
[bouncer]
client_tls = true
```
On first start with `client_tls = true`, the bouncer auto-generates a
self-signed EC P-256 certificate at `{data_dir}/bouncer.pem` (10-year validity).
The certificate fingerprint is logged at startup.
### Custom Certificate
To use your own certificate (e.g. from Let's Encrypt):
```toml
[bouncer]
client_tls = true
client_tls_cert = "/path/to/fullchain.pem"
client_tls_key = "/path/to/privkey.pem"
```
If the cert and key are in the same PEM file, set only `client_tls_cert`.
### Client Examples
**irssi:**
```
/connect -tls -tls_verify no -password mypassword 127.0.0.1 6667
```
**weechat:**
```
/server add bouncer 127.0.0.1/6667 -password=mypassword -ssl -ssl_verify=0
/connect bouncer
```
**hexchat:**
Enable "Use SSL for all the servers on this network" and accept the
self-signed certificate.
### Verify with openssl
```bash
openssl s_client -connect 127.0.0.1:6667
```
## Multi-Network Namespacing
All configured networks are multiplexed onto a single client connection. Channels
and nicks carry a `/network` suffix so you can tell which network they belong to:
```
Client sees: Server wire:
#libera/libera <-> #libera (on libera network)
#debian/oftc <-> #debian (on oftc network)
user123/libera <-> user123 (on libera network)
```
### Rules
- **Channels**: `#channel/network` in client, `#channel` on wire
- **Foreign nicks**: `nick/network` in client, `nick` on wire
- **Own nicks**: shown without suffix (prevents client confusion)
- **Sending messages**: include the `/network` suffix in the target
```
/msg #libera/libera hello -> sends "hello" to #libera on libera network
/join #test/oftc -> joins #test on oftc network
/msg user123/libera hi -> private message to user123 on libera
```
### Comma-Separated JOIN/PART
Targets can span networks:
```
/join #a/libera,#b/oftc -> joins #a on libera AND #b on oftc
```
Multiple clients can attach simultaneously. All receive the same namespaced
messages in real time.
## What Clients Receive on Connect
When a client authenticates:
1. **Backlog replay** -- missed messages (namespaced) from all networks
2. **Synthetic welcome** -- 001-004 numeric replies listing all networks
3. **Channel state** -- synthetic JOIN, TOPIC, and NAMES for every joined
channel across all networks (all namespaced with `/network` suffix)
## Backlog
Messages are stored in `bouncer.db` (SQLite) next to the config file. When
you reconnect, missed messages are automatically replayed.
Configure in `bouncer.toml`:
```toml
[bouncer.backlog]
max_messages = 10000 # per network, 0 = unlimited
replay_on_connect = true # set false to disable replay
```
Stored commands: `PRIVMSG`, `NOTICE`, `TOPIC`, `KICK`, `MODE`.
## PING Watchdog
The bouncer sends periodic PING messages to detect stale server connections
(socket open but no data flowing). If no data is received within the configured
interval, a PING is sent. If the server doesn't respond within the timeout,
the connection is dropped and a reconnect is scheduled.
```toml
[bouncer]
ping_interval = 120 # seconds of silence before sending PING
ping_timeout = 30 # seconds to wait for PONG after PING
```
The watchdog starts automatically when a network enters the READY state.
Any received data (not just PONG) resets the timer.
## IRCv3 server-time
The bouncer requests the `server-time` IRCv3 capability on every connection.
When enabled by the server, timestamps on incoming messages are preserved and
forwarded to clients. When the server does not provide a timestamp, the bouncer
injects one using the current UTC time.
Backlog replay also includes timestamps from when messages were originally
stored, so clients that support `server-time` see accurate times on replayed
messages.
No client configuration is needed -- timestamps appear automatically if the
client supports IRCv3 message tags.
## Push Notifications
When no IRC clients are connected to the bouncer, highlights and private
messages can trigger push notifications via [ntfy](https://ntfy.sh) or a
generic webhook.
### Setup
```toml
[bouncer]
notify_url = "https://ntfy.sh/my-bouncer-topic"
notify_on_highlight = true # mentions of your nick in channels
notify_on_privmsg = true # private messages
notify_cooldown = 60 # min seconds between notifications
notify_proxy = false # route notifications through SOCKS5
```
### ntfy Example
```toml
notify_url = "https://ntfy.sh/my-secret-topic"
```
Install the ntfy app on your phone and subscribe to the topic. Notifications
include the sender, target, and message text.
### Generic Webhook
Any URL that does not contain `ntfy` in the hostname is treated as a generic
webhook. The bouncer POSTs JSON:
```json
{
"network": "libera",
"sender": "user",
"target": "#channel",
"text": "hey mynick, check this out"
}
```
### Behavior
- Notifications only fire when **no clients** are attached
- The cooldown prevents notification floods (one per `notify_cooldown` seconds)
- When `notify_proxy = true`, notification requests are routed through the
configured SOCKS5 proxy
## Configuration Reference
```toml
[bouncer]
bind = "127.0.0.1" # listen address
port = 6667 # listen port
password = "changeme" # client authentication password
# Client TLS
client_tls = false # enable TLS for client listener
client_tls_cert = "" # path to PEM cert (auto-generated if empty)
client_tls_key = "" # path to PEM key (or same file as cert)
# Captcha solving (NoCaptchaAI)
captcha_api_key = "" # API key (optional, for auto-verification)
captcha_poll_interval = 3 # seconds between solve polls
captcha_poll_timeout = 120 # max seconds to wait for solve
# Connection tuning
probation_seconds = 45 # post-connect watch period for k-lines
backoff_steps = [1] # reconnect delay (seconds)
nick_timeout = 10 # seconds to wait for nick change
rejoin_delay = 3 # seconds before rejoin after kick
http_timeout = 15 # per-request HTTP timeout
# Email verification
email_poll_interval = 15 # seconds between inbox checks
email_max_polls = 30 # max inbox checks (~7.5 min)
email_request_timeout = 20 # per-request timeout for email APIs
# Certificate generation
cert_validity_days = 3650 # client cert validity (~10 years)
# PING watchdog
ping_interval = 120 # seconds of silence before sending PING
ping_timeout = 30 # seconds to wait for PONG after PING
# Push notifications
notify_url = "" # ntfy or webhook URL (empty = disabled)
notify_on_highlight = true # notify on nick mentions
notify_on_privmsg = true # notify on private messages
notify_cooldown = 60 # min seconds between notifications
notify_proxy = false # route notifications through SOCKS5
# Background account farming
farm_enabled = false # enable background registration
farm_interval = 3600 # seconds between attempts per network
farm_max_accounts = 10 # max verified accounts per network
[bouncer.backlog]
max_messages = 10000 # per network, 0 = unlimited
replay_on_connect = true # replay missed messages on client connect
[proxy]
host = "127.0.0.1" # SOCKS5 proxy address
port = 1080 # SOCKS5 proxy port
[networks.libera]
host = "irc.libera.chat" # IRC server hostname
port = 6697 # server port (default: 6697 if tls, 6667 otherwise)
tls = true # use TLS for server connection
nick = "mynick" # desired IRC nick (set after probation)
channels = ["#test"] # channels to join (after probation)
channel_keys = { "#secret" = "hunter2" } # keys for +k channels (optional)
autojoin = true # auto-join channels on ready (default: true)
password = "" # IRC server password (optional, for PASS command)
```
## Automatic Captcha Solving
Some IRC networks (e.g. OFTC) require visiting a URL with hCaptcha to verify
nick registration. The bouncer can solve these automatically using NoCaptchaAI.
### Setup
1. Sign up at [dash.nocaptchaai.com](https://dash.nocaptchaai.com) (free tier: 6000 solves/month)
2. Copy your API key from the dashboard
3. Add to config:
```toml
[bouncer]
captcha_api_key = "your-api-key-here"
```
4. Reload config:
```
/msg *bouncer REHASH
```
### How It Works
When NickServ sends a verification URL containing `/verify/`:
1. The bouncer fetches the page via the SOCKS proxy
2. If hCaptcha is detected and an API key is configured, it submits the
challenge to NoCaptchaAI for solving (all traffic routed through the proxy)
3. The solved token is submitted with the verification form
4. On success, the nick is promoted from `pending` to `verified` status
If no API key is set, or solving fails, the URL is stored as `pending` and
shown via the `CREDS` command for manual verification.
## CertFP Authentication
The bouncer supports client certificate fingerprint (CertFP) authentication
via SASL EXTERNAL. Each certificate is unique per (network, nick) pair and
stored as a combined PEM file at `{data_dir}/certs/{network}/{nick}.pem`.
### Authentication Cascade
When connecting, the bouncer selects the strongest available method:
| Priority | Method | Condition |
|----------|--------|-----------|
| 1 | SASL EXTERNAL | Stored creds + cert file exists |
| 2 | SASL PLAIN | Stored creds, no cert |
| 3 | NickServ IDENTIFY | Fallback after SASL failure |
### Setup
1. Generate a certificate:
```
/msg *bouncer GENCERT libera
```
This creates an EC P-256 self-signed cert (10-year validity) and
auto-sends `NickServ CERT ADD <fingerprint>` if the network is connected.
2. Reconnect to use CertFP:
```
/msg *bouncer RECONNECT libera
```
The bouncer will now present the client certificate during TLS and
authenticate via SASL EXTERNAL.
3. Verify the fingerprint is registered:
```
/msg *bouncer CERTFP libera
```
### Certificate Storage
Certificates are stored alongside the config file:
```
{data_dir}/certs/
libera/
fabesune.pem # cert + private key (chmod 600)
oftc/
mynick.pem
```
## Bouncer Commands
Send a PRIVMSG to `*bouncer` (or `bouncer`) from your IRC client to inspect
and control the bouncer. All commands are case-insensitive.
Responses arrive as NOTICE messages from `*bouncer`.
### Inspection
| Command | Description |
|---------|-------------|
| `HELP` | List available commands |
| `STATUS` | Overview: state, nick, host per network |
| `INFO <network>` | Detailed info for one network (state, server, channels, creds) |
| `UPTIME` | Bouncer uptime since process start |
| `NETWORKS` | List all configured networks with state |
| `CREDS [network]` | NickServ credential status (all or per-network) |
| `CHANNELS [network]` | List joined channels with topics (all or per-network) |
| `CLIENTS` | List connected bouncer clients |
| `BACKLOG [network]` | Message counts per network and database size |
| `VERSION` | Bouncer and Python version |
### Network Control
| Command | Description |
|---------|-------------|
| `CONNECT <network>` | Start a disconnected network |
| `DISCONNECT <network>` | Stop a network |
| `RECONNECT <network>` | Stop and restart with a fresh identity |
| `NICK <network> <nick>` | Change nick on a network |
| `RAW <network> <command>` | Send a raw IRC command to a network |
### Config Management
| Command | Description |
|---------|-------------|
| `REHASH` | Reload config file, add/remove/reconnect networks |
| `ADDNETWORK <name> key=val ...` | Create a network at runtime |
| `DELNETWORK <name>` | Stop and remove a network |
| `AUTOJOIN <network> +#channel [key]` | Add channel (with optional key for +k channels) |
| `AUTOJOIN <network> -#channel` | Remove channel from autojoin list |
**ADDNETWORK keys:** `host` (required), `port`, `tls` (yes/no), `nick`,
`channels` (comma-separated), `channel_keys` (`#chan=key,...`), `password`.
### NickServ
| Command | Description |
|---------|-------------|
| `IDENTIFY <network>` | Force NickServ IDENTIFY with stored credentials |
| `REGISTER <network>` | Trigger NickServ registration attempt |
| `DROPCREDS <network> [nick]` | Delete stored NickServ credentials |
### CertFP
| Command | Description |
|---------|-------------|
| `GENCERT <network> [nick]` | Generate client cert, auto-register with NickServ |
| `CERTFP [network]` | Show certificate fingerprints (all or per-network) |
| `DELCERT <network> [nick]` | Delete a client certificate |
### Account Farming
| Command | Description |
|---------|-------------|
| `FARM` | Global farming status (enabled/disabled, per-network stats) |
| `FARM <network>` | Network stats + trigger an immediate registration attempt |
| `ACCOUNTS [network]` | List all stored accounts with verified/pending counts |
### Examples
```
/msg *bouncer HELP
/msg *bouncer STATUS
/msg *bouncer INFO libera
/msg *bouncer CHANNELS
/msg *bouncer CLIENTS
/msg *bouncer BACKLOG
/msg *bouncer VERSION
/msg *bouncer CONNECT libera
/msg *bouncer DISCONNECT libera
/msg *bouncer RECONNECT libera
/msg *bouncer NICK libera newnick
/msg *bouncer RAW libera WHOIS someuser
/msg *bouncer REHASH
/msg *bouncer ADDNETWORK oftc host=irc.oftc.net port=6697 tls=yes channels=#test
/msg *bouncer DELNETWORK oftc
/msg *bouncer AUTOJOIN libera +#newchannel
/msg *bouncer AUTOJOIN libera +#secret hunter2
/msg *bouncer AUTOJOIN libera -#oldchannel
/msg *bouncer IDENTIFY libera
/msg *bouncer REGISTER libera
/msg *bouncer DROPCREDS libera
/msg *bouncer DROPCREDS libera oldnick
/msg *bouncer GENCERT libera
/msg *bouncer GENCERT libera fabesune
/msg *bouncer CERTFP
/msg *bouncer CERTFP libera
/msg *bouncer DELCERT libera
/msg *bouncer DELCERT libera fabesune
/msg *bouncer FARM
/msg *bouncer FARM libera
/msg *bouncer ACCOUNTS
/msg *bouncer ACCOUNTS libera
```
### Example Output
```
[STATUS]
libera ready fabesune user/fabesune
oftc ready ceraty cloaked.user
hackint connecting (attempt 3)
quakenet ready spetyo --
[CHANNELS]
libera #test Welcome to the test channel
libera #dev
oftc #debian Debian support
[CLIENTS]
myuser 127.0.0.1:54321 connected 2h 15m 3s
[BACKLOG]
libera 1,500 messages
oftc 842 messages
DB size: 2.1 MB
```
## Background Account Farming
The bouncer can automatically grow a pool of verified NickServ accounts across
all configured networks. Primary connections stay active with SASL-authenticated
identities while ephemeral connections register new nicks in the background.
### Setup
```toml
[bouncer]
farm_enabled = true
farm_interval = 3600 # seconds between attempts per network
farm_max_accounts = 10 # max verified accounts per network
```
### How It Works
1. A sweep loop runs every 60 seconds (after an initial 60s stabilization delay)
2. For each NickServ-enabled network, it checks:
- Is there already an active farming attempt? (skip)
- Has the cooldown (`farm_interval`) elapsed since the last attempt? (skip)
- Are there already `farm_max_accounts` verified accounts? (skip)
3. If eligible, an ephemeral connection is spawned with a random nick
4. The ephemeral goes through the full registration lifecycle: REGISTER, email
verification (or captcha), and credential storage
5. Credentials are saved under the real network name, not the ephemeral's
internal `_farm_` prefix
6. Each ephemeral has a 15-minute deadline before being terminated
7. Ephemeral connections are invisible to IRC clients (no status broadcasts,
no channel joins)
### Commands
| Command | What it does |
|---------|-------------|
| `FARM` | Global overview: enabled/disabled, interval, per-network stats |
| `FARM <network>` | Network stats + triggers an immediate registration attempt |
| `ACCOUNTS` | List all stored accounts with verified/pending counts |
| `ACCOUNTS <network>` | Accounts for a specific network |
### Configuration Reference
```toml
[bouncer]
farm_enabled = false # enable background registration (default: off)
farm_interval = 3600 # seconds between attempts per network
farm_max_accounts = 10 # stop farming when this many verified accounts exist
```
## Channel Keys
Channels with mode `+k` require a key to join. Configure keys in TOML:
```toml
[networks.libera]
channels = ["#secret", "#public"]
channel_keys = { "#secret" = "hunter2" }
```
Keys are used automatically during autojoin and KICK rejoin. To add a keyed
channel at runtime:
```
/msg *bouncer AUTOJOIN libera +#secret hunter2
```
Removing a channel also clears its key:
```
/msg *bouncer AUTOJOIN libera -#secret
```
## DCC Stripping
DCC requests (`DCC SEND`, `DCC CHAT`) embed the sender's real IP address in the
protocol payload. The bouncer strips all DCC and non-ACTION CTCP messages in
both directions:
- **Inbound** (server to client): silently dropped, logged as warning
- **Outbound** (client to server): blocked before reaching the network
ACTION (`/me`) is preserved. This is a hard security boundary -- there is no
config toggle to disable it.
## Hot Reload
The bouncer reloads its config file on `SIGHUP` or via the `REHASH` command.
Both use the same logic: re-read TOML, diff networks (add/remove/reconnect),
and update mutable fields (channels, channel_keys, nick, password).
### SIGHUP
```bash
kill -HUP $(pidof bouncer)
```
Results are logged (no client connection needed). Useful for headless
operation (systemd, containers).
### REHASH command
```
/msg *bouncer REHASH
```
Results are sent back as NOTICE messages.
### What changes on reload
| Field | Effect |
|-------|--------|
| Network host/port/tls/proxy | Network reconnected |
| channels, channel_keys, nick, password | Updated in-place |
| notify_url, notify_cooldown, etc. | Notifier recreated |
| farm_enabled, farm_interval, etc. | Farm started/stopped |
| bind, port, password, client_tls | Warning logged (restart required) |
## Systemd
The bouncer ships with a systemd user service file. See [INSTALL.md](INSTALL.md)
for setup. Key operations:
```bash
systemctl --user start bouncer # start
systemctl --user stop bouncer # stop
systemctl --user reload bouncer # hot reload (SIGHUP)
journalctl --user -u bouncer -f # follow logs
```
The service restarts automatically on failure (`RestartSec=10`).
## Stopping
Press `Ctrl+C` or send `SIGTERM`. The bouncer shuts down gracefully, closing
all network connections and the backlog database.