Skip to content

Commit db81050

Browse files
committed
feat: pluggable multi-channel notification hub (Telegram, Bale, Pushover, Gotify, ntfy)
- MCP server with send_message, send_alert, send_log, send_file, list_channels - REST API with per-channel selection and per-call delivery report - Telegram bot UI with /channels and /status - Severity -> native priority mapping per channel - Non-root Docker image with healthcheck - CI matrix on Node 20 & 22 + docker build - Smoke test suite (no network required)
0 parents  commit db81050

25 files changed

Lines changed: 5118 additions & 0 deletions

.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
node_modules
2+
uploads
3+
.env
4+
.env.*
5+
.git
6+
.gitignore
7+
.github
8+
*.md
9+
!README.md
10+
Dockerfile
11+
docker-compose.yml
12+
test

.env.example

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# --- Core ---
2+
API_PORT=3000
3+
API_KEY=watchtower_secret_key_change_me
4+
5+
# Optional: comma-separated default fan-out list. If unset, all enabled
6+
# channels are used unless the API/MCP request specifies "channels".
7+
# Example: DEFAULT_CHANNELS=telegram,ntfy
8+
DEFAULT_CHANNELS=
9+
10+
# --- Telegram channel ---
11+
TELEGRAM_BOT_TOKEN=
12+
TELEGRAM_CHAT_ID=
13+
# If set, Telegram uses webhook mode; otherwise polling.
14+
WEBHOOK_URL=
15+
16+
# --- Bale channel (Telegram-compatible BotAPI) ---
17+
BALE_BOT_TOKEN=
18+
BALE_CHAT_ID=
19+
20+
# --- Pushover channel ---
21+
# https://pushover.net
22+
PUSHOVER_APP_TOKEN=
23+
PUSHOVER_USER_KEY=
24+
PUSHOVER_DEVICE=
25+
26+
# --- Gotify channel ---
27+
# https://gotify.net
28+
GOTIFY_URL=
29+
GOTIFY_APP_TOKEN=
30+
31+
# --- ntfy channel ---
32+
# Public: https://ntfy.sh ; or self-hosted URL
33+
NTFY_URL=https://ntfy.sh
34+
NTFY_TOPIC=
35+
# Optional auth:
36+
NTFY_TOKEN=
37+
NTFY_USER=
38+
NTFY_PASSWORD=

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
node: [20, 22]
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: actions/setup-node@v4
19+
with:
20+
node-version: ${{ matrix.node }}
21+
cache: npm
22+
23+
- run: npm ci
24+
25+
- name: Syntax check
26+
run: |
27+
for f in src/*.js src/channels/*.js test/*.js; do
28+
node --check "$f"
29+
done
30+
31+
- name: Smoke test
32+
run: npm test
33+
34+
docker:
35+
runs-on: ubuntu-latest
36+
needs: test
37+
steps:
38+
- uses: actions/checkout@v4
39+
- name: Build image
40+
run: docker build -t watch-tower:ci .

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# dependencies
2+
node_modules/
3+
4+
# secrets
5+
.env
6+
.env.*
7+
!.env.example
8+
9+
# uploads (runtime)
10+
uploads/*
11+
!uploads/.gitkeep
12+
13+
# logs
14+
npm-debug.log*
15+
yarn-debug.log*
16+
yarn-error.log*
17+
*.log
18+
19+
# editor / OS
20+
.vscode/
21+
.idea/
22+
.DS_Store
23+
Thumbs.db

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [SemVer](https://semver.org/).
4+
5+
## [1.1.0] - 2026-05-28
6+
7+
### Added
8+
- Pluggable channel architecture under `src/channels/`.
9+
- Pushover, Gotify, and ntfy channels.
10+
- Per-request `channels` selector on every REST endpoint and MCP tool.
11+
- `DEFAULT_CHANNELS` env to scope the default fan-out list.
12+
- New MCP tools: `send_file`, `list_channels`.
13+
- `/api/channels` REST endpoint and `/channels` Telegram bot command.
14+
- Severity → priority mapping for Pushover (-1..2), Gotify (2..10), ntfy (1..5 + tags).
15+
- Per-channel delivery report in API responses (`delivered`, `errors`).
16+
- Automatic cleanup of files in `uploads/` after `/api/file` dispatch.
17+
- Smoke test (`npm test`) covering channel resolution and dispatcher.
18+
- LICENSE (MIT), CONTRIBUTING, SECURITY, CHANGELOG, GitHub Actions CI.
19+
20+
### Changed
21+
- API response shape now reports per-channel outcome and returns `502` when no channel delivered.
22+
- Telegram bot UI moved out of the channel module into `src/bot.js`.
23+
- Dockerfile runs as a non-root user with a healthcheck.
24+
- `docker-compose.yml` is now generic (no personal network or hostname).
25+
26+
### Removed
27+
- Hard-coded production URL from the README.
28+
- Old `src/bale.js` (replaced by `src/channels/bale.js`).
29+
30+
## [1.0.0]
31+
32+
- Initial release: Telegram bot + REST API + MCP server.

CONTRIBUTING.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Contributing to Watch Tower
2+
3+
Thanks for your interest! Watch Tower is intentionally small and pluggable.
4+
5+
## Dev setup
6+
7+
```bash
8+
git clone https://github.com/MMTE/watch-tower.git
9+
cd watch-tower
10+
npm install
11+
cp .env.example .env # fill only the channels you want to test
12+
npm start # REST API + Telegram bot
13+
npm run mcp # MCP server over stdio
14+
npm test # smoke test (no network required)
15+
```
16+
17+
Node ≥ 20 is required (uses global `fetch` / `FormData`).
18+
19+
## Adding a new channel
20+
21+
A channel is a CommonJS module under [`src/channels/`](src/channels) that exports:
22+
23+
```js
24+
module.exports = {
25+
name: 'mychannel', // lowercase, unique
26+
enabled: Boolean(/* env */), // computed at load time
27+
async sendMessage(text, { title, level, parse_mode } = {}) { /* ... */ },
28+
async sendFile(filePath, { caption, filename, title, level } = {}) { /* ... */ },
29+
};
30+
```
31+
32+
Then register it in the `ALL` array in [`src/channels/index.js`](src/channels/index.js) and add its env vars to [`.env.example`](.env.example) and the channel table in [`README.md`](README.md).
33+
34+
Guidelines:
35+
36+
- Map the four severity levels (`info`, `warn`, `error`, `critical`) to your channel's native priority if it has one.
37+
- If your channel can't natively attach files, fall back to a textual message (see [`gotify.js`](src/channels/gotify.js)).
38+
- Throw on hard failures so the dispatcher can report them per-channel; don't swallow errors silently.
39+
40+
## Style
41+
42+
- Plain CommonJS, no build step.
43+
- Match the surrounding style; no linter is configured.
44+
- Keep changes small and focused. New runtime dependencies need a clear reason.
45+
46+
## Pull requests
47+
48+
1. Fork and branch from `main`.
49+
2. Run `npm test` before pushing.
50+
3. Update [`README.md`](README.md) and [`CHANGELOG.md`](CHANGELOG.md) when behavior changes.
51+
4. Describe what you changed and why in the PR body.
52+
53+
## Reporting bugs
54+
55+
Open an issue with reproduction steps, the channels involved, Node version, and any redacted error output. Please redact tokens and chat IDs.

Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM node:20-alpine
2+
3+
WORKDIR /app
4+
5+
# Install production dependencies first for better layer caching
6+
COPY package.json package-lock.json* ./
7+
RUN npm ci --omit=dev && npm cache clean --force
8+
9+
# Copy source
10+
COPY src ./src
11+
12+
# Run as non-root, ensure uploads dir is owned correctly
13+
RUN mkdir -p uploads && chown -R node:node /app
14+
USER node
15+
16+
EXPOSE 3000
17+
18+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
19+
CMD wget -qO- http://127.0.0.1:3000/api/health || exit 1
20+
21+
CMD ["node", "src/index.js"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Watch Tower contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)