diff --git a/README.md b/README.md index 0aee860..9b73903 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,26 @@ IRC Client(s) --> [bouncer:6667] --> Router --> [SOCKS5:1080] --> IRC Server(s) 4. Joins configured channels 5. Clients connect to bouncer, receive backlog replay and channel state +## Podman Deployment + +```bash +cp config/bouncer.example.toml config/bouncer.toml +$EDITOR config/bouncer.toml + +make build +make up +make logs +``` + +See [docs/DEPLOY.md](docs/DEPLOY.md) for full container documentation. + ## Documentation | Document | Description | |----------|-------------| | [docs/INSTALL.md](docs/INSTALL.md) | Prerequisites and setup | | [docs/USAGE.md](docs/USAGE.md) | Comprehensive guide | +| [docs/DEPLOY.md](docs/DEPLOY.md) | Podman container deployment | | [docs/CHEATSHEET.md](docs/CHEATSHEET.md) | Quick reference | | [docs/DEBUG.md](docs/DEBUG.md) | Troubleshooting | diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index 75cb021..80d12f0 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -9,6 +9,21 @@ bouncer --version # version bouncer --help # help ``` +## Podman + +```bash +make build # build container image +make up # podman-compose up -d +make down # podman-compose down +make logs # podman logs -f bouncer +``` + +```bash +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 ```bash diff --git a/docs/DEBUG.md b/docs/DEBUG.md index 4a9bf34..a844ae5 100644 --- a/docs/DEBUG.md +++ b/docs/DEBUG.md @@ -123,3 +123,41 @@ bouncer -c config/bouncer.toml -v 2>&1 | grep -v aiosqlite ``` The `grep -v aiosqlite` filters out noisy SQLite debug lines. + +## Container Debugging + +### Logs + +```bash +podman logs -f bouncer # follow +podman logs --tail 50 bouncer # last 50 lines +podman logs bouncer 2>&1 | grep -E 'INFO|WARN' # important only +podman logs bouncer 2>&1 | grep -v aiosqlite # filter sqlite noise +``` + +### No log output from container + +Two things must be set: + +1. `PYTHONUNBUFFERED=1` in the Containerfile +2. `logging: driver: k8s-file` in compose.yaml + +Verify log driver: + +```bash +podman inspect bouncer --format '{{.HostConfig.LogConfig.Type}}' +``` + +### Container exits immediately + +```bash +podman logs bouncer +``` + +Likely causes: missing config file, SOCKS5 proxy not running, TOML syntax error. + +### Shell into running container + +```bash +podman exec -it bouncer bash +``` diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..5d3fa18 --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,234 @@ +# Podman Deployment + +## Overview + +The bouncer runs in a rootless podman container with host networking. Source code +and configuration are mounted as volumes -- the container image only contains +Python and pip dependencies. + +``` +Container (bouncer) + |- python:3.12-slim + deps + |- /app/src <- ./src (read-only volume) + |- /data <- ./config (read-write volume) +``` + +## Prerequisites + +- `podman` and `podman-compose` +- SOCKS5 proxy reachable at `127.0.0.1:1080` (host network) + +Verify: + +```bash +podman --version +podman-compose --version +ss -tlnp | grep 1080 +``` + +## Quick Start + +```bash +cd ~/git/bouncer + +# create config from template +cp config/bouncer.example.toml config/bouncer.toml +$EDITOR config/bouncer.toml + +# build and start +make build +make up + +# check logs +make logs +``` + +## Container Image + +The image installs only runtime dependencies. Source code is not baked in. + +```bash +# build +podman build -t bouncer -f Containerfile . + +# rebuild after dependency changes +podman build --no-cache -t bouncer -f Containerfile . +``` + +### Containerfile + +```dockerfile +FROM python:3.12-slim +WORKDIR /app +RUN pip install --no-cache-dir \ + "python-socks[asyncio]>=2.4" \ + "aiosqlite>=0.19" +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app/src +VOLUME /app/src +VOLUME /data +ENTRYPOINT ["python", "-m", "bouncer"] +CMD ["-c", "/data/bouncer.toml"] +``` + +## Compose + +```yaml +services: + bouncer: + build: + context: . + dockerfile: Containerfile + container_name: bouncer + restart: unless-stopped + network_mode: host + logging: + driver: k8s-file + volumes: + - ./src:/app/src:Z,ro + - ./config:/data:Z + command: ["-c", "/data/bouncer.toml", "-v"] +``` + +### Volume Mounts + +| Host Path | Container Path | Mode | Purpose | +|-----------|---------------|------|---------| +| `./src` | `/app/src` | read-only | Python source code | +| `./config` | `/data` | read-write | Config + SQLite backlog | + +The `:Z` suffix applies the correct SELinux context for rootless podman. + +### Host Networking + +`network_mode: host` is required because the bouncer needs access to: + +- `127.0.0.1:1080` -- SOCKS5 proxy (outbound IRC connections) +- `127.0.0.1:6667` -- client listen port (inbound IRC clients) + +### Log Driver + +`k8s-file` is used instead of the default `journald` driver to ensure +`podman logs` works reliably in rootless mode. + +## Operations + +### Start / Stop / Restart + +```bash +podman-compose up -d # start (detached) +podman-compose down # stop and remove +podman-compose restart # restart +``` + +Or with make targets: + +```bash +make up # podman-compose up -d +make down # podman-compose down +``` + +### Logs + +```bash +podman logs -f bouncer # follow all logs +podman logs --tail 50 bouncer # last 50 lines +podman logs bouncer 2>&1 | grep -v aiosqlite # filter sqlite noise +podman logs bouncer 2>&1 | grep -E 'INFO|WARN' # important events only +``` + +Or: + +```bash +make logs # podman logs -f bouncer +``` + +### Status + +```bash +podman ps --filter name=bouncer +podman inspect bouncer --format '{{.State.Status}}' +``` + +### Shell Access + +```bash +podman exec -it bouncer bash +podman exec bouncer python -c "import bouncer; print(bouncer.__version__)" +``` + +### Inspect Backlog + +The SQLite database is stored in the config volume: + +```bash +sqlite3 config/bouncer.db "SELECT network, COUNT(*) FROM messages GROUP BY network;" +``` + +## Updating + +After pulling new code: + +```bash +git pull +podman-compose down +podman-compose up -d +``` + +The image only needs rebuilding if dependencies change: + +```bash +podman-compose down +make build +podman-compose up -d +``` + +## Troubleshooting + +### Container exits immediately + +Check logs for config or proxy errors: + +```bash +podman logs bouncer +``` + +Common causes: + +- Missing `config/bouncer.toml` (not mounted) +- SOCKS5 proxy not running on host +- Invalid TOML syntax + +### No log output + +Verify `PYTHONUNBUFFERED=1` is set in the Containerfile and the log driver +is `k8s-file`: + +```bash +podman inspect bouncer --format '{{.HostConfig.LogConfig.Type}}' +``` + +### Container can't reach SOCKS5 proxy + +`network_mode: host` must be set. Verify the proxy is listening: + +```bash +ss -tlnp | grep 1080 +``` + +### Permission denied on volumes + +The `:Z` suffix handles SELinux relabeling. If issues persist: + +```bash +podman unshare ls -la config/ +``` + +### Rebuild from scratch + +```bash +podman-compose down +podman rmi bouncer +podman build --no-cache -t bouncer -f Containerfile . +podman-compose up -d +``` diff --git a/docs/INSTALL.md b/docs/INSTALL.md index c377aab..69b1cfa 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -5,7 +5,17 @@ - Python 3.10+ - SOCKS5 proxy running on `127.0.0.1:1080` -## Setup +## Container Deployment (Recommended) + +See [DEPLOY.md](DEPLOY.md) for full podman/container instructions. + +```bash +cp config/bouncer.example.toml config/bouncer.toml +$EDITOR config/bouncer.toml +make build && make up +``` + +## Local Setup ```bash cd ~/git/bouncer