# 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) - `curl` with SOCKS5 support - `jq` - `flock` (util-linux) - `mktemp` (coreutils) - `hostname`, `date`, `awk`, `sed`, `sort`, `tail` On Debian 12/13: ```bash sudo apt-get update sudo apt-get install -y bash curl jq ca-certificates util-linux coreutils ``` ## Installation ```bash 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: ```json { "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: ```bash ./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: ```cron 0 9 * * * cd /srv/share/github-release-monitor && ./monitor.sh >> monitor.log 2>&1 ``` Use `crontab -e` for the account running the monitor. Change `/srv/share/github-release-monitor` to your actual installation directory. Cron output redirection does not rotate logs. ### Log rotation To rotate `monitor.log` once it exceeds **1 MiB**, create `/etc/logrotate.d/github-release-monitor`: ```conf /srv/share/github-release-monitor/monitor.log { size 1M rotate 3 missingok notifempty compress copytruncate } ``` `rotate 3` retains up to three compressed older logs. `copytruncate` works with the existing `>> monitor.log` redirection without changing the cron entry. Install/configure `logrotate` on the host and verify the rule: ```bash sudo logrotate -d /etc/logrotate.d/github-release-monitor ``` **Note:** `logrotate` checks the size only when its scheduled job runs (typically daily). The active log can temporarily exceed 1 MiB, and rotated archives consume additional disk space. This is not a strict total-storage cap. For troubleshooting: ```bash bash -n monitor.sh ./monitor.sh --dry-run --verbose ``` ## State, ordering, and reliability - `state.json` records 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 -n` prevents 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`, and `Retry-After` headers. 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 long` on 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.