161 lines
7.7 KiB
Markdown
161 lines
7.7 KiB
Markdown
# 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.
|