Running a server
Administration and settings
The admin page, delivering sign-up codes (SMS and email), server settings, backups and security โ a reference for operators.
The server includes an admin page that you open in a browser. It shows the server's status, lets you decide how sign-up and sign-in codes are delivered, and manages users and bots. This page is a reference that gathers the admin page and the server's settings in one place. If you still need to install the server, see Server installation first.
The admin page's interface is in Korean. Below, each tab, button and setting is given with an English name, followed by its Korean label as it appears on screen.
Opening the admin page
The admin page is at /admin/ on the management HTTP port (8080 in the production setup), and it's open only on the server itself (127.0.0.1). Operators get in through an SSH tunnel.
# On your computer (leave it running)
ssh -N -L 8080:127.0.0.1:8080 <user>@<server>
# Browser: http://localhost:8080/admin/
# The token (on the server)
sudo cat /srv/pabal/pabal_server/data/admin-token
Enter the token and click Open (์ด๊ธฐ). The browser remembers the token; remove it with Clear token (ํ ํฐ ์ง์ฐ๊ธฐ) at the top right. To choose the token yourself, set TELEGRAM_ADMIN_TOKEN (16 or more characters) โ the file is then not written.
What each tab shows
| Tab | Contents | Refresh |
|---|---|---|
| Dashboard (๋์๋ณด๋) | People, sessions and connections online right now; numbers of people, bots, groups, messages, photos and files; uptime, ports and DB; JVM; health checks. A warning banner if test numbers are on | 5 seconds |
| Sign-up codes (๊ฐ์ ์ฝ๋) | Pending codes (number, delivery method, delivery status, code, time left, wrong attempts) and recent activity. Copy (๋ณต์ฌ) or cancel (์ทจ์) a code | 3 seconds |
| Users (์ฌ์ฉ์) | Every account โ phone number, sign-in email (editable), whether online, number of signed-in devices, number of messages. Sign out all devices (๋ชจ๋ ๊ธฐ๊ธฐ ๋ก๊ทธ์์), Block number (๋ฒํธ ์ฐจ๋จ), Delete account (๊ณ์ ์ญ์ , with a note on how the person was verified โ a deleted account leaves only its id and the note) | 10 seconds |
| Bots (๋ด) | Bots created with BotFather โ creator, command menu, how they connect (MTProto ยท HTTP polling ยท webhook address and failure reason), number of waiting updates | 10 seconds |
| Voice (์์ฑ) | Voice bots โ the state of the gateways, relay and policy, the kill switch, public launch, allowing transcripts, calls in progress and hanging them up, allowing and suspending bots, testers, limits (below) | 5 seconds |
| Reports (์ ๊ณ ) | What came in through the app's "Report" โ a copy of the content as it was when reported (readable even after the sender deletes it). Delete (on both sides of a chat; in a group, from every member), Suspend account (signs out all devices; the person can't sign in or sign up again), the list of suspended accounts. The number of an account deleted while suspended can't sign up again for 1 year after the deletion (the default) โ lengthen it when suspending, or later with Block sign-up (์ฌ๊ฐ์
๋ง๊ธฐ) in the list: to a later end date (whichever is later of the year and the date counts โ it is never shortened) or indefinitely (admin API: POST /admin/api/reports/<id>/suspend {"userId": โฆ, "blockSignupUntil": "2028-01-01"} or "blockSignupForever": true; to change it later, POST /admin/api/suspensions/<id>/block-signup, an empty body for the default). Handled reports can be viewed too. A handled report is erased, copy and all, 3 years after the day it was handled (its card shows the date); if a dispute, an investigation or a lawsuit begins, Hold (๋ณด์กด) keeps it until it is released (admin API: POST /admin/api/reports/<id>/hold, and /release to release) | 10 seconds |
| Support (๊ณ ๊ฐ์ผํฐ) | Conversations that came to @PabalSupport. Pick a conversation and reply, and the reply goes into that conversation under the support bot's name | 10 seconds |
| Settings (์ค์ ) | Sign-up and sign-in rules, the app-review account, SMS, email (SMTP), blocked phone numbers, the operator details shown on the website | Saved manually |
| Reserved usernames (์์ฝ ์์ด๋) | Usernames blocked so that nobody can take them โ search, add, download the list (.txt), the usernames the server blocks by default. Reserving a name someone already uses doesn't take it away from them | Once, when opened |
| Storage (์ ์ฅ์) | Data directory and DB address (password hidden), number and total size of photos and files, number of stored streams by kind | 10 seconds |
Dangerous actions (signing out, blocking, cancelling a code) only run when you click twice.
Sign-up and sign-in codes
When someone enters a phone number in the app, the server creates a code and sends it by the method chosen in the Settings (์ค์ ) tab.
| Delivery method | Where the code goes | App screens |
|---|---|---|
| Admin page (๊ด๋ฆฌ ํ๋ฉด) | The Sign-up codes (๊ฐ์ ์ฝ๋) tab. The operator copies it and passes it on personally | Number โ "code sent" โ code (name, for a new number) |
| SMS | A text message through one of Twilio ยท Solapi ยท webhook | Same as the admin page |
| Email (์ด๋ฉ์ผ) | An email over SMTP | Number โ enter email โ "code sent by email" โ code |
- With the email method, a new sign-up can use any address, and that address becomes the account's sign-in email (๋ก๊ทธ์ธ ์ด๋ฉ์ผ). Existing accounts receive codes only at their registered sign-in email โ this stops people from getting into someone else's number by entering their own address. Existing accounts without a sign-in email get their code by SMS (if configured) or through the admin page instead. You can add a sign-in email in the Users (์ฌ์ฉ์) tab.
- Sending happens in the background, so the app goes straight to the code screen. The result of sending (success or failure, with the reason) appears in the Sign-up codes tab.
- Signing up (entering a name) is possible only after entering the right code. Wrong codes are accepted only up to the allowed number of tries; after that, even the right code is refused.
- Changing an account's number (the app's Settings โ phone number) is closed by default, and the app shows an error. With Allow changing numbers on, the code goes to the new number by SMS only, whatever the delivery method (the Sign-up codes tab does not show it, even with codes shown โ whoever is handed it there may not hold the number), with a text of its own, the SMS settings' change-number text (๋ฒํธ ๋ฐ๊พธ๊ธฐ ๋ฌธ๊ตฌ; default
[ํ๋ฐ] ๋ฒํธ ๋ฐ๊พธ๊ธฐ ํ์ธ ์ฝ๋: {code}). The resend interval and the daily maximum apply to the new number and to the account; blocked and suspended numbers, and numbers another account has, are refused both when the code is asked for and when the account moves. A code fits only that account and that number and is used once; someone else's sign-in code waiting for the number is left as it is. After the move, the old number no longer signs in to the account.
Settings โ sign-up and sign-in
| Setting | Meaning | Default |
|---|---|---|
| Allow new sign-ups (์ ๊ฐ์ ํ์ฉ) | When off, only existing accounts can sign in. New numbers are rejected as an "invalid number" | On |
| Code delivery method (์ฝ๋ ์ ๋ฌ ๋ฐฉ์) | Admin page / SMS / Email | Admin page |
| Show codes on the admin page (๊ด๋ฆฌ ํ๋ฉด์ ์ฝ๋ ํ์) | Shows codes in the Sign-up codes tab even with the SMS and email methods (in case sending fails) | On |
| Test numbers (+99966โฆ) (ํ ์คํธ ๋ฒํธ) | As in server config (์๋ฒ ์ค์ ๋๋ก) / On (์ผ๊ธฐ) / Off (๋๊ธฐ). Off in production | As in server config |
| Code length (์ฝ๋ ์๋ฆฟ์) ยท validity (์ฝ๋ ์ ํจ ์๊ฐ (๋ถ)) | 5โ6 digits ยท 1โ60 minutes | 5 digits ยท 5 minutes |
| Resend interval (์ฌ์์ฒญ ๊ฐ๊ฒฉ (์ด)) ยท daily maximum (๋ฒํธ๋น ํ๋ฃจ ์ต๋ ์์ฒญ) | Seconds before the same number can get another code (0โ3600) ยท maximum number of times within 24 hours (1โ1000) | 60 seconds ยท 10 times |
| Allowed wrong attempts (ํ๋ฆฐ ์ ๋ ฅ ํ์ฉ ํ์) | Beyond this, the code is locked (1โ20) | 5 times |
| Allow changing numbers (๋ฒํธ ๋ฐ๊พธ๊ธฐ ํ์ฉ) | A signed-in account can move to another number with the app's "Change number". Can be turned on only once SMS is set up | Off |
Settings โ SMS
| Provider (์ ๊ณต์) | Values to enter | Notes |
|---|---|---|
| Webhook (์นํ ) | The receiving address (https://โฆ) (์นํ ์ฃผ์), an Authorization header (optional) | The server sends POST {"phone":"+8210โฆ","code":"12345","text":"โฆ"}. A 2xx means success. For connecting your own SMS server or another service |
| Twilio | Account SID, Auth Token, a sender number (๋ฐ์ ๋ฒํธ) or a Messaging Service SID | Worldwide, including numbers outside Korea |
| Solapi (formerly CoolSMS) | API Key, API Secret, sender number | Domestic Korean text messages. The sender number must be registered in advance with Solapi. +82 numbers are sent in the 010โฆ format |
The message text (๋ฌธ๊ตฌ) can include {code} (the code) and {minutes} (how long it's valid). Default: [ํ๋ฐ] ์ธ์ฆ ์ฝ๋: {code} ("[Pabal] Verification code: {code}"). After saving (์ ์ฅ), check it with Send test (ํ
์คํธ ๋ฐ์ก).
Settings โ email (SMTP)
| Service | Server (SMTP ์๋ฒ) ยท port (ํฌํธ) ยท security (๋ณด์) | Username (์ฌ์ฉ์๋ช ) ยท password (๋น๋ฐ๋ฒํธ) |
|---|---|---|
| Gmail | smtp.gmail.com ยท 587 ยท STARTTLS | Your Gmail address ยท an app password (Google Account โ Security โ 2-Step Verification โ App passwords) |
| Naver | smtp.naver.com ยท 587 ยท STARTTLS | Naver ID ยท password (turn on POP3/SMTP in the mail settings) |
| In-house mail relay | Relay address ยท 25 ยท none (์์) | Leave empty |
The sender address (๋ณด๋ด๋ ์ฃผ์) must be one the SMTP account is allowed to send from. You can use {code} and {minutes} in the subject (์ ๋ชฉ) and body (๋ณธ๋ฌธ) as well.
Managing users
- Sign out all devices (๋ชจ๋ ๊ธฐ๊ธฐ ๋ก๊ทธ์์): cuts off every sign-in (auth key) of that account. Use it for users who have lost a device.
- Block number (๋ฒํธ ์ฐจ๋จ): that number can no longer receive codes, and its account is immediately signed out on every device. It's the same as the Block phone numbers (์ ํ๋ฒํธ ์ฐจ๋จ) list in the Settings (์ค์ ) tab.
- Sign-in email (๋ก๊ทธ์ธ ์ด๋ฉ์ผ): needed for an existing account to sign in by email when the email method is used.
- Delete account (๊ณ์ ์ญ์ ): for a user who asked to have their account deleted, once you have confirmed it is them. It deletes exactly what the app's own account deletion does, and cannot be undone. It runs only after you type the account's id or phone number again and leave a note of how the request came and how you confirmed it. The note is kept with the id and the date (phone numbers, email addresses and the account's name and username are removed from it). For a request by email from an address other than the account's sign-in email, use Send verification code by text (ํ์ธ ์ฝ๋ ๋ฌธ์ ๋ณด๋ด๊ธฐ) in the same dialog to send a code to the account's number, then enter the code the person sends back by email. Bots and the server's own accounts cannot be deleted here.
- @BotFather is a bot inside the server, so it can't be signed out.
Managing bots
The Bots (๋ด) tab shows how each bot connects โ whether it's connected over MTProto, whether it called getUpdates over HTTP within the last minute, and what its webhook address is and whether it's currently failing (with the reason). If the number of waiting updates keeps growing, the bot program has stopped or its webhook is failing. Deleting a bot or issuing a new token is done by the bot's creator at @BotFather.
The voice tab
This is where you run voice bots (bots that people can call and that an AI voice model answers; see Voice bots). The tab is shown even when voice is off on the server, and the status area tells you what is missing. How to turn it on is in Server installation โ Voice bots. It refreshes every 5 seconds.
| Area | What you see and can do |
|---|---|
| Figures at the top | Calls in progress ยท time used today (the server's daily limit, time reserved by calls in progress) ยท connected gateways ยท bots with voice on (allowed bots) ยท testers (whether voice bots are public yet) |
| Status | The gateway link (its address, or "Off (no link secret)" (๊บผ์ง (๋งํฌ ๋น๋ฐ ์์))), the call relay (TURN; if missing, "Every bot call is busy" (๋ชจ๋ ๋ด ํตํ๊ฐ ํตํ ์ค)), whether the privacy policy has the bot-calls section (if not, "No bot takes calls" (์ด๋ค ๋ด๋ ๋ฐ์ง ์์)), pending consents, and the number of calls ended with a busy signal, by reason (for example ์์น๊ฐ ๋ชป ๊ฑฐ๋ ๋ชจ๋ ("a mode the watch can't call") โ a call from the Apple Watch to a bot that uses tools, MCP, an own key, stream mode or the owner's transcripts, WATCH_NOT_YET) |
| Kill switch | Stop all voice calls (๋ชจ๋ ์์ฑ ํตํ ๋ฉ์ถ๊ธฐ): the moment you press it, it takes effect on this server โ every bot call in progress ends, no new calls are taken, and no bot shows a call button. It is then saved, so it stays in effect after the server restarts (a warning appears if saving fails). Lift it with Reopen voice calls (์์ฑ ํตํ ๋ค์ ์ด๊ธฐ) |
| Launch | Before public launch, only the testers below can call voice bots. To open to everyone, type ๊ณต๊ฐํฉ๋๋ค ("I am launching this") exactly into the field and press Launch to everyone (๋ชจ๋์๊ฒ ๊ณต๊ฐ) (refused if the server's recipient key TELEGRAM_VOICE_RECIPIENTS is โฆ@unset, with no processing region set โ you must first release an app build that includes the region). Back to testers only (์ํ์๋ง์ผ๋ก ๋๋๋ฆฌ๊ธฐ) undoes it. Finishing the legal review and getting the provider's (Google's) written answer before public launch is the operator's decision โ the code does not stop you |
| Gateways | For each connected gateway: id, calls handled/capacity, where it sends audio (google.vertex.gemini-live@us-central1 and so on), features (toolsยทbyokยทtranscriptsยทstream โฆ), engine, boot |
| Transcripts ยท own keys | Allowing transcripts: type ๋ํ ๊ธฐ๋ก์ ํ์ฉํฉ๋๋ค ("I allow transcripts") into the field and press Allow transcripts (๋ํ ๊ธฐ๋ก ํ์ฉ). Do not allow them before a legal review. Turning them off takes effect at once, and also discards the text of calls in progress and transcripts the bots have not yet picked up. Keeping the conversation, which the caller chooses, is unrelated to this allowance and has no switch on the admin page โ the privacy policy's bot-calls-keep-text section turns it on (Server installation โ Keeping the conversation). Own keys: whether they are accepted and why not, the master key id, the number of bots with a key (the keys themselves appear nowhere) |
| Calls in progress | Call id, bot, caller (name and id), state, elapsed/maximum, gateway, number of open tool queries. Hang up (๋๊ธฐ) (runs only when clicked twice) |
| Voice bots | Which bots can use voice: Allowed bots only (ํ์ฉํ ๋ด๋ง) (the default) or All bots made with @BotFather (@BotFather ๋ก ๋ง๋ ๋ชจ๋ ๋ด). Allow (ํ์ฉ) by bot id or @username. The table: bot, creator, state (on/off, whether it can take calls and why, suspended, the stream address audio goes to in stream mode, tools and the number of answers, errors and timeouts), voice and language, limits, today's calls. Actions: Allow (ํ์ฉ) ยท Disallow (ํ์ฉ ์ทจ์), Review and allow again (ํ์ธํ๊ณ ๋ค์ ํ์ฉ), Suspend (๋ฉ์ถ๊ธฐ) (reason required, 200 characters, shown to the bot owner, calls in progress end) ยท Reopen (๋ค์ ์ด๊ธฐ), View settings (์ค์ ๋ณด๊ธฐ) |
| Viewing a bot's settings | The full instructions and greeting (for review), the tool definitions and each tool's response times (p50 ยท p95), where tool queries go, the version of the rules accepted, impersonation checks applied or exempted, recent changes, and per-month calls, minutes, missed and refused |
| Testers | Add as tester (์ํ์๋ก ๋ฃ๊ธฐ) ยท Remove (๋นผ๊ธฐ) by a person's id or @username (with or without the @, case-insensitive) (not bots or the server's own accounts). People added with the server setting TELEGRAM_VOICE_TESTERS are shown as "Server" (์๋ฒ) and cannot be removed here |
| Limits | The table below + Names to add to the impersonation check (์ฌ์นญ ๊ฒ์ฌ์ ๋ํ ์ด๋ฆ) (one per line). When you save, it tells you which bots can no longer take calls |
| Calls from bots | The global switch: type ๋ด์ด ๊ฑฐ๋ ์ ํ๋ฅผ ์ผญ๋๋ค exactly in the box and turn it on โ only after the legal review (refused unless the permission sheet's callme.legal_review is done; the exception for development and test servers is TELEGRAM_VOICE_CALLME_UNREVIEWED). Turned off, new requests get 503 at once. Countries (82 by default, 999 on test servers only), limits (a production server never stores them looser than the floors), the table of bots whose owners turned it on (review state, token freeze, automatic stop, today's requests), and a bot's detail (name, @username, photo, instructions, greeting, tools, voice, language, mode, purposes, the owner's name and its outboundReviewHash) with AllowยทTake back, Lift for an automatic stop, and refusal counts per judgement stage |
Allowing is a review. In "allowed bots only" mode, allowing a bot allows its instructions, greeting and tools (in stream mode, its stream address) as they are now. If the bot owner changes any of them, the bot table shows "Instructions or greeting changed โ takes calls only after you review and allow it again" (์ง์๋ฌธยท์ธ์ฌ๋ง์ด ๋ฐ๋ โ ํ์ธํ๊ณ ๋ค์ ํ์ฉํด์ผ ์ ํ๋ฅผ ๋ฐ์ต๋๋ค), and the bot takes no calls until you press Review and allow again (ํ์ธํ๊ณ ๋ค์ ํ์ฉ). A bot whose language is neither Korean nor English is treated the same way even in "All bots made with @BotFather" mode, because the automatic rule check at save time reads only Korean and English. For such a bot the table shows "Not a Korean or English bot (the automatic rule check cannot read it): please read the instructions and greeting โ takes calls only after you review and allow it again" (ํ๊ตญ์ดยท์์ด๊ฐ ์๋ ๋ด (์๋ ๊ท์น ๊ฒ์ฌ๊ฐ ์ฝ์ง ๋ชปํจ): ์ง์๋ฌธยท์ธ์ฌ๋ง์ ์ฝ์ด ์ฃผ์ธ์ โ ํ์ธํ๊ณ ๋ค์ ํ์ฉํด์ผ ์ ํ๋ฅผ ๋ฐ์ต๋๋ค). Have someone who reads that language read the instructions and greeting, then allow the bot.
| Limit | Default | Range |
|---|---|---|
| Longest call (seconds) | 600 | 60โ3600, up to the server setting TELEGRAM_VOICE_MAX_SECONDS |
| Concurrent calls per bot | 2 | 1โ100, up to the server setting TELEGRAM_VOICE_BOT_CONCURRENCY |
| Calls per person per hour | 20 | 1โ200 |
| Call minutes per person per day | 60 | 1โ600 |
| Call minutes per bot per day | 60 | 1โ6000 |
| Call minutes per bot owner per day | 120 | 1โ6000 |
| Call minutes for the whole server per day | 300 | 1โ100000 |
| Shortest call taken (seconds) | 60 | 30โ300 โ the call is not taken if less time than this is left |
| New-account restriction (days) | 7 | 0โ90 (0 = off) โ testers, and people the bot sent a message to more than a day ago, are exempt |
- A day follows the server's time zone (
TELEGRAM_VOICE_TIME_ZONE, defaultAsia/Seoul). When a call is taken, its maximum length is set within the time left that day and reserved on every limit; when it ends, only the actual seconds by the server's clock are charged. Calls taken with a bot owner's own key use only the caller's limits. - Bot owners can set their bot's call length (default 300 seconds) and concurrent calls only within these limits.
- Every action (kill switch, launch, allowing, suspending, hanging up, limits โฆ) is recorded in the Sign-up codes tab's activity log as
์์ฑ: โฆ("Voice: โฆ"). - Voice-call quality figures (time to first audio, time to stop on barge-in, tool answer times and so on) are not in this tab but only at
/metricson the management port (names starting withvoice.in JSON, orvoice_in the Prometheus format at/metrics?format=prometheus). They are totals only, with no people, bots or words.
You can do the same through the admin API (all with Authorization: Bearer <token>): GET /admin/api/voice, GET /admin/api/voice/bots/<id>, and with POST: voice/kill {"on": true}, voice/launch {"launched": true, "confirm": "โฆ"}, voice/transcripts {"allowed": true, "confirm": "โฆ"}, voice/testersยทvoice/testers/remove {"userId": โฆ}, voice/gate {"botGate": "ALLOWLIST"}, voice/bots/allowยทvoice/bots/disallow {"botId": โฆ}, voice/bots/<id>/suspend {"reason": "โฆ"}ยทโฆ/liftยทโฆ/name-exempt, voice/calls/<id>/end, and limits at /admin/api/settings/voice. Calls from bots: voice/outbound {"on": true, "confirm": "โฆ"}, voice/outbound/settings {"countries": [โฆ], "limits": {โฆ}}, GET /admin/api/voice/outbound/bots/<id>, voice/outbound/bots/allow {"botId": โฆ, "expect": "<outboundReviewHash>"}ยทvoice/outbound/bots/disallow, voice/outbound/bots/<id>/lift.
Environment variables
The server reads its settings file (server-config.json) and then overrides it with environment variables. The production compose file sets the values below, so usually you only need to edit .env.
| Variable | Meaning | Production compose value |
|---|---|---|
TELEGRAM_PORT | MTProto port | MTPROTO_PORT (8443) |
TELEGRAM_HOST | Address MTProto listens on | 0.0.0.0 |
TELEGRAM_PUBLIC_HOST | Server address announced to the app (help.getConfig) | PUBLIC_IP |
TELEGRAM_WEB_PORT | Port for the website, docs, Bot API and admin page | 8080 |
TELEGRAM_WEB_HOST | Address that port listens on. Default 127.0.0.1 | 0.0.0.0 (inside the container; exposed on the host only as 127.0.0.1) |
TELEGRAM_PUBLIC_URL | Website address. Used for links (me_url_prefix), invite links, examples in the docs and preview images | https://DOMAIN/ |
TELEGRAM_DATA_DIR | Location of photos, files, admin-token and operations.json | /app/data |
TELEGRAM_RSA_KEY | Path of the server's RSA private key (created on first start if missing; the public key is .pub) | /app/keys/private.pem |
TELEGRAM_DC_ID | This server's DC number. It must be the DC the apps use as their home (2 for every Telegram app) โ otherwise photo and file downloads never start | DC_ID (2) |
TELEGRAM_DB_TYPE | memory ยท h2 ยท postgresql | postgresql |
TELEGRAM_DB_URL, TELEGRAM_DB_USERNAME, TELEGRAM_DB_PASSWORD | JDBC connection | jdbc:postgresql://pabal-postgres:5432/pabal, pabal, POSTGRES_PASSWORD |
TELEGRAM_DB_MAX_POOL_SIZE | Number of DB connections | 20 |
TELEGRAM_ADMIN_TOKEN | Admin page token (if empty, created in data/admin-token) | ADMIN_TOKEN |
TELEGRAM_MAX_FILE_SIZE | The largest file anyone may send (2GB ยท 512MB ยท a plain number of bytes). Parts are written to disk as they arrive, so this bounds the disk, not the memory | MAX_FILE_SIZE (2GB) |
TELEGRAM_TEST_NUMBERS | +99966โฆ test numbers. Development only | false |
TELEGRAM_WEBHOOK_ALLOW_LOCAL | Lets bot webhooks use http:// and internal addresses. Development only | (not set = false) |
TELEGRAM_FCM_CREDENTIALS | Path of the Firebase service account key file for Android push. If empty, push is off (Server installation โ step 10) | FCM_CREDENTIALS (empty) |
TELEGRAM_FCM_PROJECT_ID | Only when sending to a Firebase project other than the key file's project_id | FCM_PROJECT_ID (empty) |
TELEGRAM_PUSH_PREVIEWS | With false, push notifications show only the sender, without the message text | PUSH_PREVIEWS (true) |
TELEGRAM_TURN_HOST, TELEGRAM_TURN_PORT, TELEGRAM_TURN_SECRET | Call relay (TURN) โ where two apps on different networks meet. With no secret, calls are direct only (Server installation โ Call relay) | TURN_HOST (empty: DOMAIN), TURN_PORT (3478), TURN_SECRET (empty) |
TELEGRAM_APNS_KEY, TELEGRAM_APNS_KEY_ID, TELEGRAM_APNS_TEAM_ID, TELEGRAM_APNS_TOPIC, TELEGRAM_APNS_MIN_BUILD, TELEGRAM_APNS_VOIP_MIN_BUILD | iPhone push (APNs) โ the key file's path, the key ID (empty: from the file name), the team that signed the app, the bundle ID, the lowest build to send to, the first build to get the CallKit call screen. Without a key, iPhone push is off (Server installation โ step 11) | APNS_* (no key, empty โ from the file name, 4Z8R468M2D, me.pabal.ios, 4, empty) |
TELEGRAM_STATIC_MAPS_KEY, TELEGRAM_STATIC_MAPS_PER_DAY, TELEGRAM_STATIC_MAPS_PER_USER_HOUR | Map pictures in location messages โ the Google Maps Static API key, new pictures fetched a day, new pictures one person may cause an hour. Without a key, map pictures are off (Server installation โ Map pictures) | STATIC_MAPS_* (no key, 300, 120) |
JAVA_OPTS | JVM options (memory) | The value from .env |
Environment variables โ voice bots
In production, the add-on file deploy/docker-compose.voice.yml sets the link, relay, time zone, limits, testers and minimum builds among them from .env values; the rest are added feature by feature after their own review (Server installation โ Voice bots). The gateway's (pabal-voice) VOICE_* settings are in the repository's voice/README.md.
| Variable | Default | Meaning |
|---|---|---|
TELEGRAM_VOICE_LINK_SECRET_FILE | โ | The 32-byte link secret shared with the gateway (64 hex digits). Missing or wrong: voice off |
TELEGRAM_VOICE_LINK_HOST, TELEGRAM_VOICE_LINK_PORT | 127.0.0.1, 8095 | Where the link listens. A non-loopback address needs TELEGRAM_VOICE_LINK_ANY_ADDRESS=true; public addresses are always refused |
TELEGRAM_VOICE_LINK_ANY_ADDRESS | โ | Declares "only the server and the gateway use this network" |
TELEGRAM_VOICE_TURN_HOST, TELEGRAM_VOICE_TURN_PORT | โ | The relay address to give the gateway (the server's LAN address). Ignored if loopback |
TELEGRAM_VOICE_MAX_SECONDS | 600 | The longest call this server allows (60โ3600) |
TELEGRAM_VOICE_BOT_CONCURRENCY | 2 | The cap on concurrent calls per bot (the operator and bot owners can only lower it) |
TELEGRAM_VOICE_TIME_ZONE | Asia/Seoul | What daily and monthly limits are based on |
TELEGRAM_VOICE_TESTERS | โ | A list of tester user ids (combined with the admin page's list). * only on test servers |
TELEGRAM_VOICE_RECIPIENTS | google.vertex.gemini-live@us-central1 | The recipient the apps' confirmation sheet names. Must equal the gateway's for calls to go through |
TELEGRAM_VOICE_MIN_BUILD_DESKTOP, _ANDROID, _IOS, _WATCHOS | 1, 62151, 5, 1 | The first app builds that really contain the confirmation sheet (for desktop, the number after the tag pabal-bc). When the recipient changes, release new builds and raise these. For desktop, raise it to 2 when you first approve a confirmation sheet that is neither Korean nor English |
TELEGRAM_VOICE_MIN_SHEET_VERSION | 1 | The lowest confirmation-sheet version accepted |
TELEGRAM_VOICE_TOOL_RESULT_BYTES | 16384 | The largest tool answer (1024โ16384) |
TELEGRAM_VOICE_KEYS_MASTER_FILE, TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE | โ | The master key that wraps own keys and stream secrets, and the old key during rotation. In a 700 folder outside the backups |
TELEGRAM_VOICE_OWN_KEYS_APPS_READY | โ | true only after the apps show the own-key bot confirmation sheet (version 2). Until then, own keys are not accepted |
TELEGRAM_VOICE_OWN_KEY_MIN_SHEET_VERSION, TELEGRAM_VOICE_TRANSCRIPT_MIN_SHEET_VERSION | 2, 1 | The confirmation-sheet versions needed for own-key calls and the transcript notice (a production server never lowers the own-key version below 2) |
TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP, _ANDROID, _IOS | 0 (none) | The first app builds with the stream confirmation sheet. At 0, nobody can call stream bots |
TELEGRAM_VOICE_STREAM_MIN_SHEET_VERSION | 2 | The stream confirmation sheet's version |
TELEGRAM_VOICE_STREAM_DENY_ADDRESSES | โ | This deployment's addresses that stream addresses must not reach (the public host and the site host are included automatically) |
TELEGRAM_WATCH_API_PORT, _HOST, _ANY_ADDRESS, TELEGRAM_WATCH_AUDIO_URL, TELEGRAM_WATCH_ALLOW_PLAIN, TELEGRAM_WATCH_SESSION_DAYS, TELEGRAM_WATCH_AUTH_PER_MINUTE | โ, 127.0.0.1, โ, โ, โ, 7, 30 | The Apple Watch API and audio edge addresses (Server installation โ Apple Watch entry points). ALLOW_PLAIN is for development only |
TELEGRAM_VOICE_EVIDENCE_KEY_DIR | โ | The folder of the key the records of permissions for calls from bots (HMAC evidence) are made with. 700, outside the backed-up folders. Made and replaced with scripts/voice-evidence-key.py. Without it nobody can turn a permission on. With Docker Compose it does not reach the server from .env alone โ when you turn calls from bots on, add it to pabal-server in deploy/docker-compose.voice.yml, under environment (the path inside the container) and volumes (the folder on the host) |
TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_DESKTOP, _ANDROID, _IOS, _WATCHOS | 0 (none) | The first app build with the permission sheet. Older builds cannot turn a permission on, and bots' calls do not ring them. 0 means that platform has none |
TELEGRAM_VOICE_CALLME_DEVICE_AGE_SECONDS | 86400 | How long after sign-in a device may turn a permission on (seconds). Test servers only may change it (a production server warns and ignores it) |
TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED | โ | Shows the conversation-keeping checkbox wording on the watch before its legal review. Internal test servers only |
TELEGRAM_MCP_*, TELEGRAM_MCP_APPS_READY | โ | The MCP components (set by deploy/docker-compose.mcp.yml). TELEGRAM_MCP_EGRESS_OWN_ADDRESSES (Pabal's public addresses) is used even without MCP, to keep webhook and tool requests from going to Pabal itself |
TELEGRAM_VOICE_BOTS, TELEGRAM_VOICE_NOTICE_CHECK=off, TELEGRAM_VOICE_STREAM_ALLOW_PRIVATE | โ | Test servers only (only with TELEGRAM_TEST_NUMBERS=true): allow the listed bots (ids or @names, * for every bot) ยท skip the legal-section check ยท private addresses in stream addresses |
TELEGRAM_VOICE_CALLME_UNREVIEWED, TELEGRAM_VOICE_DEV_ECHO_OUTBOUND, TELEGRAM_VOICE_TEST_DIAL, TELEGRAM_VOICE_TEST_DIAL_USERS | โ | Test and development servers only: the switch for calls from bots may be turned on before the legal review (development and test servers) ยท an echo gateway places bots' calls (development servers, TELEGRAM_TEST_MODE) ยท the admin API's test dial and the accounts it may ring (up to 20 besides the +99966 test numbers, test servers). A production server warns and ignores them |
Data files
| File | Contents | Permissions |
|---|---|---|
keys/private.pem | The server's RSA private key. Never let it leave the server | 600 |
keys/private.pem.pub | The public key. Used for building the app and for /docs/server-key.pem | โ |
keys/firebase-service-account.json | (Optional) The Firebase secret key for Android push. If it leaks, delete it in the Firebase console and download a new one | 600 |
data/admin-token | The admin page token | 600 |
data/operations.json | The values from the Settings (์ค์ ) tab โ sign-up rules, SMS and SMTP secrets, blocked numbers โ and the voice tab's settings (kill switch, launch, testers, allowed bots, limits) | 600 |
keys/voice-link.secret | (Voice bots) The link secret shared with the gateway | 400 |
/etc/pabal/voice/voice-master.key | (Voice bots) The master key that wraps bot owners' own keys and stream secrets. Outside the data folder, not in the backups | 600 (folder 700) |
data/media/ | Original photos and files | โ |
PostgreSQL events table | Accounts, conversations, messages, sign-ins, bots, webhook settings โ the record of every change | โ |
Security notes
- Don't expose the management port directly to the internet. In the production setup, Caddy blocks management paths such as
/admin,/healthand/metricswith a 404. - Every admin data request needs
Authorization: Bearer <token>, and pages on other sites can't read the responses. - SMS and SMTP secrets exist only in
operations.json; the screen and the API only tell you that a value is "saved" (์ ์ฅ๋จ). If you save with a secret field left empty, the existing value is kept. - Tokens and secrets are never written to the logs, and phone numbers in the logs are masked. Bot API addresses (which include the token) aren't logged either.
- Device tokens for push notifications appear in the logs only as fingerprints, and neither the contents of the Firebase key file nor the contents of notifications are logged.
- The auth keys are in the DB, so anyone who can access the DB can decrypt users' traffic. Guard the DB and backups as carefully as the RSA key.
Limitations
- Pending codes and recent activity are held in memory, so they're lost when the server restarts (people just request a code again in the app).
- Actual delivery through an SMS provider has to be verified with each provider's account. The server builds its requests as described in each provider's documentation.
- Not available yet: force-deleting bots, reading messages, viewing logs, graphs, push notification statistics (check push in the server log).