Developer docs
English

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

TabContentsRefresh
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 on5 seconds
Sign-up codes (๊ฐ€์ž… ์ฝ”๋“œ)Pending codes (number, delivery method, delivery status, code, time left, wrong attempts) and recent activity. Copy (๋ณต์‚ฌ) or cancel (์ทจ์†Œ) a code3 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 updates10 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 name10 seconds
Settings (์„ค์ •)Sign-up and sign-in rules, the app-review account, SMS, email (SMTP), blocked phone numbers, the operator details shown on the websiteSaved 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 themOnce, when opened
Storage (์ €์žฅ์†Œ)Data directory and DB address (password hidden), number and total size of photos and files, number of stored streams by kind10 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 methodWhere the code goesApp screens
Admin page (๊ด€๋ฆฌ ํ™”๋ฉด)The Sign-up codes (๊ฐ€์ž… ์ฝ”๋“œ) tab. The operator copies it and passes it on personallyNumber โ†’ "code sent" โ†’ code (name, for a new number)
SMSA text message through one of Twilio ยท Solapi ยท webhookSame as the admin page
Email (์ด๋ฉ”์ผ)An email over SMTPNumber โ†’ 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

SettingMeaningDefault
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 / EmailAdmin 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 productionAs in server config
Code length (์ฝ”๋“œ ์ž๋ฆฟ์ˆ˜) ยท validity (์ฝ”๋“œ ์œ ํšจ ์‹œ๊ฐ„ (๋ถ„))5โ€“6 digits ยท 1โ€“60 minutes5 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 upOff

Settings โ€” SMS

Provider (์ œ๊ณต์ž)Values to enterNotes
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
TwilioAccount SID, Auth Token, a sender number (๋ฐœ์‹  ๋ฒˆํ˜ธ) or a Messaging Service SIDWorldwide, including numbers outside Korea
Solapi (formerly CoolSMS)API Key, API Secret, sender numberDomestic 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)

ServiceServer (SMTP ์„œ๋ฒ„) ยท port (ํฌํŠธ) ยท security (๋ณด์•ˆ)Username (์‚ฌ์šฉ์ž๋ช…) ยท password (๋น„๋ฐ€๋ฒˆํ˜ธ)
Gmailsmtp.gmail.com ยท 587 ยท STARTTLSYour Gmail address ยท an app password (Google Account โ†’ Security โ†’ 2-Step Verification โ†’ App passwords)
Naversmtp.naver.com ยท 587 ยท STARTTLSNaver ID ยท password (turn on POP3/SMTP in the mail settings)
In-house mail relayRelay 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.

AreaWhat you see and can do
Figures at the topCalls 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)
StatusThe 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 switchStop 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 (์Œ์„ฑ ํ†ตํ™” ๋‹ค์‹œ ์—ด๊ธฐ)
LaunchBefore 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
GatewaysFor 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 keysAllowing 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 progressCall id, bot, caller (name and id), state, elapsed/maximum, gateway, number of open tool queries. Hang up (๋Š๊ธฐ) (runs only when clicked twice)
Voice botsWhich 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 settingsThe 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
TestersAdd 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
LimitsThe 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 botsThe 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.

