diff --git a/README.md b/README.md index 7bc21f6..72b57eb 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,26 @@ # GitHub Release Monitor -Bash monitor for GitHub releases, with Telegram notifications and SOCKS5 proxy support. +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+ -- curl with SOCKS5 support -- jq -- flock (util-linux) -- mktemp (coreutils) +- Bash 4+ (Linux) +- `curl` with SOCKS5 support +- `jq` +- `flock` (util-linux) +- `mktemp` (coreutils) +- `hostname`, `date`, `awk`, `sed`, `sort`, `tail` -Debian: +On Debian 12/13: ```bash -sudo apt-get install curl jq ca-certificates util-linux +sudo apt-get update +sudo apt-get install -y bash curl jq ca-certificates util-linux coreutils ``` -## Setup +## Installation ```bash cp .env.example .env @@ -24,53 +28,110 @@ chmod 600 .env chmod +x monitor.sh ``` -Set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` in `.env`. `GITHUB_TOKEN` is optional for public repositories. `SOCKS5_PROXY` can be set to `socks5h://127.0.0.1:1080`; `socks5h` resolves DNS through the proxy. The `.env` file is sourced as Bash: use only trusted local configuration. +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. -Edit `repos.json` to select GitHub repositories. Initial test set: Lychee, Firefly III and Memos. +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 -./monitor.sh --include-prereleases ``` -`--test-telegram` sends one Telegram test message without querying GitHub or accessing `state.json`/`repos.json`. +**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. -`--init` records current releases without notifications; it **replaces** existing baselines. Use only for first initialization or an intentional reset. Normal first run also initializes missing repositories silently. +`--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. -`--dry-run` queries GitHub and displays pending notifications without sending or changing state. On a repository without baseline it prints its current release tags. - -`--include-prereleases` overrides `INCLUDE_PRERELEASES=false`. To track prereleases continuously, set `INCLUDE_PRERELEASES=true` in `.env`. - -State is written atomically after each successful Telegram notification. If Telegram accepted a message but its response was lost, a duplicate may be sent next time. The state contains release IDs and grows over time. A bounded GitHub pagination window (`GITHUB_PER_PAGE * GITHUB_MAX_PAGES`) may miss releases if more are published between checks. For small projects and daily checks this is generally sufficient. +To track prereleases every day, set `INCLUDE_PRERELEASES=true` in `.env` instead of enabling them for occasional runs. ## Daily cron -At 09:00 server-local time: +Run once daily at 09:00 in the server's local timezone: ```cron 0 9 * * * cd /opt/github-release-monitor && ./monitor.sh >> monitor.log 2>&1 ``` -Run the monitor under a dedicated user where possible. Keep `.env` private and do not commit it. Use `./monitor.sh --test-telegram` to verify Telegram and proxy connectivity. +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. -### Large release histories +For troubleshooting: -GitHub response pages are processed through temporary files, rather than passed -through command-line arguments. The state file is read directly by `jq`. -This prevents `Argument list too long` for repositories with large release notes. +```bash +bash -n monitor.sh +./monitor.sh --dry-run --verbose +``` -Run `bash -n monitor.sh` and `./monitor.sh --dry-run --verbose` to verify. +## State, ordering, and reliability -### Release notes and retention +- `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. -`repos.json` uses `owner/repo` names (legacy full GitHub URLs remain accepted). -`TELEGRAM_INCLUDE_CHANGELOG=true` includes a shortened release body in Telegram messages. -`TELEGRAM_CHANGELOG_MAX_LENGTH=1800` limits the changelog; Telegram messages are capped at 4096 characters. -`STATE_MAX_IDS=500` bounds stored release IDs per repository. Keep this larger than the number of releases that may appear in the fetched pages; old releases returning after pruning may otherwise be notified again. +**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. -GitHub HTTP 403/429 responses log rate-limit headers and fail without changing the repository state. Mid-pagination errors also fail the repository without partially processing it. Tags without GitHub releases are not tracked. +**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.