#!/bin/sh # ========================================== # nginx 템플릿(nginx.conf)에 넣을 값을 만든다 — nginx 이미지의 docker-entrypoint 가 envsubst 전에 source 한다 # (Dockerfile 이 /docker-entrypoint.d/16-nginx-env.envsh 로 넣는다. 실행 권한이 있어야 읽힌다) # # 본문 한도는 백엔드 한도와 같은 루트 env 에서 만든다 → 루트 env 한 곳만 고치면 백엔드와 nginx 가 함께 바뀐다. # compose 는 정의되지 않은 변수를 빈 문자열로 넣으므로 빈 값은 백엔드 기본값으로 대신한다. # 값이 잘못됐으면 기본값으로 조용히 넘어가지 않고 컨테이너 기동을 멈춘다 (fail-fast — .claude/rules/env-structure.md). # # ⚠️ source 되는 파일이라 entrypoint 셸과 이름·옵션을 함께 쓴다 — set -e/-u 를 걸지 않고, 이름은 nginx_env_ 로 시작한다. # exit 하면 entrypoint 가 끝나 컨테이너가 뜨지 않는다 (그게 의도다). # ========================================== nginx_env_fail() { echo "nginx-env.envsh: $1" >&2 exit 1 } # $1(변수 이름)의 값이 양의 십진 정수면 그 값, 비어 있으면 $2(기본값). 그 밖은 기동 중단. # 앞자리 0 은 받지 않는다 — 셸 산술이 8진수로 읽어(010 → 8) 백엔드와 한도가 어긋나거나 08 에서 오류가 난다. # 6자리(999999MB)를 넘는 값도 받지 않는다 — 실수로 넣은 값이 산술에서 넘치지 않게. nginx_env_positive_int() { eval "nginx_env_value=\${$1:-}" if [ -z "$nginx_env_value" ]; then echo "$2" return fi case "$nginx_env_value" in 0* | *[!0-9]*) nginx_env_fail "$1 는 0 으로 시작하지 않는 양의 정수(MB)여야 합니다 (현재: \"$nginx_env_value\")." ;; esac if [ "${#nginx_env_value}" -gt 6 ]; then nginx_env_fail "$1 가 너무 큽니다 (현재: \"$nginx_env_value\", 최대 999999)." fi echo "$nginx_env_value" } # 멀티파트 경계·헤더 몫의 여유 (MB) — 백엔드 한도에 딱 맞는 파일도 nginx 에서 막히지 않게 nginx_body_margin_mb=1 # 백엔드 기본값과 같다: MAIL_ATTACHMENT_MAX_SIZE_MB=20 (modules/mail/mail.settings.ts) · UPLOAD_MAX_SIZE_MB=5 # (명령 치환 안에서 실패하면 그 서브셸만 끝나므로, 값을 먼저 받아 실패를 여기서 확인한다) nginx_env_mail_mb="$(nginx_env_positive_int MAIL_ATTACHMENT_MAX_SIZE_MB 20)" || exit 1 nginx_env_upload_mb="$(nginx_env_positive_int UPLOAD_MAX_SIZE_MB 5)" || exit 1 NGINX_MAIL_ATTACHMENT_BODY_LIMIT="$((nginx_env_mail_mb + nginx_body_margin_mb))m" NGINX_UPLOAD_BODY_LIMIT="$((nginx_env_upload_mb + nginx_body_margin_mb))m" # 백엔드 주소 — compose 가 http://-backend: 를 준다. # 서비스 이름(backend)은 쓰지 않는다: proxy 네트워크를 다른 프로젝트와 함께 쓰면 그쪽 backend 로 풀릴 수 있다. if [ -z "${NGINX_BACKEND_UPSTREAM:-}" ]; then [ -n "${PROJECT_NAME:-}" ] || nginx_env_fail "NGINX_BACKEND_UPSTREAM(예: http://-backend:3000) 또는 PROJECT_NAME 이 필요합니다." NGINX_BACKEND_UPSTREAM="http://${PROJECT_NAME}-backend:3000" fi # http://호스트[:포트] 만 받는다. 경로(끝의 / 포함)가 붙으면 proxy_pass 가 모든 /api 요청을 그 경로 하나로 보내고, # 공백·; 등은 설정 문법을 깨거나 지시어를 끼워 넣을 수 있다. nginx_env_upstream_host="${NGINX_BACKEND_UPSTREAM#http://}" if [ "$nginx_env_upstream_host" = "$NGINX_BACKEND_UPSTREAM" ] || [ -z "$nginx_env_upstream_host" ]; then nginx_env_fail "NGINX_BACKEND_UPSTREAM 은 http://호스트[:포트] 형식이어야 합니다 (현재: \"$NGINX_BACKEND_UPSTREAM\")." fi case "$nginx_env_upstream_host" in *[!A-Za-z0-9.:_-]*) nginx_env_fail "NGINX_BACKEND_UPSTREAM 에는 경로·쿼리·공백을 넣지 않습니다 — http://호스트[:포트] 만 (현재: \"$NGINX_BACKEND_UPSTREAM\")." ;; :* | *:*:* | *:*[!0-9]* | *:) nginx_env_fail "NGINX_BACKEND_UPSTREAM 의 호스트·포트가 올바르지 않습니다 (현재: \"$NGINX_BACKEND_UPSTREAM\")." ;; esac # 도커 내장 DNS — 15-local-resolvers.envsh 가 /etc/resolv.conf 에서 읽어 둔다 (Dockerfile 의 NGINX_ENTRYPOINT_LOCAL_RESOLVERS=1) NGINX_LOCAL_RESOLVERS="${NGINX_LOCAL_RESOLVERS:-127.0.0.11}" # ------------------------------------------ # 대용량 첨부의 공개 내려받기 호스트 (11단계 — docs/mail/api-contract.md 4절 "공개 호스트") # # SHARE_PUBLIC_URL(출처 — http(s)://호스트[:포트])의 호스트 이름을 포털(CORS_ORIGIN — 콤마로 여럿)의 호스트 이름들과 견준다. # 백엔드(modules/file-sharing/file-sharing.settings.ts 의 dedicatedPublicHost)와 같은 판정이다 — 포트는 보지 않는다(nginx 는 포트 없는 Host 를 넘긴다): # 다르면 공개 내려받기 server 의 이름 = 그 호스트 이름, 포털은 /s/ · /api/public-links/ 를 404 로 닫는다(share-closed · share-api-closed) # 같거나 SHARE_PUBLIC_URL 이 비었으면 포털이 함께 연다(share-open · share-api-open), 공개 내려받기 server 는 어느 요청에도 맞지 않는 이름(share.invalid — RFC 2606) # SHARE_PUBLIC_URL 의 모양이 틀리면 기동을 멈춘다 — 경로 · 공백 · ; 등이 server_name 에 들어가 설정을 깨거나 지시어를 끼워 넣지 않게. # CORS_ORIGIN 의 모양은 백엔드가 본다(운영은 틀리면 백엔드가 뜨지 않는다) — 여기서는 읽을 수 있는 주소만 견준다. # ------------------------------------------ # 출처 → 호스트 이름 (소문자, 포트 없이). 모양이 틀리면 아무것도 내지 않고 1 nginx_env_origin_host() { case "$1" in http://*) nginx_env_hostport="${1#http://}" ;; https://*) nginx_env_hostport="${1#https://}" ;; *) return 1 ;; esac # 영문 · 숫자 · . - : 만 — 경로(/) · 쿼리 · 공백 · 대괄호(IPv6) · 사용자 정보(@) 를 받지 않는다 case "$nginx_env_hostport" in "" | *[!A-Za-z0-9.:-]*) return 1 ;; esac nginx_env_host="${nginx_env_hostport%%:*}" nginx_env_port="${nginx_env_hostport#"$nginx_env_host"}" case "$nginx_env_port" in "") ;; :0* | : | :*[!0-9]* | :??????*) return 1 ;; esac # 빈 이름 · 점으로 시작 · 끝(끝 점 — nginx 의 $host 는 끝 점을 떼어 백엔드와 어긋난다) · 연이은 점 · 하이픈으로 시작 · 끝 case "$nginx_env_host" in "" | .* | *. | *..* | -* | *-) return 1 ;; esac printf '%s' "$nginx_env_host" | tr 'A-Z' 'a-z' } nginx_env_share_url="${SHARE_PUBLIC_URL:-}" nginx_env_share_host="" if [ -n "$nginx_env_share_url" ]; then nginx_env_share_host="$(nginx_env_origin_host "$nginx_env_share_url")" \ || nginx_env_fail "SHARE_PUBLIC_URL 은 공개 내려받기 화면의 출처(http(s)://호스트[:포트] — 경로 · 끝의 / · 끝 점 없이)여야 합니다 (예: https://files.arasystem.it.kr, 현재: \"$nginx_env_share_url\")." fi nginx_env_same_host="" if [ -z "$nginx_env_share_host" ]; then nginx_env_same_host=1 else # 콤마로 나눠 하나씩 — 앞뒤 공백과 끝의 / 하나는 떼고 견준다 (백엔드도 출처로 고쳐 견준다) 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 if [ "$nginx_env_portal_host" = "$nginx_env_share_host" ]; then nginx_env_same_host=1 fi done < · autodiscover., 알리는 호스트는 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 <