LimitDefaultRange
Longest call (seconds)60060โ€“3600, up to the server setting TELEGRAM_VOICE_MAX_SECONDS
Concurrent calls per bot21โ€“100, up to the server setting TELEGRAM_VOICE_BOT_CONCURRENCY
Calls per person per hour201โ€“200
Call minutes per person per day601โ€“600
Call minutes per bot per day601โ€“6000
Call minutes per bot owner per day1201โ€“6000
Call minutes for the whole server per day3001โ€“100000
Shortest call taken (seconds)6030โ€“300 โ€” the call is not taken if less time than this is left
New-account restriction (days)70โ€“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, default Asia/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 /metrics on the management port (names starting with voice. in JSON, or voice_ 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.

VariableMeaningProduction compose value
TELEGRAM_PORTMTProto portMTPROTO_PORT (8443)
TELEGRAM_HOSTAddress MTProto listens on0.0.0.0
TELEGRAM_PUBLIC_HOSTServer address announced to the app (help.getConfig)PUBLIC_IP
TELEGRAM_WEB_PORTPort for the website, docs, Bot API and admin page8080
TELEGRAM_WEB_HOSTAddress that port listens on. Default 127.0.0.10.0.0.0 (inside the container; exposed on the host only as 127.0.0.1)
TELEGRAM_PUBLIC_URLWebsite address. Used for links (me_url_prefix), invite links, examples in the docs and preview imageshttps://DOMAIN/
TELEGRAM_DATA_DIRLocation of photos, files, admin-token and operations.json/app/data
TELEGRAM_RSA_KEYPath of the server's RSA private key (created on first start if missing; the public key is .pub)/app/keys/private.pem
TELEGRAM_DC_IDThis 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 startDC_ID (2)
TELEGRAM_DB_TYPEmemory ยท h2 ยท postgresqlpostgresql
TELEGRAM_DB_URL, TELEGRAM_DB_USERNAME, TELEGRAM_DB_PASSWORDJDBC connectionjdbc:postgresql://pabal-postgres:5432/pabal, pabal, POSTGRES_PASSWORD
TELEGRAM_DB_MAX_POOL_SIZENumber of DB connections20
TELEGRAM_ADMIN_TOKENAdmin page token (if empty, created in data/admin-token)ADMIN_TOKEN
TELEGRAM_MAX_FILE_SIZEThe 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 memoryMAX_FILE_SIZE (2GB)
TELEGRAM_TEST_NUMBERS+99966โ€ฆ test numbers. Development onlyfalse
TELEGRAM_WEBHOOK_ALLOW_LOCALLets bot webhooks use http:// and internal addresses. Development only(not set = false)
TELEGRAM_FCM_CREDENTIALSPath 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_IDOnly when sending to a Firebase project other than the key file's project_idFCM_PROJECT_ID (empty)
TELEGRAM_PUSH_PREVIEWSWith false, push notifications show only the sender, without the message textPUSH_PREVIEWS (true)
TELEGRAM_TURN_HOST, TELEGRAM_TURN_PORT, TELEGRAM_TURN_SECRETCall 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_BUILDiPhone 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_HOURMap 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_OPTSJVM 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.

VariableDefaultMeaning
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_PORT127.0.0.1, 8095Where 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_SECONDS600The longest call this server allows (60โ€“3600)
TELEGRAM_VOICE_BOT_CONCURRENCY2The cap on concurrent calls per bot (the operator and bot owners can only lower it)
TELEGRAM_VOICE_TIME_ZONEAsia/SeoulWhat 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_RECIPIENTSgoogle.vertex.gemini-live@us-central1The recipient the apps' confirmation sheet names. Must equal the gateway's for calls to go through
TELEGRAM_VOICE_MIN_BUILD_DESKTOP, _ANDROID, _IOS, _WATCHOS1, 62151, 5, 1The 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_VERSION1The lowest confirmation-sheet version accepted
TELEGRAM_VOICE_TOOL_RESULT_BYTES16384The 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_VERSION2, 1The 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, _IOS0 (none)The first app builds with the stream confirmation sheet. At 0, nobody can call stream bots
TELEGRAM_VOICE_STREAM_MIN_SHEET_VERSION2The 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, 30The 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, _WATCHOS0 (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_SECONDS86400How 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

FileContentsPermissions
keys/private.pemThe server's RSA private key. Never let it leave the server600
keys/private.pem.pubThe 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 one600
data/admin-tokenThe admin page token600
data/operations.jsonThe 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 gateway400
/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 backups600 (folder 700)
data/media/Original photos and filesโ€”
PostgreSQL events tableAccounts, 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, /health and /metrics with 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).