Files
arasystem/.env.production.example
T
ttipo 93147b88b7 feat(docker): 대용량 첨부 — ClamAV 검사기 · 공개 내려받기 호스트 · 받기 동시 연결 상한
- ClamAV 1.4.6 LTS 를 둘로 나눴다: clamd 는 백엔드와만 닿는 internal 네트워크(바깥 길 없음 · 서명 볼륨 읽기 전용),
  freshclam 은 자기 바깥 길에만. 비-root · cap_drop ALL. 운영은 늘 뜨고 개발은 profile clamav.
  clamd.conf 는 한 파일 한도 2047M 까지 끝까지 검사하고 암호 · 한도 초과를 판정으로 알린다(실측표는 mail/README).
  검사 중에 죽으면 남던 임시 파일을 띄울 때 지운다.
- nginx 를 포털 · 공개 내려받기(files.) 두 server 로 가른다(SHARE_PUBLIC_URL 과 CORS_ORIGIN 의 호스트 비교 — 같으면 포털이 함께 연다).
  조각 PUT 17m, 받기는 버퍼링 · 압축 끔 + X-Accel-Buffering 넘김 + 머리글 버퍼 16k,
  limit_conn 은 X-Forwarded-For 마지막 칸으로 IP 마다 공개 10 · 앱 안 30, 전체 공개 400 · 앱 안 200.
  받기 location 은 /api/ 블록 안의 대소문자를 가리지 않는 정규식이다(리뷰 — /FILES/ 같은 변형이 상한을 비켜 갔다).
  접근 로그는 경로 모양과 43자 토큰 모양으로 토큰을 가린다.
- 파일은 named volume <PROJECT_NAME>_files(개발도 — builder 단계가 /app/files 를 node 소유로 만든다), 백업하지 않는다.
- CI infra 잡: nginx 두 꼴 · 받기 경로 변형 시험(가짜 백엔드) · clamconf(-n 없이 기본값까지) · 틀린 SHARE_PUBLIC_URL 거부.
- mail/README: 악성 검사 · 공개 호스트 · NPM 칸(Advanced 다섯 줄) · 메모리 예산 · 문제 해결.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:37:21 +09:00

248 lines
20 KiB
Bash

