- Python 97.8%
- Shell 1.3%
- Dockerfile 0.9%
pytest's auto-collection scans imported classes matching Test*. Quart's quart.testing.app.TestApp matches but has an __init__, producing a non-actionable warning twice per test run. Filter by exact message in [tool.pytest.ini_options]. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| app | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| Claude.md | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| SECURITY.md | ||
Gitea-Apprise Webhook Bridge
A lightweight bridge service that forwards Gitea webhook events to Apprise notifications. Get notified about pushes, issues, pull requests, releases, and more through any of Apprise's 100+ supported notification services (Telegram, Discord, Slack, email, etc.).
AI Disclosure
This container was completely written by Claude AI.
How It Works
Gitea --(webhook)--> Bridge --(API call)--> Apprise API --> Your notification services
- You configure a webhook in Gitea pointing at the bridge
- When events happen in your repo, Gitea sends the event data to the bridge
- The bridge formats it into a human-readable message and forwards it to your Apprise API instance
- Apprise delivers the notification to your configured services
Features
- 22 Gitea event types supported: push, issues, pull requests, comments, reviews, releases, wiki, branch/tag create/delete, forks, and more
- Markdown and plain-text formatting with clickable links, bold text, and smart truncation
- HMAC-SHA256 signature verification to ensure webhooks are actually from your Gitea
- Two Apprise modes: stateful (pre-configured key) or stateless (direct notification URLs)
- Event filtering to only forward the events you care about
- Apprise tags for routing notifications to specific services
- Stateless and lightweight -- no database, no storage, just a single container
- Health check endpoint at
/health
Quick Start
1. Run the bridge
# docker-compose.yml
services:
gitea-apprise-bridge:
image: thefizi/apprise-gitea-notifications
ports:
- "5000:5000"
environment:
- GITEA_WEBHOOK_SECRET=your-shared-secret
- APPRISE_BASE_URL=http://your-apprise-instance:8000
- APPRISE_STATEFUL_KEY=gitea
restart: unless-stopped
docker-compose up -d
2. Configure Gitea
- Go to your repository (or organization) Settings > Webhooks > Add Webhook
- Select Gitea as the webhook type
- Set Target URL to
http://your-bridge-host:5000/webhook - Set Secret to the same value as
GITEA_WEBHOOK_SECRET - Choose which events to trigger on (or select "All Events")
- Save
Note: Depending on your Gitea configuration you may need to update your ALLOWED_HOST_LIST
3. Test it
Push a commit or open an issue -- you should receive a notification through Apprise.
Configuration
All configuration is done through environment variables.
| Variable | Required | Default | Description |
|---|---|---|---|
APPRISE_BASE_URL |
Yes | URL of your Apprise API instance (e.g. http://apprise:8000) |
|
APPRISE_STATEFUL_KEY |
One of these | Apprise configuration key for stateful mode (uses /notify/{key}) |
|
APPRISE_STATELESS_URLS |
One of these | Apprise notification URLs for stateless mode (e.g. tgram://bot/chat,slack://token) |
|
GITEA_WEBHOOK_SECRET |
Recommended | (empty) | Shared secret for HMAC-SHA256 signature verification |
APPRISE_TAGS |
No | (empty) | Comma-separated Apprise tags included on every notification |
NOTIFICATION_FORMAT |
No | markdown |
Message format: markdown or text |
ALLOWED_EVENTS |
No | (empty = all) | Comma-separated list of event types to forward |
LOG_LEVEL |
No | INFO |
Logging level: DEBUG, INFO, WARNING, ERROR |
APPRISE_TIMEOUT |
No | 30 |
Read timeout in seconds for Apprise API calls |
APPRISE_CONNECT_TIMEOUT |
No | 5 |
Connection timeout in seconds when reaching the Apprise API. A shorter value than APPRISE_TIMEOUT means a down Apprise instance fails fast rather than holding the Gitea connection open. |
LISTEN_HOST |
No | 0.0.0.0 |
Host address to bind the server to. When BEHIND_PROXY=true and the proxy is on the same host, consider setting this to 127.0.0.1 to prevent direct external access to the bridge port. (entrypoint-only — not read by the app) |
LISTEN_PORT |
No | 5000 |
Port to bind the server to. (entrypoint-only — not read by the app) |
BEHIND_PROXY |
No | false |
Set to exactly true to pass --proxy-headers to uvicorn when behind a reverse proxy (Nginx, Traefik, etc.). (entrypoint-only — not read by the app) |
PROXY_TRUSTED_IPS |
No | 127.0.0.1 |
IP(s) trusted to set X-Forwarded-For headers. Only used when BEHIND_PROXY=true. Accepts a single IP, a CIDR range, or a comma-separated list. Do not set to * unless your proxy is on a fully private network. (entrypoint-only — not read by the app) |
UVICORN_NO_ACCESS_LOG |
No | false |
Set to true to suppress uvicorn's per-request access logs. Useful when a reverse proxy already handles access logging, or to reduce log noise from health check polling. (entrypoint-only — not read by the app) |
Note: Do not wrap env var values in quotes in your
.envfile — the bridge strips them automatically. For example, useGITEA_WEBHOOK_SECRET=mysecret, notGITEA_WEBHOOK_SECRET="mysecret".
Apprise Modes
You must configure one of the two Apprise modes:
Stateful mode (APPRISE_STATEFUL_KEY): Your Apprise API instance has pre-configured notification URLs saved under a key. The bridge sends to /notify/{key}. This is the simplest setup if you already manage your Apprise configuration.
Stateless mode (APPRISE_STATELESS_URLS): You provide the Apprise notification URLs directly. The bridge sends them with each request to /notify/. Useful if you don't want to configure state in Apprise.
If both are set, stateful mode takes precedence.
Event Filtering
By default, the bridge forwards all events it receives. To limit which events are forwarded, set ALLOWED_EVENTS to a comma-separated list:
ALLOWED_EVENTS=push,issues,pull_request,release
Events not in the list are silently skipped (HTTP 200 returned to Gitea so it doesn't retry).
Supported Events
| Event | Example Notification | Severity |
|---|---|---|
| Push | [user/repo] 3 new commits to main |
info |
| Issue opened | [user/repo] Issue #42 opened |
info |
| Issue closed | [user/repo] Issue #42 closed |
success |
| Issue reopened | [user/repo] Issue #42 reopened |
warning |
| Issue assigned/labeled/milestoned | [user/repo] Issue #42 assigned |
info |
| PR opened | [user/repo] PR #15 opened |
info |
| PR merged | [user/repo] PR #15 merged |
success |
| PR closed (not merged) | [user/repo] PR #15 closed |
warning |
| PR assigned/labeled/milestoned/synced | [user/repo] PR #15 synchronized |
info |
| PR review approved | [user/repo] PR #15 approved |
success |
| PR review changes requested | [user/repo] PR #15 changes requested |
failure |
| Comment (issue or PR) | [user/repo] Comment on issue #42 |
info |
| Review comment | [user/repo] Review comment on PR #15 |
info |
| Release published | [user/repo] Release v1.0.0 published |
success |
| Branch/tag created | [user/repo] Branch created: feature-x |
info |
| Branch/tag deleted | [user/repo] Branch deleted: feature-x |
warning |
| Fork | [user/repo] Repository forked |
info |
| Wiki | [user/repo] Wiki page edited: Home |
info |
The severity maps to Apprise notification types (info, success, warning, failure), which some notification services render differently (e.g. different colors or icons).
Security
- Webhook signature verification: Set
GITEA_WEBHOOK_SECRETto a strong shared secret. The bridge verifies every incoming request using HMAC-SHA256 before processing it. Without this, anyone who knows your bridge URL could send fake notifications. - Non-root container: The Docker image runs as a non-root user.
- No secrets in logs: Webhook secrets and Apprise URLs are never logged.
Development
Running locally
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
APPRISE_BASE_URL=http://localhost:8000 APPRISE_STATEFUL_KEY=test uvicorn app:create_app --factory --reload
Running tests
source .venv/bin/activate
pytest -v # run all tests
pytest --cov=app --cov-report=term-missing # with coverage report
Project structure
app/
__init__.py # Quart app factory
config.py # Environment variable configuration
webhook.py # POST /webhook endpoint
signature.py # HMAC-SHA256 verification
apprise_client.py # Apprise API HTTP client
formatters/ # One module per event type
base.py # Shared formatting helpers
push.py, issues.py, pull_request.py, comments.py,
review.py, repository.py, release.py, wiki.py