GitHub Release Monitor
Lightweight Bash monitor that checks GitHub Releases and sends notifications to Telegram through a SOCKS5 proxy. Designed for unattended daily execution with cron. No Python or Docker required.
The default repository list tracks Lychee, Firefly III, and Memos.
Requirements
- Bash 4+ (Linux)
curlwith SOCKS5 supportjqflock(util-linux)mktemp(coreutils)hostname,date,awk,sed,sort,tail
On Debian 12/13:
sudo apt-get update
sudo apt-get install -y bash curl jq ca-certificates util-linux coreutils
Installation
cp .env.example .env
chmod 600 .env
chmod +x monitor.sh
Edit .env and supply TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID. Create a bot with BotFather and send it a message first; the chat ID can then be obtained through Telegram Bot API getUpdates. A bot must have access to the destination chat.
The script sources .env as Bash code. Only use a trusted, locally maintained file. Do not commit it.
Configuration
All settings from .env.example:
| Variable | Default | Purpose |
|---|---|---|
TELEGRAM_BOT_TOKEN |
empty | Telegram bot token; required for sending |
TELEGRAM_CHAT_ID |
empty | Target user/group/channel ID |
GITHUB_TOKEN |
empty | Optional GitHub API token; recommended to avoid low anonymous rate limits |
SOCKS5_PROXY |
socks5h://127.0.0.1:1080 |
Proxy for both GitHub and Telegram; use socks5h for proxy-side DNS |
REQUEST_TIMEOUT |
30 |
Maximum time in seconds per HTTP request |
GITHUB_PER_PAGE |
100 |
Releases per API page, range 1–100 |
GITHUB_MAX_PAGES |
3 |
Maximum pages fetched per repository |
REPOS_FILE |
repos.json |
Repository configuration path |
STATE_FILE |
state.json |
Persisted release IDs |
INCLUDE_PRERELEASES |
false |
Include GitHub prereleases |
TELEGRAM_INCLUDE_CHANGELOG |
true |
Include shortened release notes |
TELEGRAM_CHANGELOG_MAX_LENGTH |
1800 |
Maximum release-note characters in each message |
STATE_MAX_IDS |
500 |
Maximum stored release IDs per repository |
REPOS_FILE and STATE_FILE are resolved relative to the script directory unless absolute paths are specified. The default .env is loaded from that directory; set ENV_FILE to select another configuration file.
For authenticated GitHub requests, use a token with access to the repositories you want to monitor. For public repositories, authentication is optional. GitHub's unauthenticated REST API rate limit is normally 60 requests/hour per originating IP; authenticated requests generally have a larger allowance.
Repositories
repos.json contains GitHub owner/repo names:
{
"repositories": [
"LycheeOrg/Lychee",
"firefly-iii/firefly-iii",
"usememos/memos"
]
}
Legacy URLs such as https://github.com/usememos/memos or https://www.github.com/usememos/memos.git are accepted, but owner/repo is preferred. The script monitors GitHub Releases, not Git tags that have no release object.
Commands
| Command | Behavior |
|---|---|
./monitor.sh --test-telegram |
Send one test message, without GitHub requests or reading/writing repos.json/state.json |
./monitor.sh --dry-run --verbose |
Fetch releases, show pending messages and diagnostics, without Telegram delivery or state changes |
./monitor.sh --init |
Replace baseline for all configured repositories; do not notify |
./monitor.sh |
Normal monitoring: notify for new releases and save successful deliveries |
./monitor.sh --include-prereleases |
Include prereleases for this run |
./monitor.sh --help |
Show command-line help |
Recommended first run:
./monitor.sh --test-telegram
./monitor.sh --dry-run --verbose
./monitor.sh --init
./monitor.sh
First-run behavior: if a repository has no entry in state.json, its current releases are recorded as a baseline without notifications. --init replaces existing baselines, so avoid using it as a routine daily command. A dry run with no baseline prints the currently fetched tags; it does not send them.
--test-telegram uses the configured proxy and sends a sample changelog if changelogs are enabled. It does not verify GitHub connectivity. --dry-run does not send any Telegram message.
To track prereleases every day, set INCLUDE_PRERELEASES=true in .env instead of enabling them for occasional runs.
Daily cron
Run once daily at 09:00 in the server's local timezone:
0 9 * * * cd /opt/github-release-monitor && ./monitor.sh >> monitor.log 2>&1
Use crontab -e for the account running the monitor. Change /opt/github-release-monitor to your actual installation directory. Keep monitor.log under log rotation if it grows; cron output redirection does not rotate logs.
For troubleshooting:
bash -n monitor.sh
./monitor.sh --dry-run --verbose
State, ordering, and reliability
state.jsonrecords notified release IDs separately for each repository; it is created automatically.- After successful Telegram delivery, the corresponding release ID is written atomically to state.
- Release notifications are processed oldest-first within the fetched API window.
flock -nprevents overlapping runs; a second invocation exits immediately while another holds the lock.- If any page of a repository's GitHub response fails, that repository is not processed from a partial response. Other repositories continue; the run exits nonzero if any repository failed.
- GitHub HTTP failures log status and available
X-RateLimit-Remaining,X-RateLimit-Reset, andRetry-Afterheaders. The script does not sleep until rate-limit reset. - The Telegram message contains repository, version, name, publication date, release URL, and optionally a truncated changelog. The message is limited to 4096 Unicode characters.
- GitHub API response pages are processed using temporary files rather than passing large JSON values through process arguments, avoiding
Argument list too longon long release histories.
Retention caveat: STATE_MAX_IDS keeps a bounded set of IDs. Pruned IDs may be treated as new if their releases reappear within the fetched API pages. The fetched window is limited to GITHUB_PER_PAGE * GITHUB_MAX_PAGES releases per repository; releases outside it can be missed. Set retention and pagination limits with these trade-offs in mind. The script does not track edited releases or tag-only versions.
Delivery caveat: Telegram does not provide idempotency for sendMessage. If it accepted a message but the HTTP response was lost, the next run may resend it. Telegram POST requests are not automatically retried by this script.
Security and files
Keep .env private (chmod 600 .env). Do not commit .env, state.json, lock files, or logs; these are excluded by .gitignore. The monitor is intended to run as an unprivileged account.
For a clean installation, copy the tracked project files and configure .env locally. Back up state.json if migrating an existing installation to avoid reinitializing baselines.