# ==========================================
# 프로젝트 전역 설정
# ==========================================
PROJECT_NAME=project
APP_ENV=production
BUILD_MODE=production
FRONTEND_DIR=frontend
BACKEND_DIR=backend
# ==========================================
# 포트 설정
# ==========================================
FRONTEND_PORT=80
BACKEND_PORT=3000
WEBSOCKET_PORT=3001
DB_PORT=5432
REDIS_PORT=6379
# ==========================================
# 내부 네트워크 (compose app-network · edge)
#
# 서브넷을 고정한다 — 메일 엔진은 app-network 대역만 신뢰한다 (게이트웨이 로그인·자동 차단 예외, mail/README.md).
# APP_NETWORK_SUBNET : backend·db·redis·mail — 사설 대역, /24 ~ /28 (mail setup 이 검사한다)
# EDGE_NETWORK_SUBNET : frontend(nginx) ↔ backend 전용. 인터넷을 향한 frontend 는 app-network 에 붙지 않는다.
# TRUST_PROXY 를 주소 목록으로 쓸 때 이 대역을 넣는다 (아래)
# MAIL_RESOLVER_SUBNET : mail ↔ 메일 전용 DNS 리졸버(mail-resolver) 전용 네트워크 mail-dns — 바깥 길이 없는 internal (사설 대역, /24 ~ /29)
# MAIL_RESOLVER_IP : 그 안의 리졸버 주소(고정) — 엔진의 DNS(setup 이 맞춘다)와 mail 컨테이너의 dns 가 이 주소다. 서브넷의 끝 쪽에 둔다
# (도커는 mail 에 앞쪽부터 차례로 준다). 둘은 함께 바꾸고 setup 을 다시 실행한다 (mail/README.md "DNS 리졸버와 스팸 차단 목록")
# 도커 기본 주소 풀(172.17~31.x, 192.168.x) 밖의 값이라 자동으로 배정되는 네트워크와 겹치지 않는다.
# 같은 호스트의 다른 프로젝트와 겹치면 up 이 "Pool overlaps" 로 실패한다 → 다른 /24 로 바꾸고 setup 을 다시 실행한다.
# 서버의 사설망(NCP VPC 등)과도 겹치지 않게 한다 — 겹치면 그 대역으로 가는 길이 도커 브리지로 간다 (mail/README.md "NCP 쪽 할 일")
# ==========================================
APP_NETWORK_SUBNET=172.16.71.0/24
EDGE_NETWORK_SUBNET=172.16.72.0/24
MAIL_RESOLVER_SUBNET=172.16.73.0/24
MAIL_RESOLVER_IP=172.16.73.254
# ==========================================
# Database 설정
# ==========================================
DB_IMAGE=postgres:15-alpine
DB_USER=app_user
DB_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
DB_NAME=project_db
DB_DATA_DIR=./data/postgres
# ==========================================
# Redis 설정
# ==========================================
REDIS_IMAGE=redis:7-alpine
REDIS_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
REDIS_DATA_DIR=./data/redis
# ==========================================
# 화면·API 주소 — 같은 주소(origin) 배포 (docs/mail/api-contract.md 머리말)
#
# 브라우저는 포털 주소의 /api 를 부르고 프론트 nginx 가 백엔드로 넘긴다. 운영의 입구는 프론트 nginx 하나다.
# - CORS_ORIGIN : 화면의 공개 주소(origin, 여러 개면 콤마). 상태 변경 요청의 출처 검사에 쓴다 — 운영 필수
# - BACKEND_API_URL : 화면 주소 + /api. 앱은 쓰지 않고, 운영 기동 때 공개 주소인지 검사하는 데만 쓴다
#
# localhost 나 example.com 이 남아 있으면 **백엔드가 기동에 실패한다**
# (backend/src/shared/config/env.validation.ts — fail-fast).
# 로컬에서 운영 이미지를 검증하느라 일부러 localhost 를 쓰는 경우에만
# ALLOW_LOCALHOST_URL=true 로 이 검사를 건너뛴다. 실제 배포에는 설정하지 않는다.
#
# - TRUST_PROXY : 백엔드 앞 프록시 — 실제 클라이언트 IP(로그인 실패 한도의 기준)를 읽는다. 운영 필수
# 이 compose 의 운영 경로는 클라이언트 → 외부 리버스 프록시(TLS) → 프론트 nginx → 백엔드 하나뿐이다.
# (프론트 nginx 는 TLS 를 하지 않고 호스트 포트도 열지 않으므로(다른 서버의 프록시용 구성은 사설 IP 에만) 외부 프록시 없이는 쓸 수 없다 — 1 은 해당 없음)
# 2 = 홉 수. proxy 네트워크를 이 프로젝트의 외부 프록시만 쓸 때
# 주소 목록 = <EDGE_NETWORK_SUBNET>,<외부 프록시 IP> (예: 172.16.72.0/24,172.20.0.10) — proxy 네트워크를
# 다른 프로젝트와 함께 쓴다면 이쪽을 권장한다. 홉 수는 "몇 번째 주소"만 믿어서, 같은 네트워크의 다른
# 컨테이너가 외부 프록시를 건너뛰고 frontend 에 바로 붙으면 X-Forwarded-For 를 꾸며 IP 를 바꿀 수 있다.
# 외부 프록시 IP 는 frontend 가 보는 주소(proxy 네트워크 안 주소)이며 고정해 둔다(ipv4_address 등).
# 넓은 사설 대역을 통째로 넣지 않는다.
# 외부 프록시가 다른 서버에 있으면(아래 "리버스 프록시가 다른 서버에 있을 때") 주소 목록으로 —
# <EDGE_NETWORK_SUBNET>,<프록시 서버의 사설 IP> (예: 172.16.72.0/24,10.0.1.11). 사설 IP 는 frontend 접근 로그의 첫 칸이다
# true·0·false 는 운영에서 기동이 거부된다.
# ==========================================
BACKEND_API_URL=https://portal.arasystem.it.kr/api
# ⚠️ 대용량 첨부의 공개 내려받기 호스트(아래 SHARE_PUBLIC_URL — files.)는 여기에 넣지 않는다 — 넣으면 공개 호스트 분리가 꺼져
# 포털에서도 공개 링크가 열린다(백엔드가 운영에서는 기동을 막는다). 공개 화면은 자기 호스트의 /api/public-links/ 만 부른다
CORS_ORIGIN=https://portal.arasystem.it.kr
TRUST_PROXY=2
# ==========================================
# Rate Limiting (API 남용 방지)
# 미설정 시 백엔드 기본값(60초 / 100회) 사용
# ==========================================
THROTTLE_TTL_MS=60000
THROTTLE_LIMIT=100
# ==========================================
# DB 스키마 동기화 (개발 전용, 기본 false)
# true 로 켜면 엔티티 변경이 DB 에 자동 반영되어 빠르게 실험할 수 있지만,
# 그 상태에서는 migration:generate 가 "변경 없음"만 내놓는다.
# 운영으로 갈 코드라면 꺼둔 채 마이그레이션으로 관리할 것.
# (운영 환경에서는 이 값과 무관하게 항상 비활성화된다)
# ==========================================
DB_SYNCHRONIZE=false
# ==========================================
# 선택적 서비스 (Compose profiles)
#
# 기본값 = mail — 메일 엔진(Stalwart)을 띄운다. 직원 로그인이 메일 엔진에 의존한다.
# (redis 는 기동하지 않는다. 대용량 첨부의 악성 검사기 clamav · clamav-updater 는 profile 없이 늘 뜬다 — 아래 "대용량 첨부")
# 캐시·세션 공유가 필요해지면 redis 를 추가하면 컨테이너가 함께 뜬다.
# COMPOSE_PROFILES=mail,redis
# 일회성으로 켜려면: docker compose --profile redis ... up -d
# ==========================================
COMPOSE_PROFILES=mail
# ==========================================
# 리버스 프록시가 다른 서버에 있을 때 (선택 — mail/README.md "리버스 프록시가 다른 서버에 있을 때")
#
# 기본은 외부 리버스 프록시가 이 호스트에서 external proxy 네트워크로 <PROJECT_NAME>-frontend:8080 에 붙는 구성이다.
# 다른 서버의 프록시(예: 같은 VPC 의 Nginx Proxy Manager)가 사설망으로 붙는다면 COMPOSE_FILE 의 주석을 풀고 FRONTEND_PRIVATE_BIND 를 채운다 —
# docker-compose.remote-proxy.yml 이 frontend 를 proxy 네트워크에서 빼고 8080 을 이 서버의 사설 IP 에만 게시한다.
# COMPOSE_FILE : compose 파일 목록 (리눅스는 : 로 잇는다). 이 파일에 두면 문서의 운영 명령(sudo docker compose --env-file .env.production …)이
# -f 없이 그대로 두 파일을 읽는다 (compose v2.40 · 5.4 에서 확인). 쓰지 않을 때는 주석으로 둔다 —
# 빈 값(COMPOSE_FILE=)은 compose 가 파일 목록으로 읽다 실패한다
# FRONTEND_PRIVATE_BIND : <이 서버의 사설 IPv4>:<포트> (예: 10.0.1.12:8080 — ip -4 addr 로 본다).
# 비어 있으면 compose 가 멈추고, 포트뿐인 값 · 0.0.0.0 · 공인 IP · IPv6 면 frontend 가 뜨지 않는다 (frontend-bind-check)
# TRUST_PROXY 는 위 "화면·API 주소" 의 주소 목록으로 바꾼다 — <EDGE_NETWORK_SUBNET>,<프록시 서버의 사설 IP>
# ⚠️ 게시 주소만으로는 바깥을 막지 못한다 — NCP 는 공인 IP 를 사설 IP 로 NAT 하므로 8080 은 방화벽(ACG)이 프록시 서버의 사설 IP 에서만 받게 한다
# ==========================================
# COMPOSE_FILE=docker-compose.yml:docker-compose.remote-proxy.yml
FRONTEND_PRIVATE_BIND=
# ==========================================
# 파일 업로드 (로컬 디스크)
#
# 저장 위치는 named volume <PROJECT_NAME>_uploads 다 (docker-compose.yml). 호스트 디렉터리를 쓰지 않는다 —
# sudo docker compose 가 없는 디렉터리를 root 소유로 만들어 backend(node)가 쓰지 못한다.
# (UPLOAD_DATA_DIR 는 개발 전용이라 운영에는 두지 않는다. 백업은 docker 스킬 "운영 환경")
# ==========================================
UPLOAD_MAX_SIZE_MB=5
# ==========================================
# 대용량 첨부 (11단계 — docs/mail/api-contract.md 4절, mail/README.md "악성 검사 (ClamAV)" · "대용량 첨부 — 운영")
#
# 일반 첨부 한도(아래 MAIL_ATTACHMENT_*)를 넘는 파일은 백엔드의 볼륨(<PROJECT_NAME>_files — 30일짜리 전송물이라 백업하지 않는다)에 올리고,
# 메일에는 받는 사람이 로그인 없이 여는 내려받기 링크를 넣는다. 아래 값은 백엔드 기본값과 같다(SHARE_PUBLIC_URL · CLAMAV_HOST 는 운영 필수).
# SHARE_PUBLIC_URL : 공개 내려받기 화면의 출처 — **운영 필수**, https, 경로 · 끝의 / 없이. 포털과 다른 호스트로 둔다:
# 털린 계정이 퍼뜨린 악성 파일로 포털 주소까지 차단 목록에 오르는 위험을 떼어 놓고, 포털 쿠키도 그 호스트로 가지 않는다.
# DNS(가비아 files A → 외부 리버스 프록시 서버) · 외부 프록시의 Proxy Host(같은 사설 IP:8080, 접근 로그 끔)가 있어야 한다.
# ⚠️ 이 호스트를 위 CORS_ORIGIN 에 넣지 않는다(넣으면 분리가 꺼진다 — 백엔드가 기동을 막는다).
# ⚠️ 한 번 정하면 바꾸지 않는다 — 이미 보낸 메일의 링크가 이 주소다(옛 호스트가 남아 있어야 열린다)
# SHARE_REPORT_ADDRESS : 공개 화면의 신고 · 문의 주소 (비우면 postmaster@<MAIL_DOMAIN>)
# SHARE_DOWNLOAD_LIMIT : 파일마다 내려받기 상한(받기 시작한 수, 1~100000)
# MAIL_LARGE_FILE_MAX_SIZE_MB : 한 파일(MB, 1~2047 — 악성 검사기가 끝까지 검사할 수 있는 2GiB 아래, MAIL_ATTACHMENT_MAX_SIZE_MB 이상)
# MAIL_LARGE_FILE_MAX_COUNT : 메일 한 통에 새로 싣는 대용량 파일 수(1~20)
# MAIL_LARGE_FILE_USER_QUOTA_GB : 직원마다 동시에 보관하는 양(GB, 1~1000 — 올리는 중 · 임시 저장 포함)
# MAIL_LARGE_FILE_RETENTION_DAYS : 보낸 날(한국)부터 받을 수 있는 날 수(1~90)
# FILE_STORAGE_MAX_TOTAL_GB : 서버 전체 보관량(GB). 비우면 한도 없음 — 디스크 여유만 본다. 디스크를 메일 · DB 와 함께 쓰면 정해 둔다
# FILE_STORAGE_MIN_FREE_GB : 올리기를 만든 뒤에도 남길 디스크 여유(GB) — 도커 data-root 의 디스크(볼륨 · 악성 검사의 임시 파일이 함께 쓴다)
# CLAMAV_HOST · CLAMAV_PORT : 악성 검사기(clamd) — **운영 필수**. compose 의 clamav 서비스(호스트 이름만 — 스킴 · 포트 없이)
# ==========================================
SHARE_PUBLIC_URL=https://files.arasystem.it.kr
SHARE_REPORT_ADDRESS=
SHARE_DOWNLOAD_LIMIT=500
MAIL_LARGE_FILE_MAX_SIZE_MB=2047
MAIL_LARGE_FILE_MAX_COUNT=10
MAIL_LARGE_FILE_USER_QUOTA_GB=10
MAIL_LARGE_FILE_RETENTION_DAYS=30
FILE_STORAGE_MAX_TOTAL_GB=
FILE_STORAGE_MIN_FREE_GB=10
CLAMAV_HOST=clamav
CLAMAV_PORT=3310
# ==========================================
# 메일 엔진 (Stalwart) — profile `mail`
#
# 운영 기본 가정 = 메일 전용 호스트: 25·465·993·443 을 이 호스트에 직접 연다 (docker-compose.yml).
# 첫 배포 후 setup 스크립트를 한 번 실행한다 — mail/README.md "운영 배포".
#
# ⚠️ 비밀번호는 모두 강한 무작위 값으로 교체한다. CHANGE-ME 가 남아 있으면 setup 스크립트가 중단한다.
# 영문·숫자로만 만든다 — # · 따옴표 · $ · 공백은 compose 와 Node 가 다르게 읽는다.
# 예: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
# ⚠️ 8080(관리 API)은 공개하지 않는다. setup 스크립트는 내부망에서 실행한다 (README 참조).
# 공개 443 의 JMAP·관리 API·웹 포털은 setup 이 막는다 (MTA-STS 정책 파일만 열어 둔다).
# ==========================================
# 패치까지 고정한다 — setup 스크립트가 0.16 의 동작(관리 API·bootstrap)에 맞춰져 있다 (docker 스킬 "이미지 버전 고정")
MAIL_IMAGE=stalwartlabs/stalwart:v0.16.27
MAIL_DOMAIN=arasystem.it.kr
# 새 엔진의 호스트명 (EHLO·PTR·TLS 인증서) — 최초 bootstrap 때만 반영된다.
# mailcow 가 mail.arasystem.it.kr 를 쓰는 전환 기간에는 새 이름(mx1.)을 쓴다 (mail/README.md "전환 절차")
MAIL_HOSTNAME=mx1.arasystem.it.kr
# JMAP·OAuth 디스커버리 기준 주소 — 새 엔진에 실제로 닿는 주소여야 한다
MAIL_PUBLIC_URL=https://mx1.arasystem.it.kr
# 영구 관리자 아이디 (주소는 <이 값>@MAIL_DOMAIN, 미설정 시 admin) — setup 스크립트가 이 이름으로 관리자를 만들고,
# 백엔드는 이 계정의 웹메일 로그인·대리 접근을 막는다 (두 쪽이 이 값 하나를 읽는다).
# ⚠️ 운영 bootstrap 전에 정한다. mailcow 에 실제 admin@ 메일함이 있으면 다른 이름으로 한다
# (그대로 두면 엔진 관리자와 주소가 겹쳐 그 메일함은 옮겨 와도 웹메일에 로그인할 수 없다).
# ⚠️ bootstrap 뒤에 이 값만 바꾸면 엔진 관리자는 그대로 남고 백엔드는 새 이름만 막게 되므로, setup 이 중단한다
# (되돌리는 절차: mail/README.md "문제 해결"). 영문·숫자·. _ - 만, 앞뒤는 영문·숫자, 64자 이하 (백엔드와 같은 규칙)
MAIL_ADMIN_LOCAL_PART=admin
# 영구 관리자 비밀번호 — setup 스크립트가 이 값으로 맞춘다
MAIL_ADMIN_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
# 복구 관리자(MAIL_RECOVERY_ADMIN, "아이디:비밀번호")는 이 파일에 적지 않는다 — 최초 bootstrap·관리자 복구 때
# 셸에서 일회성으로만 준다. 값이 있는 동안은 정상 모드에서도 관리자로 로그인되기 때문이다.
# 이 파일에 값이 있거나, 끝난 뒤에도 mail 컨테이너에 남아 있으면 setup 이 실패한다 (mail/README.md "운영 배포").
# read -rsp '임시 복구 비밀번호: ' P; echo; export MAIL_RECOVERY_ADMIN="admin:$P"
# docker compose --env-file .env.production up -d mail
# node --env-file=.env.production mail/scripts/setup.mjs --recovery
# unset MAIL_RECOVERY_ADMIN P; docker compose --env-file .env.production up -d mail # 복구 관리자 끄기
# node --env-file=.env.production mail/scripts/setup.mjs # 꺼졌는지 확인
# 백엔드가 직원 메일함을 대리 접근(impersonate)할 때 쓰는 서비스 계정 (주소 전체).
# ⚠️ 이 비밀번호는 전 직원 메일함의 열쇠다
MAIL_GATEWAY_USER=mail-gateway@arasystem.it.kr
MAIL_GATEWAY_PASSWORD=CHANGE-ME-BEFORE-DEPLOY
# 사내 목록 계정 아이디 (주소는 <이 값>@MAIL_DOMAIN, 미설정 시 mail-directory) — 백엔드가 엔진의 계정 목록에서 직원 이름·주소만 읽을 때
# 게이트웨이 대리 접근으로 쓰는 전용 계정이다. 비밀번호가 없다. 관리자·게이트웨이 아이디와 달라야 하고, 옮겨 올 메일함과 겹치지 않게 정한다.
# ⚠️ 엔진 관리자는 이 계정의 권한을 내줄 수 없어 setup 의 --recovery 실행(위 복구 관리자 절차)에서만 만들어진다 — 첫 배포 때 만들어지고,
# 이미 운영 중인 서버라면 --recovery 를 한 번 더 한다. 없는 동안 백엔드는 로그인한 직원 기록으로 사내 목록을 대신한다 (mail/README.md "사내 목록 계정").
# 바꾸면 --recovery 로 setup 을 다시 실행하고 backend 를 다시 띄운다. 영문·숫자·. _ - 만, 앞뒤는 영문·숫자, 64자 이하 (MAIL_ADMIN_LOCAL_PART 와 같은 규칙)
MAIL_DIRECTORY_LOCAL_PART=mail-directory
# 백엔드가 쓰는 내부 JMAP 주소 (compose 내부망)
MAIL_JMAP_URL=http://mail:8080
# 첨부 1개 최대 크기(MB, 미설정 시 20). 프론트 nginx 의 본문 한도는 이 값 + 1MB 로 자동으로 따라간다.
# ⚠️ 그 앞의 외부 리버스 프록시 본문 한도는 직접 이 값 + 1MB 이상으로 둔다 —
# 작으면 프록시가 먼저 413 을 내고 사용자에게는 FILE_002 로 보인다 (docker 스킬 "운영 환경")
MAIL_ATTACHMENT_MAX_SIZE_MB=20
# 메일 한 통의 첨부 합계(MB, 미설정 시 25) — 파일당 한도 이상이어야 한다.
# 바꾸면 mail setup 도 다시 실행한다 — 엔진의 임시 업로드 한도를 이 값의 20배로 맞춘다 (mail/README.md "엔진 한도")
MAIL_ATTACHMENT_TOTAL_MAX_SIZE_MB=25
# 게이트웨이 계정을 쓸 수 있고 자동 차단에서 빠지는 대역 (콤마 구분).
# 비우면 APP_NETWORK_SUBNET 과 같다. 그 안의 더 좁은 대역만 쓸 수 있다 — 더 넓으면 setup 이 실패한다
MAIL_TRUSTED_NETWORKS=
# (선택) 웹메일 로그인·대리 접근을 막을 계정 — 주소 전체를 콤마로. 비어 있어도 위 관리자(MAIL_ADMIN_LOCAL_PART)는 항상 막힌다.
# 관리자를 더 만들었다면 여기에 넣는다 (setup 이 빠진 관리자 계정을 [주의] 로 알려 준다)
# MAIL_BLOCKED_ACCOUNTS=ops@arasystem.it.kr
# Spamhaus 조회 키 (선택 — mail/README.md "DNS 리졸버와 스팸 차단 목록"). 있으면 스팸 필터의 Spamhaus 두 항목(ZEN · DBL)이 공개 미러 대신
# Spamhaus 의 "Blocklists via DNS Query"(옛 DQS — <키>.zen.dq.spamhaus.net)로 묻는다. 비어 있으면 setup 이 두 항목을 끄고 [주의] 를 낸다
# (공개 미러는 소규모 · 비상업 용도라 쓰지 않는다 — 나머지 차단 목록은 그대로).
# 무료 계정은 Spamhaus 의 Fair Use(소규모 · 비상업) 조건이다 — 회사가 대상인지는 Spamhaus 가 판단하고, 안 되면 비워 두거나(두 항목 꺼짐) 유료(30일 체험)로.
# ⚠️ 체험(30일) 키는 기간이 끝나면 답이 멈춘다 — 구독하거나 키를 비우고 setup. 멈추면 엔진의 Spamhaus 조회가 실패해 표시가 붙지 않고(메일은 받는다)
# 다음 setup 실행이 두 항목을 끄고 종료 1 로 알린다 — setup 을 주기적으로 돌려 종료 0 인지 보거나 Spamhaus 계정의 만료일을 본다.
# 키(26자 영문 · 숫자)는 비밀이다 — 이 파일에만 둔다(파일에 없거나 파일과 다른 값이 셸에 있으면 setup 이 멈춘다). setup 은 출력하지 않지만 엔진의 스팸 필터 설정(조회 이름 식)에는
# 그대로 저장된다 — 관리 화면 · 관리 API · 메일 볼륨 백업으로 보인다. 바꾸거나 비우면 setup 을 다시 실행한다
MAIL_SPAMHAUS_DQS_KEY=
# 시범 공존 — 한 사람만 새 서버를 먼저 쓰고 나머지 직원은 기존 서버(mailcow)에 남을 때 (mail/README.md "파일럿 공존"). 비우면 꺼짐.
# MX 는 mailcow 그대로이고, mailcow 의 Transport Map 이 시범 사용자 주소만 이 서버로 넘긴다. 값은 셸이 아니라 이 파일에 둔다.
# ⚠️ 켠 뒤에 값이 비면 setup 이 멈춘다 — 시범을 끝낼 때(전면 전환)는 둘 다 비우고 setup 에 --end-pilot 을 붙인다.
# 기존 서버 주소 — host · host:port · [host]:port (운영은 포트 25 만 — 기본값, IPv4 주소면 인증서 이름 확인을 끈다). 예: mail.arasystem.it.kr
MAIL_PILOT_LEGACY_HOST=
# 기존 서버가 이 서버에 붙을 때의 공인 IPv4 · CIDR(/24 까지, 콤마) — HOST 가 있으면 필수. 예: 46.250.248.43
MAIL_PILOT_LEGACY_IPS=