서버 운영
관리와 설정
관리 화면, 가입 코드 전달(SMS·이메일), 서버 설정 값, 백업과 보안 — 운영자를 위한 참조.
서버에는 브라우저로 여는 관리 화면이 들어 있습니다. 서버 상태를 보고, 가입·로그인 코드를 어떻게 전달할지 정하고, 사용자와 봇을 관리합니다. 이 문서는 관리 화면과 서버 설정 값을 한곳에 정리한 참조입니다. 설치가 먼저라면 서버 설치를 보세요.
관리 화면 열기
관리 화면은 관리 HTTP 포트(운영 구성에서 8080)의 /admin/ 이고, 서버 자신(127.0.0.1)에서만 열립니다. 운영자는 SSH 터널로 들어갑니다.
# 내 컴퓨터에서 (켜 둔 채로)
ssh -N -L 8080:127.0.0.1:8080 <사용자>@<서버>
# 브라우저: http://localhost:8080/admin/
# 토큰 (서버에서)
sudo cat /srv/pabal/pabal_server/data/admin-token
토큰을 넣고 열기. 브라우저가 토큰을 기억하며, 오른쪽 위 토큰 지우기로 지웁니다. 토큰을 직접 정하려면 TELEGRAM_ADMIN_TOKEN(16자 이상)을 주세요 — 그러면 파일을 쓰지 않습니다.
탭별로 보이는 것
| 탭 | 내용 | 갱신 |
|---|---|---|
| 대시보드 | 지금 접속 중인 사람·세션·연결, 사람·봇·그룹·메시지·사진·파일 수, 가동 시간·포트·DB, JVM, 상태 점검. 테스트 번호가 켜져 있으면 경고 띠 | 5초 |
| 가입 코드 | 기다리는 코드(번호·전달 방식·발송 상태·코드·남은 시간·틀린 입력 횟수)와 최근 기록. 코드 복사·취소 | 3초 |
| 사용자 | 모든 계정 — 전화번호, 로그인 이메일(편집), 접속 여부, 로그인 기기 수, 메시지 수. 모든 기기 로그아웃, 번호 차단, 계정 삭제(본인 확인 메모와 함께 — 지운 계정은 id 와 메모만 남음) | 10초 |
| 봇 | BotFather 로 만든 봇 — 만든 사람, 명령어 메뉴, 연결 방식(MTProto · HTTP 폴링 · 웹훅 주소와 실패 이유), 기다리는 업데이트 수 | 10초 |
| 음성 | 음성 봇 — 게이트웨이·중계·방침의 상태, 차단 스위치, 공개, 대화 기록 허용, 진행 중인 통화와 끊기, 봇 허용·멈추기, 시험자, 한도(아래) | 5초 |
| 신고 | 앱의 "신고"로 들어온 것 — 신고 당시 내용의 사본(보낸 사람이 지운 뒤에도 읽힘). 삭제(대화의 양쪽, 그룹이면 모든 멤버에게서), 계정 정지(모든 기기 로그아웃, 다시 로그인·가입 못 함), 정지된 계정 목록. 정지된 채 탈퇴한 계정의 번호로는 탈퇴 뒤 1년(기본) 새로 가입하지 못함 — 정지할 때 고르거나 목록의 재가입 막기에서 더 늦은 끝 날짜·무기한으로 늘림(1년과 고른 날짜 가운데 늦은 쪽까지 — 줄이지는 못함. 관리 API: POST /admin/api/reports/<id>/suspend {"userId": …, "blockSignupUntil": "2028-01-01"} 또는 "blockSignupForever": true, 뒤에 바꾸기는 POST /admin/api/suspensions/<id>/block-signup, 빈 본문은 기본으로). 처리 끝난 신고도 볼 수 있음. 처리 끝난 신고는 처리 끝난 날부터 3년이 지나면 사본까지 지움(카드에 그 날짜) — 분쟁·수사·소송이 시작되면 보존으로 풀 때까지 남김(관리 API: POST /admin/api/reports/<id>/hold, 풀기는 /release) | 10초 |
| 고객센터 | @PabalSupport 로 온 대화. 대화를 골라 답장하면 고객센터 봇의 이름으로 그 대화에 갑니다 | 10초 |
| 설정 | 가입·로그인 규칙, 앱 심사 계정, SMS, 이메일(SMTP), 전화번호 차단, 웹사이트에 실리는 운영자 정보 | 직접 저장 |
| 예약 아이디 | 아무도 가져갈 수 없게 막아 둔 사용자명 — 검색, 추가, 목록 내려받기(.txt), 서버가 기본으로 막는 아이디. 이미 누가 쓰는 이름은 예약해도 뺏지 않음 | 열 때 한 번 |
| 저장소 | 데이터 디렉터리·DB 주소(비밀번호는 가림), 사진·파일 개수와 용량, 종류별 저장 스트림 수 | 10초 |
위험한 동작(로그아웃·차단·코드 취소)은 두 번 눌러야 실행됩니다.
가입·로그인 코드
사람이 앱에 전화번호를 넣으면 서버가 코드를 만들어 설정 탭에서 고른 방법으로 보냅니다.
| 전달 방식 | 코드가 가는 곳 | 앱 화면 |
|---|---|---|
| 관리 화면 | 가입 코드 탭. 운영자가 복사해 직접 알려 줌 | 번호 → "코드를 보냈습니다" → 코드 (새 번호면 이름) |
| SMS | Twilio · Solapi · 웹훅 중 하나로 문자 | 관리 화면과 같음 |
| 이메일 | SMTP 로 메일 | 번호 → 이메일 입력 → "이메일로 코드를 보냈습니다" → 코드 |
- 이메일 방식에서 새 가입은 아무 주소나 쓸 수 있고, 그 주소가 계정의 로그인 이메일이 됩니다. 기존 계정은 등록된 로그인 이메일로만 받습니다 — 남의 번호에 자기 주소를 넣어 들어오는 것을 막습니다. 로그인 이메일이 없는 기존 계정은 SMS(설정돼 있으면) 또는 관리 화면으로 대신 보냅니다. 사용자 탭에서 로그인 이메일을 넣어 줄 수 있습니다.
- 발송은 뒤에서 이루어져 앱은 바로 코드 화면으로 갑니다. 발송 결과(성공·실패와 이유)는 가입 코드 탭에 뜹니다.
- 가입(이름 입력)은 코드를 맞힌 뒤에만 됩니다. 틀린 코드는 허용 횟수만큼만 받고 그 뒤로는 맞는 코드도 거절합니다.
- 번호 바꾸기(앱의 설정 → 전화번호)는 기본으로 닫혀 있어 앱에 오류가 뜹니다. 번호 바꾸기 허용을 켜면 코드는 전달 방식과 상관없이 새 번호로 SMS 로만 가고(가입 코드 탭에는 코드 표시를 켜도 보이지 않습니다 — 거기서 건네받는 사람이 그 번호의 주인이라는 보장이 없습니다), 문구는 SMS 설정의 번호 바꾸기 문구(기본
[파발] 번호 바꾸기 확인 코드: {code})입니다. 재요청 간격·하루 최대는 새 번호와 계정 모두에 걸리고, 차단·정지된 번호와 다른 계정이 쓰는 번호는 코드를 받을 때와 옮길 때 두 번 거절합니다. 코드는 그 계정·그 번호에만 맞고 한 번만 쓰이며, 그 번호를 기다리는 남의 로그인 코드는 그대로 둡니다. 옮긴 뒤 옛 번호로는 그 계정에 로그인할 수 없습니다.
설정 — 가입·로그인
| 항목 | 뜻 | 기본 |
|---|---|---|
| 새 가입 허용 | 끄면 이미 있는 계정만 로그인. 새 번호는 "잘못된 번호"로 거절 | 켬 |
| 코드 전달 방식 | 관리 화면 / SMS / 이메일 | 관리 화면 |
| 관리 화면에 코드 표시 | SMS·이메일 방식에서도 가입 코드 탭에 코드를 보여 줌 (발송 실패 대비) | 켬 |
| 테스트 번호 (+99966…) | 서버 설정대로 / 켜기 / 끄기. 운영에서는 끄기 | 서버 설정대로 |
| 코드 자릿수 · 유효 시간 | 5~6자리 · 1~60분 | 5자리 · 5분 |
| 재요청 간격 · 하루 최대 | 같은 번호가 코드를 다시 받기까지 초(0~3600) · 24시간 안 최대 횟수(1~1000) | 60초 · 10회 |
| 틀린 입력 허용 횟수 | 넘으면 그 코드는 잠김 (1~20) | 5회 |
| 번호 바꾸기 허용 | 로그인한 계정이 앱의 "번호 바꾸기"로 다른 번호로 옮김. SMS 가 설정돼 있어야 켤 수 있음 | 끔 |
설정 — SMS
| 제공자 | 넣을 값 | 비고 |
|---|---|---|
| 웹훅 | 받을 주소(https://…), Authorization 헤더(선택) | 서버가 POST {"phone":"+8210…","code":"12345","text":"…"} 를 보냅니다. 2xx 면 성공. 자체 문자 서버나 다른 서비스에 연결할 때 |
| Twilio | Account SID, Auth Token, 발신 번호 또는 Messaging Service SID | 해외 번호 포함 전 세계 |
| Solapi (구 CoolSMS) | API Key, API Secret, 발신 번호 | 국내 문자. 발신 번호는 Solapi 에 사전 등록된 번호. +82 번호는 010… 형식으로 보냅니다 |
문구에는 {code}(코드)와 {minutes}(유효 시간)를 넣을 수 있습니다. 기본값: [파발] 인증 코드: {code}. 저장한 뒤 테스트 발송으로 확인하세요.
설정 — 이메일 (SMTP)
| 서비스 | 서버 · 포트 · 보안 | 사용자명 · 비밀번호 |
|---|---|---|
| Gmail | smtp.gmail.com · 587 · STARTTLS | Gmail 주소 · 앱 비밀번호 (Google 계정 → 보안 → 2단계 인증 → 앱 비밀번호) |
| 네이버 | smtp.naver.com · 587 · STARTTLS | 아이디 · 비밀번호 (메일 환경설정에서 POP3/SMTP 사용 켜기) |
| 사내 메일 릴레이 | 릴레이 주소 · 25 · 없음 | 비움 |
보내는 주소는 SMTP 계정이 보낼 수 있는 주소여야 합니다. 제목·본문에도 {code}, {minutes} 를 씁니다.
사용자 관리
- 모든 기기 로그아웃: 그 계정의 모든 로그인(인증 키)을 끊습니다. 기기를 잃어버린 사용자에게 씁니다.
- 번호 차단: 그 번호는 코드를 받을 수 없고, 그 번호의 계정은 모든 기기에서 즉시 로그아웃됩니다. 설정 탭의 전화번호 차단 목록과 같습니다.
- 로그인 이메일: 이메일 방식에서 기존 계정이 이메일로 로그인하려면 필요합니다.
- 계정 삭제: 계정 삭제를 요청한 사용자에게, 본인임을 확인한 뒤 씁니다. 앱의 계정 삭제와 똑같이 지우며 되돌릴 수 없습니다. 계정 id 나 전화번호를 다시 입력하고, 요청이 어디로 왔고 본인을 어떻게 확인했는지 메모를 남겨야 실행됩니다. 메모는 id·날짜와 함께 남습니다(전화번호·메일 주소와 그 계정의 이름·사용자명은 지웁니다). 등록된 로그인 이메일이 아닌 곳에서 온 이메일 요청은, 같은 창의 확인 코드 문자 보내기로 그 계정의 번호에 코드를 보내고, 요청한 사람이 메일로 돌려준 코드를 넣어 확인합니다. 봇과 서버 자체 계정은 여기서 지울 수 없습니다.
- @BotFather 는 서버 안의 봇이라 로그아웃 대상이 아닙니다.
봇 관리
봇 탭에서 각 봇의 연결 방식을 봅니다 — MTProto 로 붙어 있는지, HTTP 로 최근 1분 안에 getUpdates 를 불렀는지, 웹훅 주소가 무엇이고 지금 실패 중인지(이유 포함). 기다리는 업데이트가 계속 늘면 봇 프로그램이 멈췄거나 웹훅이 실패하는 중입니다. 봇 삭제·토큰 재발급은 봇을 만든 사람이 @BotFather 에서 합니다.
음성 탭
음성 봇(사람이 전화를 걸면 AI 음성 모델이 받는 봇, 음성 봇)을 운영하는 곳입니다. 서버에 음성이 켜져 있지 않아도 탭은 보이고, 상태 칸이 무엇이 빠졌는지 알려 줍니다. 켜는 법은 서버 설치 — 음성 봇. 5초마다 새로 고칩니다.
| 칸 | 보이는 것과 할 수 있는 것 |
|---|---|
| 맨 위 숫자 | 진행 중인 통화 · 오늘 쓴 시간(서버 하루 한도, 통화 중 잡아 둔 시간) · 연결된 게이트웨이 · 음성을 켠 봇(허용한 봇) · 시험자(공개 여부) |
| 상태 | 게이트웨이 링크(주소, 또는 "꺼짐 (링크 비밀 없음)"), 통화 중계(TURN, 없으면 "모든 봇 통화가 통화 중"), 개인정보처리방침의 bot-calls 절이 있는지(없으면 "어떤 봇도 받지 않음"), 기다리는 동의, 그리고 통화 중 신호로 끝낸 전화의 이유별 횟수(예: 워치가 못 거는 모드 — 애플 워치로 도구·MCP·자기 키·원음 모드·주인 대화 기록을 쓰는 봇에 건 경우, WATCH_NOT_YET) |
| 차단 스위치 | 모든 음성 통화 멈추기: 누르는 순간 이 서버에서 켜져 진행 중인 봇 통화가 모두 끝나고, 새 통화를 받지 않으며, 어떤 봇에도 통화 버튼이 보이지 않습니다. 그다음 저장되어 서버를 다시 켜도 그대로입니다(저장이 실패하면 경고). 음성 통화 다시 열기로 풉니다 |
| 공개 | 공개 전에는 아래 시험자만 음성 봇에 전화할 수 있습니다. 모두에게 열려면 칸에 공개합니다 를 그대로 입력하고 모두에게 공개(서버의 받는 곳 키 TELEGRAM_VOICE_RECIPIENTS 가 처리 지역을 정하지 않은 …@unset 이면 거부 — 먼저 지역을 넣은 앱 빌드를 내야 합니다). 시험자만으로 되돌리기로 되돌립니다. 공개하기 전에 법률 검토와 제공자(Google)의 서면 답변을 마치는 것은 운영자가 판단할 일입니다 — 코드가 막지 않습니다 |
| 게이트웨이 | 연결된 게이트웨이마다 id, 맡은 통화/용량, 소리를 보내는 곳(google.vertex.gemini-live@us-central1 등), 기능(tools·byok·transcripts·stream …), 엔진, boot |
| 대화 기록 · 자기 키 | 대화 기록 허용: 칸에 대화 기록을 허용합니다 를 입력하고 대화 기록 허용. 법률 검토 전에는 허용하지 마세요. 끄면 곧바로 꺼지고, 진행 중 통화와 봇들이 아직 가져가지 않은 대화 글도 버립니다. 발신자가 고르는 대화 남기기는 이 허용과 상관없고, 관리 화면에 스위치가 없습니다 — 개인정보처리방침의 bot-calls-keep-text 절이 켭니다(서버 설치 — 대화 남기기). 자기 키: 받는지와 받지 않는 이유, 마스터 키 id, 키가 있는 봇 수(키 자체는 어디에도 나오지 않음) |
| 진행 중인 통화 | 통화 id, 봇, 발신자(이름·id), 상태, 경과/최대, 게이트웨이, 열린 도구 질의 수. 끊기(두 번 눌러야 실행) |
| 음성 봇 | 음성을 쓸 수 있는 봇: 허용한 봇만(기본) 또는 @BotFather 로 만든 모든 봇. 봇 id 나 @사용자명으로 허용. 표: 봇, 만든 사람, 상태(켜짐·꺼짐·받을 수 있는지와 이유, 멈춤, 원음 모드면 소리가 가는 주소, 도구와 답·오류·시간 초과 횟수), 목소리·언어, 한도, 오늘 통화. 할 수 있는 것: 허용·허용 취소, 확인하고 다시 허용, 멈추기(사유 필수, 200자, 봇 주인에게 보임, 진행 중 통화가 끝남)·다시 열기, 설정 보기 |
| 봇의 설정 보기 | 지시문·인사말 원문(검토용), 도구 정의와 도구별 응답 시간(p50·p95), 도구 질의가 가는 곳, 규칙 동의 판, 사칭 검사 적용·면제, 최근 바뀐 것, 달마다의 통화·시간·받지 못함·거절 |
| 시험자 | 사람의 id 나 @사용자명(@ 는 있어도 없어도, 대소문자 상관없음)으로 시험자로 넣기·빼기(봇과 서버 자체 계정은 안 됨). 서버 설정 TELEGRAM_VOICE_TESTERS 로 넣은 사람은 "서버" 로 표시되고 여기서 뺄 수 없습니다 |
| 한도 | 아래 표 + 사칭 검사에 더할 이름(한 줄에 하나). 저장하면 이제 통화를 받지 못하게 된 봇을 알려 줍니다 |
| 봇이 거는 전화 | 전역 스위치: 칸에 봇이 거는 전화를 켭니다 를 그대로 입력하고 켭니다 — 법률 검토 뒤에만(허락 창 글의 callme.legal_review 가 done 이 아니면 거절. 개발·시험 서버의 예외는 TELEGRAM_VOICE_CALLME_UNREVIEWED). 끄면 곧바로 새 요청이 503. 나라(기본 82, 999 는 시험 서버만), 한도(운영 서버는 바닥보다 느슨하게 저장되지 않음), 주인이 켠 봇의 표(검토 상태, 토큰 멈춤, 자동 정지, 오늘 요청), 봇 상세(이름·@사용자명·사진·지시문·인사말·도구·목소리·언어·모드·목적·주인 이름과 그 outboundReviewHash)에서 허락·허락 거둠, 자동 정지 풀기, 판정 단계별 거절 횟수 |
허용은 검토입니다. "허용한 봇만" 모드에서 봇을 허용하면 지금의 지시문·인사말·도구(원음 모드면 원음 주소)를 허용한 것이 됩니다. 봇 주인이 그중 하나라도 바꾸면 봇 표에 "지시문·인사말이 바뀜 — 확인하고 다시 허용해야 전화를 받습니다" 가 뜨고, 확인하고 다시 허용을 누를 때까지 그 봇은 전화를 받지 않습니다. 봇 언어가 한국어·영어가 아닌 봇은 "@BotFather 로 만든 모든 봇" 모드에서도 같습니다 — 저장할 때의 자동 규칙 검사가 한국어와 영어만 읽기 때문입니다. 그런 봇의 표에는 "한국어·영어가 아닌 봇 (자동 규칙 검사가 읽지 못함): 지시문·인사말을 읽어 주세요 — 확인하고 다시 허용해야 전화를 받습니다" 가 뜨니, 그 언어를 읽을 수 있는 사람이 지시문·인사말을 읽고 허용하세요.
| 한도 | 기본 | 범위 |
|---|---|---|
| 한 통화 최대 (초) | 600 | 60~3600, 서버 설정 TELEGRAM_VOICE_MAX_SECONDS 까지 |
| 봇 하나의 동시 통화 | 2 | 1~100, 서버 설정 TELEGRAM_VOICE_BOT_CONCURRENCY 까지 |
| 한 사람의 시간당 통화 수 | 20 | 1~200 |
| 한 사람의 하루 통화 (분) | 60 | 1~600 |
| 봇 하나의 하루 통화 (분) | 60 | 1~6000 |
| 봇 주인 한 명의 하루 통화 (분) | 120 | 1~6000 |
| 서버 전체의 하루 통화 (분) | 300 | 1~100000 |
| 가장 짧게 받을 통화 (초) | 60 | 30~300 — 남은 시간이 이보다 적으면 받지 않음 |
| 새 계정 제한 (일) | 7 | 0~90 (0 = 끔) — 시험자, 그리고 봇이 하루 이상 전에 메시지를 보낸 사람은 예외 |
- 하루는 서버의 시간대(
TELEGRAM_VOICE_TIME_ZONE, 기본Asia/Seoul) 기준입니다. 통화를 받아들일 때 그날 남은 시간 안에서 최대 길이를 정해 모든 한도에 잡아 두고, 끝나면 서버 시계로 실제 초만 청구합니다. 봇 주인의 자기 키로 받는 통화는 발신자의 한도만 씁니다. - 봇 주인은 자기 봇의 한 통화 길이(기본 300초)와 동시 통화를 이 한도 안에서만 정할 수 있습니다.
- 모든 조치(차단 스위치, 공개, 허용, 멈추기, 끊기, 한도 …)는 가입 코드 탭의 활동 기록에
음성: …으로 남습니다. - 음성 통화의 품질 수치(첫 소리까지 걸린 시간, 끼어들기 때 멈추기까지, 도구의 답 시간 등)는 이 탭에 없고 관리 포트의
/metrics에만 있습니다(JSON 에서는voice., Prometheus 형식/metrics?format=prometheus에서는voice_로 시작하는 이름). 사람·봇·말은 없고 합계만입니다.
같은 일을 관리 API 로도 할 수 있습니다(모두 Authorization: Bearer <토큰>): GET /admin/api/voice, GET /admin/api/voice/bots/<id>, 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, 한도는 /admin/api/settings/voice. 봇이 거는 전화는 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.
환경 변수
서버는 설정 파일(server-config.json)을 읽은 뒤 환경 변수로 덮어씁니다. 운영 컴포즈 파일이 아래 값을 정해 주므로 보통은 .env 만 고치면 됩니다.
| 변수 | 뜻 | 운영 컴포즈 값 |
|---|---|---|
TELEGRAM_PORT | MTProto 포트 | MTPROTO_PORT (8443) |
TELEGRAM_HOST | MTProto 가 듣는 주소 | 0.0.0.0 |
TELEGRAM_PUBLIC_HOST | 앱에게 알려 줄 서버 주소 (help.getConfig) | PUBLIC_IP |
TELEGRAM_WEB_PORT | 웹사이트·문서·Bot API·관리 화면 포트 | 8080 |
TELEGRAM_WEB_HOST | 그 포트가 듣는 주소. 기본 127.0.0.1 | 0.0.0.0 (컨테이너 안. 호스트에는 127.0.0.1 로만 공개) |
TELEGRAM_PUBLIC_URL | 웹사이트 주소. 링크(me_url_prefix)·초대 링크·문서 예제·미리보기 이미지 | https://DOMAIN/ |
TELEGRAM_DATA_DIR | 사진·파일·admin-token·operations.json 위치 | /app/data |
TELEGRAM_RSA_KEY | 서버 RSA 비밀키 경로 (없으면 처음에 만듦, 공개키는 .pub) | /app/keys/private.pem |
TELEGRAM_DC_ID | 이 서버의 DC 번호. 앱이 집으로 쓰는 DC(텔레그램 앱은 모두 2)와 같아야 합니다 — 다르면 사진·파일 내려받기가 시작되지 않습니다 | DC_ID (2) |
TELEGRAM_DB_TYPE | memory · h2 · postgresql | postgresql |
TELEGRAM_DB_URL, TELEGRAM_DB_USERNAME, TELEGRAM_DB_PASSWORD | JDBC 접속 | jdbc:postgresql://pabal-postgres:5432/pabal, pabal, POSTGRES_PASSWORD |
TELEGRAM_DB_MAX_POOL_SIZE | DB 연결 수 | 20 |
TELEGRAM_ADMIN_TOKEN | 관리 화면 토큰 (비우면 data/admin-token 에 만듦) | ADMIN_TOKEN |
TELEGRAM_MAX_FILE_SIZE | 보낼 수 있는 가장 큰 파일 (2GB·512MB·바이트 수). 조각은 도착하는 대로 디스크에 쓰이므로 메모리가 아니라 디스크가 한도입니다 | MAX_FILE_SIZE (2GB) |
TELEGRAM_TEST_NUMBERS | +99966… 테스트 번호. 개발 전용 | false |
TELEGRAM_WEBHOOK_ALLOW_LOCAL | 봇 웹훅이 http:// 와 내부 주소를 쓸 수 있게. 개발 전용 | (정하지 않음 = false) |
TELEGRAM_FCM_CREDENTIALS | 안드로이드 푸시용 Firebase 서비스 계정 키 파일 경로. 비우면 푸시가 꺼집니다(서버 설치 — 10단계) | FCM_CREDENTIALS (비어 있음) |
TELEGRAM_FCM_PROJECT_ID | 키 파일의 project_id 가 아닌 Firebase 프로젝트로 보낼 때만 | FCM_PROJECT_ID (비어 있음) |
TELEGRAM_PUSH_PREVIEWS | false 면 푸시 알림에 메시지 내용 없이 보낸 사람만 | PUSH_PREVIEWS (true) |
TELEGRAM_TURN_HOST, TELEGRAM_TURN_PORT, TELEGRAM_TURN_SECRET | 통화 중계(TURN) — 서로 다른 망의 두 앱이 만나는 곳. 비밀을 비우면 직접 연결만 됩니다(서버 설치 — 통화 중계) | TURN_HOST(비우면 DOMAIN), TURN_PORT (3478), TURN_SECRET (비어 있음) |
TELEGRAM_APNS_KEY, TELEGRAM_APNS_KEY_ID, TELEGRAM_APNS_TEAM_ID, TELEGRAM_APNS_TOPIC, TELEGRAM_APNS_MIN_BUILD, TELEGRAM_APNS_VOIP_MIN_BUILD | 아이폰 푸시(APNs) — 키 파일 경로, 키 ID(비우면 파일 이름에서), 앱에 서명한 팀, 번들 ID, 보낼 가장 낮은 빌드, CallKit 전화 화면을 쓸 첫 빌드. 키가 없으면 아이폰 푸시가 꺼집니다(서버 설치 — 11단계) | APNS_* (키 없음, 비어 있음 — 파일 이름에서, 4Z8R468M2D, me.pabal.ios, 4, 비어 있음) |
TELEGRAM_STATIC_MAPS_KEY, TELEGRAM_STATIC_MAPS_PER_DAY, TELEGRAM_STATIC_MAPS_PER_USER_HOUR | 위치 메시지의 지도 그림 — Google Maps Static API 키, 하루에 새로 받는 그림 수, 한 사람이 한 시간에 새로 받게 할 수 있는 수. 키가 없으면 지도 그림이 꺼집니다(서버 설치 — 지도 그림) | STATIC_MAPS_* (키 없음, 300, 120) |
JAVA_OPTS | JVM 옵션 (메모리) | .env 의 값 |
환경 변수 — 음성 봇
운영 구성에서는 덧붙이는 파일 deploy/docker-compose.voice.yml 이 그중 링크·중계·시간대·한도·시험자·최소 빌드를 .env 값으로 넣고, 나머지는 기능마다 검토한 뒤에 더합니다(서버 설치 — 음성 봇). 게이트웨이(pabal-voice)의 VOICE_* 설정은 저장소의 voice/README.md 에 있습니다.
| 변수 | 기본 | 뜻 |
|---|---|---|
TELEGRAM_VOICE_LINK_SECRET_FILE | — | 게이트웨이와 같은 32바이트 링크 비밀(64자리 hex). 없거나 틀리면 음성 꺼짐 |
TELEGRAM_VOICE_LINK_HOST, TELEGRAM_VOICE_LINK_PORT | 127.0.0.1, 8095 | 링크가 듣는 곳. 루프백이 아니면 TELEGRAM_VOICE_LINK_ANY_ADDRESS=true 필요, 공인 주소는 늘 거절 |
TELEGRAM_VOICE_LINK_ANY_ADDRESS | — | "이 망은 서버와 게이트웨이만 쓴다"는 선언 |
TELEGRAM_VOICE_TURN_HOST, TELEGRAM_VOICE_TURN_PORT | — | 게이트웨이에게 줄 중계 주소(서버의 LAN 주소). 루프백이면 무시 |
TELEGRAM_VOICE_MAX_SECONDS | 600 | 이 서버가 허용하는 가장 긴 통화(60~3600) |
TELEGRAM_VOICE_BOT_CONCURRENCY | 2 | 봇 하나의 동시 통화 상한(운영자·봇 주인은 낮추기만) |
TELEGRAM_VOICE_TIME_ZONE | Asia/Seoul | 하루·달 한도의 기준 |
TELEGRAM_VOICE_TESTERS | — | 시험자 사용자 id 목록(관리 화면 목록과 합쳐짐). * 는 시험 서버만 |
TELEGRAM_VOICE_RECIPIENTS | google.vertex.gemini-live@us-central1 | 앱의 확인 창이 말하는 받는 곳. 게이트웨이의 것과 같아야 통화가 감 |
TELEGRAM_VOICE_MIN_BUILD_DESKTOP, _ANDROID, _IOS, _WATCHOS | 1, 62151, 5, 1 | 확인 창이 실제로 든 첫 앱 빌드(데스크톱은 태그 pabal-bc 뒤의 숫자). 받는 곳이 바뀌면 새 빌드를 내고 올림. 데스크톱은 한국어·영어가 아닌 확인 창을 처음 승인할 때 2 로 올림 |
TELEGRAM_VOICE_MIN_SHEET_VERSION | 1 | 받아들일 가장 낮은 확인 창 판 |
TELEGRAM_VOICE_TOOL_RESULT_BYTES | 16384 | 도구의 답 최대 크기(1024~16384) |
TELEGRAM_VOICE_KEYS_MASTER_FILE, TELEGRAM_VOICE_KEYS_PREVIOUS_MASTER_FILE | — | 자기 키·원음 비밀을 감싸는 마스터 키, 교체 중인 옛 키. 백업 밖 700 폴더에 |
TELEGRAM_VOICE_OWN_KEYS_APPS_READY | — | 앱이 자기 키 봇의 확인 창(2판)을 보여 준 뒤에만 true. 그 전에는 자기 키를 받지 않음 |
TELEGRAM_VOICE_OWN_KEY_MIN_SHEET_VERSION, TELEGRAM_VOICE_TRANSCRIPT_MIN_SHEET_VERSION | 2, 1 | 자기 키 통화·대화 기록 안내에 필요한 확인 창 판(운영 서버는 자기 키 판을 2 밑으로 내리지 않음) |
TELEGRAM_VOICE_STREAM_MIN_BUILD_DESKTOP, _ANDROID, _IOS | 0 (없음) | 원음 확인 창이 든 첫 앱 빌드. 0 이면 원음 봇에 아무도 걸 수 없음 |
TELEGRAM_VOICE_STREAM_MIN_SHEET_VERSION | 2 | 원음 확인 창의 판 |
TELEGRAM_VOICE_STREAM_DENY_ADDRESSES | — | 원음 주소가 닿으면 안 되는 이 배포의 주소(공개 호스트와 사이트 호스트는 저절로) |
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 | 애플 워치 API 와 소리 입구 주소(서버 설치 — 애플 워치 입구). ALLOW_PLAIN 은 개발 전용 |
TELEGRAM_VOICE_EVIDENCE_KEY_DIR | — | 봇이 거는 전화의 허락 기록(HMAC 증거)에 쓰는 키의 폴더. 700, 백업되는 폴더 밖. scripts/voice-evidence-key.py 로 만들고 바꿈. 없으면 아무도 허락을 켤 수 없음. 컴포즈에서는 .env 에만 두면 서버에 닿지 않음 — 봇이 거는 전화를 켤 때 deploy/docker-compose.voice.yml 의 pabal-server 에 environment(컨테이너 안 경로)와 volumes(호스트의 그 폴더)로 더함 |
TELEGRAM_VOICE_OUTBOUND_MIN_BUILD_DESKTOP, _ANDROID, _IOS, _WATCHOS | 0 (없음) | 허락 창이 든 첫 앱 빌드. 이보다 옛 빌드는 허락을 켤 수 없고 봇의 전화도 울리지 않음. 0 이면 그 플랫폼은 없음 |
TELEGRAM_VOICE_CALLME_DEVICE_AGE_SECONDS | 86400 | 허락을 켤 수 있는 기기의 로그인 뒤 시간(초). 시험 서버만 바꿀 수 있음(운영 서버는 경고하고 무시) |
TELEGRAM_VOICE_KEEP_TEXT_UNREVIEWED | — | 법률 검토 전의 대화 남기기 체크 상자 문구를 워치에 보임. 내부 시험 서버 전용 |
TELEGRAM_MCP_*, TELEGRAM_MCP_APPS_READY | — | MCP 부품(deploy/docker-compose.mcp.yml 이 넣음). TELEGRAM_MCP_EGRESS_OWN_ADDRESSES(파발의 공인 주소)는 MCP 가 아니어도 웹훅·도구 요청이 파발 자신에게 가지 못하게 쓰입니다 |
TELEGRAM_VOICE_BOTS, TELEGRAM_VOICE_NOTICE_CHECK=off, TELEGRAM_VOICE_STREAM_ALLOW_PRIVATE | — | 시험 서버 전용(TELEGRAM_TEST_NUMBERS=true 일 때만): 나열한 봇(id·@이름, * 은 모든 봇) 허용 · 법률 절 확인 건너뛰기 · 원음 주소에 사설 주소 |
TELEGRAM_VOICE_CALLME_UNREVIEWED, TELEGRAM_VOICE_DEV_ECHO_OUTBOUND, TELEGRAM_VOICE_TEST_DIAL, TELEGRAM_VOICE_TEST_DIAL_USERS | — | 시험·개발 서버 전용: 법률 검토 전에 봇이 거는 전화의 스위치를 켤 수 있게(개발·시험 서버) · 에코 게이트웨이가 봇의 전화를 걸게(개발 서버, TELEGRAM_TEST_MODE) · 관리 API 의 시험 걸기와 그것이 울릴 계정(+99966 시험 번호 말고 20개까지, 시험 서버). 운영 서버는 경고하고 무시 |
데이터 파일
| 파일 | 내용 | 권한 |
|---|---|---|
keys/private.pem | 서버 RSA 비밀키. 절대 밖으로 내보내지 않음 | 600 |
keys/private.pem.pub | 공개키. 앱 빌드와 /docs/server-key.pem 에 쓰임 | — |
keys/firebase-service-account.json | (선택) 안드로이드 푸시용 Firebase 비밀 키. 새어 나가면 Firebase 콘솔에서 지우고 새로 받음 | 600 |
data/admin-token | 관리 화면 토큰 | 600 |
data/operations.json | 설정 탭의 값 — 가입 규칙, SMS·SMTP 비밀값, 차단 번호, 음성 탭의 설정(차단 스위치·공개·시험자·허용한 봇·한도) | 600 |
keys/voice-link.secret | (음성 봇) 게이트웨이와 같은 링크 비밀 | 400 |
/etc/pabal/voice/voice-master.key | (음성 봇) 봇 주인의 자기 키·원음 비밀을 감싸는 마스터 키. 데이터 폴더 밖, 백업에 넣지 않음 | 600 (폴더 700) |
data/media/ | 사진과 파일 원본 | — |
PostgreSQL events 테이블 | 계정·대화·메시지·로그인·봇·웹훅 설정 — 모든 변경의 기록 | — |
보안 메모
- 관리 포트를 인터넷에 직접 열지 마세요. 운영 구성의 Caddy 는
/admin,/health,/metrics같은 관리 경로를 404 로 막습니다. - 관리 데이터 요청은 모두
Authorization: Bearer <토큰>이 필요하고, 다른 사이트의 페이지는 읽을 수 없습니다. - SMS·SMTP 비밀값은
operations.json에만 있고, 화면과 API 는 "저장됨"만 알려 줍니다. 비밀값 칸을 비워 두고 저장하면 기존 값이 유지됩니다. - 토큰·비밀값은 로그에 찍히지 않고, 로그의 전화번호는 가려서 남깁니다. Bot API 주소(토큰 포함)도 로그에 남기지 않습니다.
- 푸시 알림의 기기 토큰은 로그에 지문으로만 남고, Firebase 키 파일의 내용과 알림 내용은 로그에 남지 않습니다.
- 인증 키가 DB 에 들어 있으므로, DB 에 접근할 수 있는 사람은 사용자 트래픽을 풀 수 있습니다. DB·백업을 RSA 키만큼 지키세요.
한계
- 기다리는 코드와 최근 기록은 메모리에 있어 서버를 다시 켜면 사라집니다(앱에서 다시 요청하면 됨).
- SMS 제공자의 실제 발송은 각 제공자 계정으로 확인해야 합니다. 서버는 각 제공자의 문서대로 요청을 만듭니다.
- 아직 없는 것: 봇 강제 삭제, 메시지 열람, 로그 보기, 그래프, 푸시 알림 통계(푸시는 서버 로그로 확인합니다).