Files
bouncer/docs/CHEATSHEET.md
user 2f40f5e508 feat: add CertFP authentication with SASL EXTERNAL
Per-network, per-nick client certificates (EC P-256, self-signed,
10-year validity) stored as combined PEM files. Authentication
cascade: SASL EXTERNAL > SASL PLAIN > NickServ IDENTIFY.

New commands: GENCERT, CERTFP, DELCERT. GENCERT auto-registers
the fingerprint with NickServ CERT ADD when the network is connected.

Includes email verification module for NickServ registration and
expanded NickServ interaction (IDENTIFY, REGISTER, VERIFY).
2026-02-21 01:15:25 +01:00

5.2 KiB

Cheatsheet

Run

bouncer -c config/bouncer.toml         # start
bouncer -c config/bouncer.toml -v      # start (debug)
bouncer --version                      # version
bouncer --help                         # help

Podman

make build      # build container image
make up         # podman-compose up -d
make down       # podman-compose down
make logs       # podman logs -f bouncer
podman logs bouncer 2>&1 | grep -E 'INFO|WARN'   # filtered logs
podman exec -it bouncer bash                      # shell into container
podman ps --filter name=bouncer                   # status

Develop

make dev        # install with dev deps into .venv
make test       # pytest
make lint       # ruff check
make fmt        # black + ruff --fix
make run        # run with config/bouncer.toml
make clean      # rm .venv, build artifacts

Client Auth

PASS <password>                 # authenticate (all networks)

Bouncer Commands

Inspection

/msg *bouncer HELP                  # list commands
/msg *bouncer STATUS                # all network states
/msg *bouncer INFO libera           # detailed network info
/msg *bouncer UPTIME                # process uptime
/msg *bouncer NETWORKS              # list networks
/msg *bouncer CREDS [network]       # NickServ creds
/msg *bouncer CHANNELS [network]    # joined channels + topics
/msg *bouncer CLIENTS               # connected clients
/msg *bouncer BACKLOG [network]     # message counts + DB size
/msg *bouncer VERSION               # bouncer + Python version

Network Control

/msg *bouncer CONNECT libera        # start disconnected network
/msg *bouncer DISCONNECT libera     # stop network
/msg *bouncer RECONNECT libera      # restart with fresh identity
/msg *bouncer NICK libera newnick   # change nick
/msg *bouncer RAW libera WHOIS user # send raw IRC command

Config Management

/msg *bouncer REHASH                # reload config file
/msg *bouncer ADDNETWORK name host=h port=N tls=yes nick=n channels=#a,#b
/msg *bouncer DELNETWORK name       # remove network
/msg *bouncer AUTOJOIN net +#chan   # add to autojoin
/msg *bouncer AUTOJOIN net -#chan   # remove from autojoin

NickServ

/msg *bouncer IDENTIFY libera       # force IDENTIFY
/msg *bouncer REGISTER libera       # trigger registration
/msg *bouncer DROPCREDS libera      # delete all creds
/msg *bouncer DROPCREDS libera nick # delete one nick's creds

CertFP

/msg *bouncer GENCERT libera        # generate cert (current nick)
/msg *bouncer GENCERT libera nick   # generate cert (specific nick)
/msg *bouncer CERTFP                # list all cert fingerprints
/msg *bouncer CERTFP libera         # list certs for one network
/msg *bouncer DELCERT libera        # delete cert (current nick)
/msg *bouncer DELCERT libera nick   # delete cert (specific nick)

Namespacing

#channel/network                # channel on a specific network
nick/network                    # foreign nick on a specific network
own-nick                        # own nicks shown without suffix
/msg #libera/libera hello       # send to #libera on libera network
/join #test/oftc                # join #test on oftc
/join #a/libera,#b/oftc         # comma-separated, different networks

Connection States

DISCONNECTED -> CONNECTING -> REGISTERING -> PROBATION (15s) -> READY
State What happens
CONNECTING TCP + SOCKS5 + TLS handshake
REGISTERING Random nick/user/realname sent to server
PROBATION 15s wait, watching for K-line
READY Switch to configured nick, join channels

Auth Cascade

SASL EXTERNAL (cert + creds) > SASL PLAIN (creds) > NickServ IDENTIFY

Reconnect Backoff

5s -> 10s -> 30s -> 60s -> 120s -> 300s (cap)

Config Skeleton

[bouncer]
bind / port / password
  [bouncer.backlog]
    max_messages / replay_on_connect

[proxy]
host / port

[networks.<name>]                 # repeatable
host / port / tls
nick / channels / autojoin
password                          # optional, IRC server PASS

Files

Path Purpose
config/bouncer.toml Active config (gitignored)
config/bouncer.example.toml Example template
config/bouncer.db SQLite backlog (auto-created)
{data_dir}/certs/{net}/{nick}.pem Client certificates (auto-created)

Backlog Queries

-- recent messages
SELECT * FROM messages ORDER BY id DESC LIMIT 20;

-- per-network counts
SELECT network, COUNT(*) FROM messages GROUP BY network;

-- last seen state
SELECT * FROM client_state;

Source Layout

src/bouncer/
  __main__.py    # entry point, event loop
  cli.py         # argparse
  config.py      # TOML loader
  irc.py         # IRC message parse/format
  namespace.py   # /network encode/decode for multiplexing
  proxy.py       # SOCKS5 connector (local DNS, multi-IP, CertFP)
  network.py     # server connection + state machine + SASL
  client.py      # client session handler
  cert.py        # client certificate generation + management
  commands.py    # 25 bouncer control commands (/msg *bouncer)
  router.py      # message routing + backlog trigger
  server.py      # TCP listener
  backlog.py     # SQLite store/replay/prune