Private, asynchronous Telegram bot for checking scheduled departures and flight delays through FlightAware AeroAPI.
The bot works only in private Telegram chats. Access is granted by an administrator, and each approved user provides their own AeroAPI key. Keys are encrypted before they are stored in SQLite or PostgreSQL.
- administrator-approved access requests;
- one-off airport checks using three-letter IATA codes;
- personal favorite-airport shortcuts for quick checks and monitoring;
- recurring airport monitoring;
- alerts for flights exceeding the configured delay threshold;
- separate AeroAPI credentials for each user;
- Fernet encryption for stored keys and deletion of key messages;
- API request limits and per-user cooldowns;
- SQLite (default, with automated local snapshots) and PostgreSQL database backends;
- audit log, usage statistics, and automated data retention cleanup;
- hardened systemd service configuration.
The bot includes a small server-rendered administrator panel. It has no background polling, WebSockets, frontend build step, or JSON API. Pages are refreshed only when opened, navigated, or explicitly refreshed.
By default it listens only on the local loopback address 127.0.0.1:8080:
http://127.0.0.1:8080/
To change the bind address or port, set FPB_WEB_HOST and FPB_WEB_PORT in
the environment file. For remote access, keep the application bound to
loopback and publish it through the Caddy reverse proxy.
Set FPB_WEB_AUTH_TOKEN to protect the panel with a token. A browser prompts
for HTTP Basic credentials (the username is ignored; use the token as the
password); API clients can send Authorization: Bearer <token>. When omitted,
the panel remains available only according to its network binding, preserving
the previous local-only behavior.
The panel includes the dashboard, monitoring, users, history, delayed flights, and a read-only Settings page. The dashboard and Settings page show total API usage for today and the current month; Settings also includes the current month's per-user usage. It displays operational configuration and retention values, but deliberately never exposes bot tokens or AeroAPI keys.
- Python 3.11 or newer;
- a Telegram account and bot token from BotFather;
- a FlightAware account with AeroAPI enabled;
- the Telegram user ID of at least one administrator;
- Linux with systemd for production deployment;
- PostgreSQL 14+ (optional, when using PostgreSQL instead of SQLite).
cd /opt/flightping
python3 -m venv .venv
.venv/bin/pip install -e '.[test]'
mkdir -p config state
cp .env.example config/flightpingbot.envEdit config/flightpingbot.env before starting the bot. Never commit this file or any other secret to the repository.
Run locally:
.venv/bin/python -m flightpingbotRun the test suite:
.venv/bin/pytest -qThe test command reports coverage and fails when total coverage falls below 65%. Raise this baseline as new paths receive tests.
The documented deployment is a systemd user service, so it runs as the
current user automatically. Do not set User= or Group= in this unit: those
directives are for system services and cannot express the installing user.
Keep state/ and config/flightpingbot.env readable and writable only by the
user who runs systemctl --user.
The example configuration is available in .env.example.
| Variable | Description |
|---|---|
FPB_BOT_TOKEN |
Telegram bot token from BotFather. |
FPB_CREDENTIALS_KEY |
Stable Fernet key used to encrypt AeroAPI keys. |
FPB_ADMIN_USER_IDS |
Comma-separated Telegram administrator user IDs. |
FPB_STATE_DIR |
Directory for the database, backups, and state; defaults to /opt/flightping/state. |
FPB_DATABASE_URL |
Optional database connection URL. When omitted, SQLite is used at state/flightpingbot.sqlite3. Set to postgresql://user:password@host:5432/flightping to use PostgreSQL. |
FPB_MONITOR_INTERVAL_MINUTES |
Interval between monitoring checks. |
FPB_MONITOR_WINDOW_HOURS |
Departure window requested from AeroAPI. |
FPB_MONITOR_DURATION_HOURS |
Maximum monitoring duration. |
FPB_MIN_DELAY_MINUTES |
Minimum delay considered an alert. |
FPB_TIMEZONE |
Time zone used in user-facing messages. |
FPB_MAX_ACTIVE_AIRPORTS |
Maximum active monitored airports per user. |
FPB_DAILY_API_REQUEST_LIMIT |
Daily request limit; 0 disables the limit. |
FPB_MONTHLY_API_REQUEST_LIMIT |
Monthly request limit; 0 disables the limit. |
FPB_USAGE_WARNING_PERCENT |
Usage percentage at which a warning is shown. |
FPB_USER_REQUEST_COOLDOWN_SECONDS |
Minimum interval between manual user checks. |
FPB_WEB_AUTH_TOKEN |
Optional token protecting the web panel through HTTP Basic or Bearer authentication. |
FPB_TELEGRAM_MESSAGES_PER_MINUTE |
Maximum inbound messages per user per minute; defaults to 30. Administrators are exempt. |
FPB_FSM_STATE_TTL_SECONDS |
Inactivity timeout for interactive input states; defaults to 900 seconds. |
FPB_*_RETENTION_DAYS |
Retention periods for observations, checks, alerts, audit events, API request logs, and stopped monitor jobs. Flight observations are retained for 7 days; checks for 90 days; alerts and audit events for 180 days; stopped jobs for 30 days by default. |
Generate this key once and keep it unchanged across restarts. Changing it makes existing encrypted AeroAPI keys unreadable.
.venv/bin/python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Set the generated value as FPB_CREDENTIALS_KEY in the configuration file.
All commands work in a private chat with the bot.
- Send
/startand wait for approval. - After approval, send
/aeroapiand provide your personal AeroAPI key. - Verify the setup with
/aeroapi statusor/aeroapi test.
| Command | Description |
|---|---|
/start |
Request access or show the current access status. |
/check TFS |
Run a one-off scheduled departure check. |
/monitor TFS |
Start monitoring an airport. |
/status |
Show active monitoring subscriptions. |
/favorites |
Manage up to 8 favorite airports; quickly check or monitor a saved airport. |
/settings |
Change language and defaults for new checks and monitoring; shows personal API usage for the current month. Resetting restores the default English menu and keyboard. |
/stop |
Stop your monitoring subscriptions. |
/aeroapi |
Set or replace your AeroAPI key. |
/aeroapi status |
Show whether an AeroAPI key is configured. |
/aeroapi test |
Test the key through the AeroAPI account endpoint. |
/aeroapi remove |
Remove the key and stop monitoring. |
/usage |
Show your personal AeroAPI usage statistics. |
/cancel |
Cancel the current input flow. |
/help |
Show help and current settings. |
/hide |
Hide the Telegram keyboard. |
Airport values must be three-letter ASCII IATA codes, for example WAW, TFS, or LHR.
Use /favorites or the ⭐ Favorites keyboard button to add or remove an
airport. A saved airport opens actions to check it immediately or start its
monitoring. Favorites are private to each user, are not created automatically
by checks, and are limited to eight airports per user.
Administrators are configured through FPB_ADMIN_USER_IDS.
| Command | Description |
|---|---|
/requests |
List pending access requests. |
/approve <user_id> |
Approve an access request. |
/deny <user_id> |
Deny an access request. |
/users |
List users and their statuses. |
/revoke <user_id> |
Revoke a user's access. |
/block <user_id> |
Block a user. |
/unblock <user_id> |
Restore a user's access. |
/checks [IATA] |
Show recent checks. |
/checklog <check_id> |
Show observations for a check. |
/usage [user_id] |
Show overall or selected-user AeroAPI usage statistics. |
/alerts [IATA] |
Show alert history. |
/audit [days] |
Show recent audit events. |
/admin_status |
Show users, monitoring, and database status. |
/db_status |
Show database status (WAL and backups for SQLite; engine version, database size, and active connections for PostgreSQL). |
/stopall |
Emergency stop for all monitors. |
After revoke or block, the user's active monitors are stopped and any in-progress input flow can no longer be completed.
The flightpingbot.service unit expects the application at /opt/flightping and configuration at config/flightpingbot.env.
Example installation as a user service:
mkdir -p ~/.config/systemd/user
cp flightpingbot.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now flightpingbot.service
systemctl --user status flightpingbot.serviceView logs:
journalctl --user -u flightpingbot.service -fTo keep the service running after logging out of the system account, enable linger:
loginctl enable-linger "$USER"The optional flightpingbot-healthcheck.service unit lets systemd check whether the main service is active.
FlightPingBot supports both SQLite (default) and PostgreSQL backends:
- SQLite (default): When
FPB_DATABASE_URLis omitted, data is stored locally instate/flightpingbot.sqlite3. SQLite runs in WAL mode with secure permissions (0600for database files,0700for the state directory). The automated maintenance task executes WAL checkpoints, creates up to three local snapshot backups instate/backups/, and removes expired records according to retention settings. - PostgreSQL: Set
FPB_DATABASE_URLto a PostgreSQL connection URL (e.g.postgresql://user:password@localhost:5432/flightping). Migrations are applied automatically upon connection using connection pooling (asyncpg). Maintenance tasks purge expired data according to retention policies. Database passwords are masked in logs and/db_statusoutputs.
In production, periodically maintain off-host copies of your database (e.g. through pg_dump for PostgreSQL or by offloading state/backups/ for SQLite).
The repository provides scripts/migrate_sqlite_to_postgres.py to migrate an existing SQLite database to PostgreSQL without data loss.
The migration script:
- Applies all pending schema migrations to the target PostgreSQL database;
- Copies all records in dependency order inside a single atomic transaction;
- Resynchronizes all table serial sequences (
setval) to match current maximum IDs; - Verifies row counts between SQLite and PostgreSQL, failing if any mismatch is detected.
To migrate:
- Stop the bot service:
systemctl --user stop flightpingbot.service
- Run the migration script:
(Alternatively, export
.venv/bin/python scripts/migrate_sqlite_to_postgres.py \ --sqlite-path state/flightpingbot.sqlite3 \ --postgres-url "postgresql://user:password@localhost:5432/flightping"FPB_DATABASE_URLor loadconfig/flightpingbot.envbefore running). - Verify that all tables show
[MATCH]in the verification summary. - Set
FPB_DATABASE_URLinconfig/flightpingbot.env:FPB_DATABASE_URL=postgresql://user:password@localhost:5432/flightping
- Start the bot service and verify status:
Send
systemctl --user start flightpingbot.service
/db_statusto the bot in Telegram as an administrator to inspect PostgreSQL version, database size, and active connection count.
config/flightpingbot.envcontains secrets and must remain outside Git;- database passwords in
FPB_DATABASE_URLare masked in logs and administrative outputs (/db_status), butconfig/flightpingbot.envmust always be protected with restricted file permissions (0600); - keep
FPB_CREDENTIALS_KEYseparately from the repository and database backups; - do not change
FPB_CREDENTIALS_KEYwithout a migration plan for encrypted data; - the bot should not be used in groups or channels;
- users are identified by numeric Telegram user IDs, not
@usernamevalues; - the message containing an API key is deleted after receipt, but Telegram permissions or a temporary API error may prevent deletion; the bot warns the user in that case;
- after code changes, run the tests and inspect the service logs.
.venv/bin/pytest -q
systemd-analyze verify flightpingbot.service flightpingbot-healthcheck.serviceThe .github/workflows/security-audit.yml workflow runs pip-audit and the automated test suite in CI.