# Dis2Hook — Webhook Relay to Discord Bot Dis2Hook receives webhooks from anything that can send an HTTP POST — Gitea, Gogs, Proxmox notifications, Uptime Kuma, home-automation scripts — formats them as Markdown, and relays them into Discord channels **through the Discord Bot API** (not Discord's incoming webhooks, so one bot identity serves every channel). It is built to live in a small **Alpine Linux LXC on Proxmox** and is configured entirely from a built-in web UI. ``` source A ──▶ POST /hook/gitea-ci ──┐ source B ──▶ POST /hook/kuma ──────┼──▶ Dis2Hook ──▶ Discord Bot API ──▶ #channels source C ──▶ POST /hook/backup ────┘ ``` ## Features - **Web UI configurator** — patch in any number of webhook sources, each routed to its own Discord server/channel, from a single console page. - **Markdown templates** — shape each source's message with Discord Markdown and `{dotted.path[0]}` placeholders resolved from the incoming JSON payload; leave the template empty to relay the raw payload as a pretty-printed JSON block. - **Event filters** — per-source rules (`equals` / `contains` / `exists` on any payload path) so only the events you care about reach Discord. - **Heartbeat** — optional periodic pulse to a channel with uptime and relay counts. - **Status updates** — optional online/offline announcements when the service starts or stops. - **Secrets stay local** — bot token and admin key live in a `token.json` generated at install time; it is never pulled from, nor pushed to, this repo. - **One-file updater** — pulls fresh `app.py` / `index.html` straight from the repo, validates them, backs up the old ones, and restarts the service. ## Install > **Run this inside an existing Alpine LXC — never on the Proxmox host.** > The installer refuses to run if it detects Proxmox VE. 1. On the Proxmox host, enter your Alpine container: `pct enter ` 2. Run the one-liner: ```sh wget -qO- https://gogs.av2x.dev/av2x/Dis2Hook/raw/main/install.sh | ash ``` The installer will: - verify it is running inside Alpine (and not on a Proxmox host), - install `python3`, `py3-flask`, `py3-requests` via `apk`, - pull `app.py`, `index.html`, `update.sh`, and the OpenRC service script **directly from this repo**, - prompt for your **Discord bot token** (Developer Portal → your app → Bot), - generate `/opt/dis2hook/token.json` (mode `600`) with the token and a fresh random **admin key**, printed once at the end — save it, - register and start the `dis2hook` OpenRC service. Non-interactive install: ```sh DISCORD_BOT_TOKEN=xxxxx wget -qO- https://gogs.av2x.dev/av2x/Dis2Hook/raw/main/install.sh | ash ``` Optional environment overrides: `D2H_BRANCH` (defaults to `main`, auto-falls back to `master`), `D2H_DIR` (default `/opt/dis2hook`), `D2H_PORT` (default `8823`). ## First-time setup 1. Create a bot at the Discord Developer Portal and copy its token. 2. Invite it to your server with the **Send Messages** and **View Channel** permissions (OAuth2 URL generator → scope `bot`). 3. In Discord, enable Developer Mode, right-click a channel → **Copy Channel ID**. 4. Open `http://:8823/`, unlock with the admin key, add a source, paste the channel ID, save, and hit **Send test message**. 5. Point your service at the source's webhook URL, sending the source secret in the `X-Hook-Secret` header (or `?secret=` query, or a `Bearer` token). ### Sending a webhook by hand ```sh curl -X POST "http://:8823/hook/gitea-ci" \ -H "Content-Type: application/json" \ -H "X-Hook-Secret: " \ -d '{"action":"opened","repository":{"full_name":"av2x/Dis2Hook"}}' ``` ### Template example ``` 🔔 **{repository.full_name}** — {pusher.name} pushed to `{ref}` > {commits[0].message} {commits[0].url} ``` Unresolved placeholders render as empty strings; objects/lists render as compact JSON. Messages are truncated to Discord's 2000-character limit. ## Updating Pull the latest `app.py` and `index.html` directly from the repo and restart: ```sh /opt/dis2hook/update.sh # app.py + index.html /opt/dis2hook/update.sh --all # also refresh update.sh and the service script ``` Downloads are validated (version marker + compile check) before they replace anything, previous files are kept as `*.bak`, and **`token.json` and `config.json` are never touched**. ## Files | Path | Purpose | In repo? | |---|---|---| | `/opt/dis2hook/app.py` | relay service (Flask) | yes | | `/opt/dis2hook/index.html` | web UI | yes | | `/opt/dis2hook/update.sh` | updater | yes | | `/etc/init.d/dis2hook` | OpenRC service (`dis2hook.initd`) | yes | | `/opt/dis2hook/token.json` | **bot token + admin key — generated at install, mode 600** | **never** | | `/opt/dis2hook/config.json` | sources & settings, managed by the web UI | never | | `/var/log/dis2hook.log` | service log | — | ## Service management ```sh rc-service dis2hook status|start|stop|restart tail -f /var/log/dis2hook.log ``` ## Security notes - `token.json` is generated locally, `chmod 600`, and listed in `.gitignore`; treat the admin key like a password — it is the only credential for the UI/API. - Every source has its own shared secret, compared in constant time; requests with a wrong or missing secret are rejected with `401`. - Dis2Hook serves plain HTTP. Keep it on a trusted LAN/VLAN, or front it with a reverse proxy (Caddy, nginx, Nginx Proxy Manager) for TLS if sources send webhooks across the internet. - Relayed messages are sent with all mentions disabled, so a hostile payload cannot ping `@everyone`. - The UI cannot change the listen host/port — edit `config.json` and restart if you need to, so a bad save can never lock you out. ## How it compares to Discohook Discohook is a superb editor for Discord's *incoming webhooks* — you compose a message and Discord hosts the endpoint. Dis2Hook is the opposite direction: **it hosts the endpoint**, accepts webhooks from your own services, filters and reformats them, and delivers via a bot token — so one self-hosted relay fans out many sources to many servers and channels, with heartbeat and uptime status built in. ## Uninstall ```sh rc-service dis2hook stop; rc-update del dis2hook default rm -rf /opt/dis2hook /etc/init.d/dis2hook /var/log/dis2hook.log ```