generated from ttipo/project-base
feat(docker): 13단계 운영 기반 — 백업 · 감시 · 관리자 단단히 · 휴지통 30일 · 메일 프로그램 자동 설정
전 직원 전환 전에 서버가 스스로 지키고 되살릴 수 있게 했다. - 관리자 단단히(setup): 관리자 역할에서 impersonate 를 빼고 관리자 허용 IP 를 앱 네트워크로 — 관리자 비밀번호로 바깥 993 · JMAP 에서 직원 메일함에 대리 로그인되던 길을 막았다. 운영에서 처음 걸 때는 되돌릴 복구 관리자가 있을 때만 건다(관리자 잠김 방지). - 휴지통 · 스팸 30일 정리를 KST 03:00 으로 명시. imapsync 로 옮긴 메일도 옮긴 때부터 센다(임시 엔진 실측). - restic 백업(호스트 systemd 03:30 — 메일 볼륨 warm → 엔진 멈춤 9초 안팎 → final, pg_dump, 업로드, env) · 복원(새 서버 차례를 빈 클론에서 실측) · 15분 점검 · 부팅 되살리기, Healthchecks 하트비트. - 메일 프로그램 자동 설정: frontend nginx 의 autoconfig · autodiscover 고정 응답(엔진 것은 POP3 를 알리고 입력을 되돌려 쓴다). - 모든 서비스 docker 로그 보존, DB 이미지 판 · digest 고정. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -133,5 +133,62 @@ else
|
||||
echo "nginx-env.envsh: 공개 내려받기 호스트 ${nginx_env_share_host} — 포털에서는 /s/ · /api/public-links/ 를 닫는다"
|
||||
fi
|
||||
|
||||
# ------------------------------------------
|
||||
# 메일 프로그램 자동 설정 (13단계 — mail/README.md "메일 프로그램 자동 설정")
|
||||
#
|
||||
# server 이름 autoconfig.<MAIL_DOMAIN> · autodiscover.<MAIL_DOMAIN>, 알리는 호스트는 MAIL_HOSTNAME(엔진 인증서의 이름 — 전환 기간 mx1.). 둘 다 compose 가 루트 env 에서 준다.
|
||||
# 둘 중 하나라도 비면 끈다 — server 이름이 어느 요청에도 맞지 않는 autoconfig.invalid 다. 모양이 틀리면 기동을 멈춘다(server_name · 응답 XML 에 그대로 들어간다 —
|
||||
# 지시어 · XML 을 끼워 넣지 못하게 영문 소문자 · 숫자 · - · . 의 호스트 이름만). 포털 · 공개 내려받기 호스트와 겹치면(그쪽 요청을 가로챈다) 멈춘다
|
||||
# ------------------------------------------
|
||||
|
||||
# 호스트 이름 — 소문자로 바꾼 값이 이름 꼴이면 그 값, 아니면 아무것도 내지 않고 1 (칸마다 63자 · 모두 253자 · 점 하나 이상)
|
||||
nginx_env_hostname() {
|
||||
nginx_env_name="$(printf '%s' "$1" | tr 'A-Z' 'a-z')"
|
||||
# 빈 값 · 그 밖의 글자 · 점이나 하이픈으로 시작 · 끝 · 연이은 점 · 칸의 앞뒤 하이픈 · 점 없는 이름
|
||||
case "$nginx_env_name" in
|
||||
"" | *[!a-z0-9.-]* | .* | *. | *..* | -* | *- | *.-* | *-.*) return 1 ;;
|
||||
*.*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
[ "${#nginx_env_name}" -le 253 ] || return 1
|
||||
nginx_env_rest="$nginx_env_name"
|
||||
while [ -n "$nginx_env_rest" ]; do
|
||||
nginx_env_label="${nginx_env_rest%%.*}"
|
||||
[ "${#nginx_env_label}" -le 63 ] || return 1
|
||||
case "$nginx_env_rest" in *.*) nginx_env_rest="${nginx_env_rest#*.}" ;; *) nginx_env_rest="" ;; esac
|
||||
done
|
||||
printf '%s' "$nginx_env_name"
|
||||
}
|
||||
|
||||
if [ -z "${MAIL_DOMAIN:-}" ] || [ -z "${MAIL_HOSTNAME:-}" ]; then
|
||||
NGINX_AUTOCONFIG_SERVER_NAMES="autoconfig.invalid"
|
||||
NGINX_MAIL_DOMAIN="invalid"
|
||||
NGINX_MAIL_CLIENT_HOST="invalid"
|
||||
echo "nginx-env.envsh: 메일 프로그램 자동 설정을 끈다 (MAIL_DOMAIN · MAIL_HOSTNAME 중 비어 있는 값이 있다)"
|
||||
else
|
||||
NGINX_MAIL_DOMAIN="$(nginx_env_hostname "$MAIL_DOMAIN")" \
|
||||
|| nginx_env_fail "MAIL_DOMAIN 은 호스트 이름(영문 · 숫자 · - · . — 예: arasystem.it.kr)이어야 합니다 (현재: \"$MAIL_DOMAIN\")."
|
||||
NGINX_MAIL_CLIENT_HOST="$(nginx_env_hostname "$MAIL_HOSTNAME")" \
|
||||
|| nginx_env_fail "MAIL_HOSTNAME 은 호스트 이름(예: mx1.arasystem.it.kr)이어야 합니다 (현재: \"$MAIL_HOSTNAME\")."
|
||||
# 견줄 호스트 — 공개 내려받기 호스트와 CORS_ORIGIN 의 호스트들(공유 호스트 판정과 같게 빈칸 · 끝의 / 를 뗀다 — 백엔드도 끝 / 를 받는다)
|
||||
nginx_env_taken_hosts="$nginx_env_share_host"
|
||||
while IFS= read -r nginx_env_origin; do
|
||||
nginx_env_origin="$(printf '%s' "$nginx_env_origin" | tr -d ' ')"
|
||||
nginx_env_origin="${nginx_env_origin%/}"
|
||||
nginx_env_portal_host="$(nginx_env_origin_host "$nginx_env_origin")" || continue
|
||||
nginx_env_taken_hosts="$nginx_env_taken_hosts $nginx_env_portal_host"
|
||||
done <<NGINX_ENV_TAKEN_ORIGINS
|
||||
$(printf '%s\n' "${CORS_ORIGIN:-}" | tr ',' '\n')
|
||||
NGINX_ENV_TAKEN_ORIGINS
|
||||
for nginx_env_taken in $nginx_env_taken_hosts; do
|
||||
case "$nginx_env_taken" in
|
||||
"autoconfig.$NGINX_MAIL_DOMAIN" | "autodiscover.$NGINX_MAIL_DOMAIN")
|
||||
nginx_env_fail "자동 설정 호스트(autoconfig. · autodiscover.$NGINX_MAIL_DOMAIN)가 포털(CORS_ORIGIN) · 공개 내려받기(SHARE_PUBLIC_URL) 호스트와 겹칩니다 — 그 요청을 가로챈다." ;;
|
||||
esac
|
||||
done
|
||||
NGINX_AUTOCONFIG_SERVER_NAMES="autoconfig.$NGINX_MAIL_DOMAIN autodiscover.$NGINX_MAIL_DOMAIN"
|
||||
echo "nginx-env.envsh: 메일 프로그램 자동 설정 ${NGINX_AUTOCONFIG_SERVER_NAMES} — IMAP · SMTP 호스트 ${NGINX_MAIL_CLIENT_HOST}"
|
||||
fi
|
||||
|
||||
export NGINX_MAIL_ATTACHMENT_BODY_LIMIT NGINX_UPLOAD_BODY_LIMIT NGINX_BACKEND_UPSTREAM NGINX_LOCAL_RESOLVERS \
|
||||
NGINX_SHARE_SERVER_NAME NGINX_PORTAL_SHARE
|
||||
NGINX_SHARE_SERVER_NAME NGINX_PORTAL_SHARE NGINX_AUTOCONFIG_SERVER_NAMES NGINX_MAIL_DOMAIN NGINX_MAIL_CLIENT_HOST
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# 포털 default_server — 웹메일 화면과 /api. 공개 내려받기(/s/ · /api/public-links/)는 공개 호스트를 따로 두면 404 로 닫는다
|
||||
# 공개 내려받기 SHARE_PUBLIC_URL 의 호스트 이름 — /s/<토큰>(화면) · 화면 자산 · /api/public-links/ 만 연다. 그 밖은 404
|
||||
# 공개 호스트를 따로 두지 않으면(SHARE_PUBLIC_URL 이 비었거나 그 호스트 이름이 CORS_ORIGIN 에 있다) 포털이 둘 다 연다 — nginx-env.envsh
|
||||
# 메일 프로그램 자동 설정(13단계) autoconfig. · autodiscover.<MAIL_DOMAIN> — 고정 XML 만 낸다(백엔드로 넘기지 않는다). 파일 끝의 셋째 server
|
||||
#
|
||||
# ⚠️ 백엔드(Express)는 경로의 대소문자를 가리지 않는다 — /api/Public-Links/… · /api/messages/x/LARGE-FILES/… 도 처리기에 닿는다.
|
||||
# 그래서 /api 아래에서 경로로 무엇을 정하는 location(받기의 동시 연결 수 · 버퍼링 끔, 포털의 공개 API 닫기, 조각 본문 한도)은
|
||||
@@ -525,3 +526,83 @@ server {
|
||||
# API 의 nginx 오류 응답 (포털과 같은 파일)
|
||||
include /etc/nginx/snippets/api-errors.conf;
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# 메일 프로그램 자동 설정 (13단계 — mail/README.md "메일 프로그램 자동 설정") — autoconfig.<MAIL_DOMAIN> · autodiscover.<MAIL_DOMAIN>
|
||||
#
|
||||
# 주소만 치면 메일 프로그램이 서버 · 포트를 찾아 채운다. 고정 응답이다 — 사용자가 보낸 값(질의의 emailaddress · Outlook 이 POST 한 주소)을 되돌려 쓰지 않는다
|
||||
# (엔진의 같은 경로는 그 값을 이스케이프 없이 XML 에 넣고, 열지 않은 POP3 · DAV 까지 알려서 쓰지 않는다 — 엔진 443 은 그대로 닫아 둔다).
|
||||
# 알리는 것은 IMAP 993(SSL) · SMTP 465(SSL) 둘뿐 — 호스트는 MAIL_HOSTNAME(엔진 인증서의 이름), 사용자 이름은 메일 주소(%EMAILADDRESS% 는 프로그램이 채운다).
|
||||
# GET /mail/config-v1.1.xml Thunderbird · Thunderbird for Android(K-9) · Evolution 등 (IETF draft-ietf-mailmaint-autoconfig 의 첫 자리)
|
||||
# GET /.well-known/autoconfig/mail/config-v1.1.xml 같은 내용 (그 초안의 둘째 자리 — 원래는 도메인 자체의 주소지만 이 호스트에서도 같게)
|
||||
# POST /autodiscover/autodiscover.xml 클래식 Outlook(POX) — 대소문자를 가리지 않는다. LoginName 을 주지 않는다(되돌려 쓰지 않으려고) —
|
||||
# 그러면 Outlook 은 @ 앞부분으로 로그인하는데 엔진은 그것도 받는다(IMAP · SMTP · 앱 비밀번호 — 2026-10-31 임시 엔진)
|
||||
# 값은 nginx-env.envsh 가 루트 env 의 MAIL_DOMAIN · MAIL_HOSTNAME 에서 만든다 — 비었으면 이 server 의 이름은 어느 요청에도 맞지 않는 autoconfig.invalid 다.
|
||||
# 백엔드로 넘기는 것이 없다(/api 도 404). 외부 리버스 프록시는 이 두 호스트를 포털과 같은 8080 으로 넘기고 Host 를 그대로 둔다 — DNS · Proxy Host 는 전환 때
|
||||
# (전환 전 DNS 는 옛 서버를 가리킨다 — mail/README.md "메일 프로그램 자동 설정")
|
||||
# ==========================================
|
||||
server {
|
||||
listen 8080;
|
||||
server_name ${NGINX_AUTOCONFIG_SERVER_NAMES};
|
||||
|
||||
# 접근 로그 — 포털과 같은 형식(쿼리 없음 — 질의에 메일 주소가 실린다)
|
||||
access_log /var/log/nginx/access.log main_rid;
|
||||
# Outlook 이 POST 하는 요청은 1KB 안팎의 XML 이다
|
||||
client_max_body_size 64k;
|
||||
|
||||
default_type text/xml;
|
||||
charset utf-8;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer" always;
|
||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
|
||||
location = /mail/config-v1.1.xml {
|
||||
return 200 '<?xml version="1.0" encoding="UTF-8"?>
|
||||
<clientConfig version="1.1">
|
||||
<emailProvider id="${NGINX_MAIL_DOMAIN}">
|
||||
<domain>${NGINX_MAIL_DOMAIN}</domain>
|
||||
<displayName>${NGINX_MAIL_DOMAIN}</displayName>
|
||||
<displayShortName>${NGINX_MAIL_DOMAIN}</displayShortName>
|
||||
<incomingServer type="imap">
|
||||
<hostname>${NGINX_MAIL_CLIENT_HOST}</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</incomingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>${NGINX_MAIL_CLIENT_HOST}</hostname>
|
||||
<port>465</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</outgoingServer>
|
||||
</emailProvider>
|
||||
</clientConfig>
|
||||
';
|
||||
}
|
||||
|
||||
location = /.well-known/autoconfig/mail/config-v1.1.xml {
|
||||
rewrite ^ /mail/config-v1.1.xml last;
|
||||
}
|
||||
|
||||
location ~* ^/autodiscover/autodiscover\.xml$ {
|
||||
return 200 '<?xml version="1.0" encoding="utf-8"?>
|
||||
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
|
||||
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
|
||||
<Account>
|
||||
<AccountType>email</AccountType>
|
||||
<Action>settings</Action>
|
||||
<Protocol><Type>IMAP</Type><Server>${NGINX_MAIL_CLIENT_HOST}</Server><Port>993</Port><DomainRequired>off</DomainRequired><SPA>off</SPA><SSL>on</SSL><AuthRequired>on</AuthRequired></Protocol>
|
||||
<Protocol><Type>SMTP</Type><Server>${NGINX_MAIL_CLIENT_HOST}</Server><Port>465</Port><DomainRequired>off</DomainRequired><SPA>off</SPA><SSL>on</SSL><AuthRequired>on</AuthRequired></Protocol>
|
||||
</Account>
|
||||
</Response>
|
||||
</Autodiscover>
|
||||
';
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user