도메인 생성 공통 스크립트 nginx 미반영 상태 복구 처리 추가
인증서 파일이 사라진 호스트가 있으면 nginx 설정 검사가 실패해 NPM의 모든 등록/수정이 500으로 막힌다. 이때 NPM은 DB 변경은 되돌리지 않아 레코드만 남고, 다음 실행에서 "이미 존재"로 판정돼 도메인이 죽은 채로 고착됐다. - 기존 도메인이 있어도 SSL 미적용/nginx 미반영이면 이어서 복구 - 생성·발급 실패 시 남은 레코드를 정리해 고착 방지 - 인증서 파일이 사라진 호스트를 찾아 안내 (unblock으로 비활성화) - PUT 성공과 무관하게 nginx 반영 여부를 확인 후 완료 처리 - jq 설치에 sudo 누락 수정
This commit is contained in:
+350
-76
@@ -28,6 +28,19 @@ inputs:
|
|||||||
required: false
|
required: false
|
||||||
default: "true"
|
default: "true"
|
||||||
description: 웹소켓 지원 활성화
|
description: 웹소켓 지원 활성화
|
||||||
|
repair:
|
||||||
|
required: false
|
||||||
|
default: "true"
|
||||||
|
description: >
|
||||||
|
이미 등록된 도메인이 미완성(SSL 미적용) 또는 nginx 미반영 상태면 이어서 복구합니다.
|
||||||
|
false면 기존 동작대로 손대지 않고 종료합니다.
|
||||||
|
unblock:
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
description: >
|
||||||
|
nginx 반영에 실패한 다른 호스트를 비활성화해 NPM 전체의 설정 반영 막힘을 해소합니다.
|
||||||
|
인증서 파일이 사라진 호스트가 하나라도 있으면 모든 도메인의 등록/수정이 막히기 때문입니다.
|
||||||
|
다른 팀의 도메인을 중단시킬 수 있으므로 기본값은 false입니다.
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
host-id:
|
host-id:
|
||||||
@@ -36,6 +49,9 @@ outputs:
|
|||||||
created:
|
created:
|
||||||
description: 이번 실행에서 새로 생성했으면 true, 이미 있었으면 false
|
description: 이번 실행에서 새로 생성했으면 true, 이미 있었으면 false
|
||||||
value: ${{ steps.create.outputs.created }}
|
value: ${{ steps.create.outputs.created }}
|
||||||
|
repaired:
|
||||||
|
description: 이번 실행에서 기존 호스트를 복구했으면 true
|
||||||
|
value: ${{ steps.create.outputs.repaired }}
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
@@ -50,13 +66,16 @@ runs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SUDO=""
|
||||||
|
[ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||||
|
|
||||||
# 패키지 매니저에 따라 jq를 설치합니다.
|
# 패키지 매니저에 따라 jq를 설치합니다.
|
||||||
if command -v apt-get >/dev/null 2>&1; then
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
apt-get update && apt-get install -y jq
|
$SUDO apt-get update && $SUDO apt-get install -y jq
|
||||||
elif command -v apk >/dev/null 2>&1; then
|
elif command -v apk >/dev/null 2>&1; then
|
||||||
apk add --no-cache jq
|
$SUDO apk add --no-cache jq
|
||||||
elif command -v yum >/dev/null 2>&1; then
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
yum install -y jq
|
$SUDO yum install -y jq
|
||||||
else
|
else
|
||||||
echo "jq 설치 실패: 지원하지 않는 러너 이미지"
|
echo "jq 설치 실패: 지원하지 않는 러너 이미지"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -74,6 +93,8 @@ runs:
|
|||||||
FORWARD_HOST: ${{ inputs.forward-host }}
|
FORWARD_HOST: ${{ inputs.forward-host }}
|
||||||
FORWARD_PORT: ${{ inputs.forward-port }}
|
FORWARD_PORT: ${{ inputs.forward-port }}
|
||||||
WEBSOCKETS: ${{ inputs.websockets }}
|
WEBSOCKETS: ${{ inputs.websockets }}
|
||||||
|
REPAIR: ${{ inputs.repair }}
|
||||||
|
UNBLOCK: ${{ inputs.unblock }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
@@ -81,9 +102,21 @@ runs:
|
|||||||
BASE="http://${NPM_HOST}:${NPM_PORT}/api" # NPM_SCHEME=http 고정
|
BASE="http://${NPM_HOST}:${NPM_PORT}/api" # NPM_SCHEME=http 고정
|
||||||
FORWARD_SCHEME="http" # 대상 프로토콜 http 고정
|
FORWARD_SCHEME="http" # 대상 프로토콜 http 고정
|
||||||
|
|
||||||
# NPM API 호출 헬퍼: 응답 본문과 상태코드를 분리해, 2xx가 아니면
|
WORKDIR="$(mktemp -d)"
|
||||||
# NPM이 보낸 에러 본문을 그대로 출력하고 실패시킵니다.
|
trap 'rm -rf "$WORKDIR"' EXIT
|
||||||
# 사용법: api <METHOD> <PATH> [JSON_DATA] (성공 시 응답 본문을 stdout으로)
|
|
||||||
|
REPAIRED=false
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# NPM API 호출 헬퍼
|
||||||
|
#
|
||||||
|
# 주의: NPM은 설정을 DB에 먼저 쓰고 나서 nginx를 reload합니다.
|
||||||
|
# reload가 실패하면 500을 반환하지만 DB 변경은 되돌리지 않습니다.
|
||||||
|
# 즉 POST/PUT/DELETE의 500은 "아무 일도 없었다"는 뜻이 아닙니다.
|
||||||
|
# 반면 disable/enable은 실패해도 200을 주고 meta.nginx_online에
|
||||||
|
# 결과를 기록합니다. 그래서 반영 여부 판단은 상태코드가 아니라
|
||||||
|
# meta.nginx_online으로 합니다.
|
||||||
|
# ---------------------------------------------------------------
|
||||||
api() {
|
api() {
|
||||||
local method="$1" path="$2" data="${3:-}" resp code body
|
local method="$1" path="$2" data="${3:-}" resp code body
|
||||||
if [ -n "$data" ]; then
|
if [ -n "$data" ]; then
|
||||||
@@ -101,6 +134,176 @@ runs:
|
|||||||
printf '%s' "$body"
|
printf '%s' "$body"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 도메인으로 프록시 호스트를 찾습니다. 없으면 빈 문자열.
|
||||||
|
find_host() {
|
||||||
|
api GET "/nginx/proxy-hosts" \
|
||||||
|
| jq -c --arg d "$DOMAIN" 'map(select(.domain_names | index($d))) | .[0] // empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 프록시 호스트 페이로드를 만듭니다. $1=certificate_id $2=ssl_forced $3=http2_support
|
||||||
|
host_payload() {
|
||||||
|
jq -nc \
|
||||||
|
--arg d "$DOMAIN" \
|
||||||
|
--arg fh "$FORWARD_HOST" \
|
||||||
|
--arg fp "$FORWARD_PORT" \
|
||||||
|
--arg fs "$FORWARD_SCHEME" \
|
||||||
|
--argjson cert "$1" \
|
||||||
|
--argjson ssl "$2" \
|
||||||
|
--argjson h2 "$3" \
|
||||||
|
--argjson ws "$WS" \
|
||||||
|
'{domain_names:[$d],
|
||||||
|
forward_host:$fh,
|
||||||
|
forward_port:($fp|tonumber),
|
||||||
|
forward_scheme:$fs,
|
||||||
|
certificate_id:$cert,
|
||||||
|
ssl_forced:$ssl,
|
||||||
|
http2_support:$h2,
|
||||||
|
hsts_enabled:false,
|
||||||
|
hsts_subdomains:false,
|
||||||
|
caching_enabled:false,
|
||||||
|
allow_websocket_upgrade:$ws,
|
||||||
|
block_exploits:true,
|
||||||
|
access_list_id:0,
|
||||||
|
advanced_config:"",
|
||||||
|
locations:[],
|
||||||
|
meta:{}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# disable → enable 로 nginx 설정 파일을 다시 쓰게 만듭니다.
|
||||||
|
# POST/PUT이 500으로 실패해 설정 파일이 생성되지 않은 호스트를
|
||||||
|
# 되살리는 유일한 API 경로입니다.
|
||||||
|
recycle_host() {
|
||||||
|
echo " nginx 반영 재시도 (disable → enable): host id=$1"
|
||||||
|
api POST "/nginx/proxy-hosts/$1/disable" >/dev/null 2>&1 || true
|
||||||
|
sleep 2
|
||||||
|
api POST "/nginx/proxy-hosts/$1/enable" >/dev/null 2>&1 || true
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# 호스트가 실제로 nginx에 반영됐는지 확인하고, 아니면 한 번 복구를 시도합니다.
|
||||||
|
ensure_online() {
|
||||||
|
local id="$1" j
|
||||||
|
j="$(api GET "/nginx/proxy-hosts/$id")" || return 1
|
||||||
|
if printf '%s' "$j" | jq -e '.meta.nginx_online == true' >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
recycle_host "$id"
|
||||||
|
REPAIRED=true
|
||||||
|
|
||||||
|
j="$(api GET "/nginx/proxy-hosts/$id")" || return 1
|
||||||
|
if printf '%s' "$j" | jq -e '.meta.nginx_online == true' >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "nginx 반영 실패 (host id=$id):" >&2
|
||||||
|
printf '%s' "$j" | jq -r '.meta.nginx_err // "(NPM이 오류 내용을 남기지 않았습니다)"' >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 500으로 실패했지만 DB에는 남았을 수 있는 레코드를 정리합니다.
|
||||||
|
# 이걸 남겨두면 다음 배포에서 "이미 존재"로 오판해 도메인이 영구히
|
||||||
|
# 죽은 상태로 고착됩니다.
|
||||||
|
cleanup_orphan() {
|
||||||
|
local j id
|
||||||
|
j="$(find_host || true)"
|
||||||
|
id="$(printf '%s' "$j" | jq -r '.id // empty' 2>/dev/null || true)"
|
||||||
|
if [ -n "$id" ]; then
|
||||||
|
echo " 실패로 남은 레코드를 정리합니다: host id=$id"
|
||||||
|
# DELETE도 막힌 상태에서는 500을 주지만 삭제 자체는 수행됩니다.
|
||||||
|
api DELETE "/nginx/proxy-hosts/$id" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 인증서 파일이 디스크에서 사라졌는지 확인합니다.
|
||||||
|
# download 엔드포인트는 실제 파일을 읽으므로, 파일이 없으면 404를 줍니다.
|
||||||
|
# 200=파일 있음 404=파일 없음(이 인증서를 쓰는 호스트가 NPM 전체를 막습니다)
|
||||||
|
# 400=Let's Encrypt 인증서가 아님(판별 불가)
|
||||||
|
# 인증서마다 한 번만 조회하도록 결과를 캐시합니다.
|
||||||
|
cert_file_missing() {
|
||||||
|
local cid="$1" cache="$WORKDIR/cert-$1" code
|
||||||
|
if [ -f "$cache" ]; then
|
||||||
|
[ "$(cat "$cache")" = "missing" ]
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
code="$(curl -sS -o /dev/null -w '%{http_code}' \
|
||||||
|
"${BASE}/nginx/certificates/${cid}/download" -H "$AUTH" 2>/dev/null || echo 000)"
|
||||||
|
if [ "$code" = "404" ]; then
|
||||||
|
echo missing > "$cache"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo present > "$cache"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# NPM의 설정 반영을 막고 있는 호스트를 찾습니다.
|
||||||
|
# 결과는 전역 배열 BLOCKER_IDS(비활성화 대상)와 BLOCKER_LINES(출력용),
|
||||||
|
# 그리고 개수 BLOCKING_COUNT/FINDING_COUNT에 담깁니다.
|
||||||
|
# (구형 bash에서 set -u와 빈 배열이 충돌하므로 개수는 별도 변수로 셉니다.)
|
||||||
|
scan_blockers() {
|
||||||
|
BLOCKER_IDS=()
|
||||||
|
BLOCKER_LINES=()
|
||||||
|
BLOCKING_COUNT=0
|
||||||
|
FINDING_COUNT=0
|
||||||
|
|
||||||
|
local hosts hid cid names online reason
|
||||||
|
hosts="$(api GET "/nginx/proxy-hosts")" || return 0
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r hid cid names online; do
|
||||||
|
[ -z "${hid:-}" ] && continue
|
||||||
|
|
||||||
|
reason=""
|
||||||
|
if [ "$cid" != "0" ] && cert_file_missing "$cid"; then
|
||||||
|
# 이 호스트가 nginx 설정 검사를 실패시켜 NPM 전체를 막습니다.
|
||||||
|
reason="인증서 파일이 사라짐 (cert id=${cid}) — NPM 전체를 막습니다"
|
||||||
|
BLOCKER_IDS+=("$hid")
|
||||||
|
BLOCKING_COUNT=$((BLOCKING_COUNT + 1))
|
||||||
|
elif [ "$online" != "true" ]; then
|
||||||
|
# NPM이 이미 격리한 호스트입니다. 다른 도메인을 막지는 않습니다.
|
||||||
|
reason="nginx 미반영 (이 도메인만 동작하지 않음)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$reason" ]; then
|
||||||
|
BLOCKER_LINES+=(" - id=${hid} ${names} — ${reason}")
|
||||||
|
FINDING_COUNT=$((FINDING_COUNT + 1))
|
||||||
|
fi
|
||||||
|
done <<< "$(printf '%s' "$hosts" | jq -r --arg d "$DOMAIN" \
|
||||||
|
'.[]
|
||||||
|
| select(.enabled == true and ((.domain_names | index($d)) | not))
|
||||||
|
| [.id, .certificate_id, (.domain_names | join(",")), (.meta.nginx_online // false | tostring)]
|
||||||
|
| @tsv')"
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnose_blocked() {
|
||||||
|
scan_blockers
|
||||||
|
if [ "$FINDING_COUNT" -gt 0 ]; then
|
||||||
|
echo "" >&2
|
||||||
|
echo "문제가 있는 프록시 호스트:" >&2
|
||||||
|
printf '%s\n' "${BLOCKER_LINES[@]+"${BLOCKER_LINES[@]}"}" >&2
|
||||||
|
fi
|
||||||
|
cat >&2 <<'MSG'
|
||||||
|
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
NPM이 nginx 설정을 반영하지 못하는 상태로 보입니다.
|
||||||
|
|
||||||
|
인증서 파일이 사라진 프록시 호스트가 하나라도 있으면, nginx 설정
|
||||||
|
검사가 통째로 실패해 NPM의 모든 등록/수정이 500으로 막힙니다.
|
||||||
|
내 도메인과 무관한 호스트가 원인일 수 있습니다.
|
||||||
|
|
||||||
|
해소 방법
|
||||||
|
1. unblock: "true" 로 이 액션을 다시 실행하거나
|
||||||
|
2. NPM 관리 화면에서 아래 목록의 호스트를 Disable 하거나
|
||||||
|
3. NPM 컨테이너에서 직접 정리
|
||||||
|
docker exec <npm> nginx -t
|
||||||
|
docker exec <npm> rm -f /data/nginx/proxy_host/<id>.conf
|
||||||
|
docker exec <npm> nginx -s reload
|
||||||
|
|
||||||
|
주의: 이 상태로 NPM을 재시작하면 nginx가 아예 기동하지 못해
|
||||||
|
모든 도메인과 관리 화면이 함께 중단됩니다.
|
||||||
|
────────────────────────────────────────────────────────────
|
||||||
|
MSG
|
||||||
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 1. 토큰 발급 (로그인)
|
# 1. 토큰 발급 (로그인)
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
@@ -119,55 +322,107 @@ runs:
|
|||||||
WS=$([ "$WEBSOCKETS" = "true" ] && echo true || echo false)
|
WS=$([ "$WEBSOCKETS" = "true" ] && echo true || echo false)
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 2. 기존 프록시 호스트 확인 → 있으면 즉시 종료 (덮어쓰지 않음)
|
# 2. 사전 점검 — nginx에 반영되지 않은 호스트 확인
|
||||||
# 이미 등록된 도메인은 기존 설정을 보존하기 위해 아무것도 건드리지 않습니다.
|
# 이 중 인증서를 잃은 호스트가 NPM 전체의 반영을 막습니다.
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
HOST_ID=$(api GET "/nginx/proxy-hosts" \
|
scan_blockers
|
||||||
| jq -r --arg d "$DOMAIN" \
|
|
||||||
'map(select(.domain_names | index($d))) | .[0].id // empty')
|
|
||||||
|
|
||||||
if [ -n "$HOST_ID" ]; then
|
if [ "$FINDING_COUNT" -gt 0 ]; then
|
||||||
echo "이미 존재하는 도메인입니다. 덮어쓰지 않고 종료합니다: ${DOMAIN} (host id=${HOST_ID})"
|
echo "경고: 문제가 있는 프록시 호스트 ${FINDING_COUNT}건을 발견했습니다."
|
||||||
echo "host-id=${HOST_ID}" >> "$GITHUB_OUTPUT"
|
printf '%s\n' "${BLOCKER_LINES[@]+"${BLOCKER_LINES[@]}"}"
|
||||||
echo "created=false" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
if [ "$BLOCKING_COUNT" -gt 0 ]; then
|
||||||
|
if [ "$UNBLOCK" = "true" ]; then
|
||||||
|
echo " unblock=true — 막고 있는 호스트를 비활성화합니다."
|
||||||
|
for bid in "${BLOCKER_IDS[@]+"${BLOCKER_IDS[@]}"}"; do
|
||||||
|
api POST "/nginx/proxy-hosts/${bid}/disable" >/dev/null 2>&1 || true
|
||||||
|
echo " host id=${bid} 비활성화"
|
||||||
|
done
|
||||||
|
sleep 2
|
||||||
|
else
|
||||||
|
echo " 인증서를 잃은 호스트가 있어 아래 작업이 실패할 수 있습니다."
|
||||||
|
echo " 해소하려면 unblock: \"true\" 로 실행하세요."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 3. HTTP 전용 프록시 호스트 먼저 생성 (SSL 없이)
|
# 3. 기존 프록시 호스트 확인
|
||||||
# 도메인이 80포트로 응답해야 Let's Encrypt HTTP-01 검증이 통과합니다.
|
# 존재하더라도 SSL이 붙지 않았거나 nginx에 반영되지 않았으면
|
||||||
|
# 미완성으로 보고 이어서 복구합니다. 존재 여부만 보고 건너뛰면
|
||||||
|
# 한 번 실패한 도메인이 영구히 죽은 채로 고착됩니다.
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
EXISTING="$(find_host)"
|
||||||
|
HOST_ID=""
|
||||||
|
CREATED=false
|
||||||
|
|
||||||
|
if [ -n "$EXISTING" ]; then
|
||||||
|
HOST_ID="$(printf '%s' "$EXISTING" | jq -r '.id')"
|
||||||
|
|
||||||
|
if printf '%s' "$EXISTING" \
|
||||||
|
| jq -e '.certificate_id != 0 and .meta.nginx_online == true' >/dev/null 2>&1; then
|
||||||
|
echo "이미 완성된 도메인입니다. 덮어쓰지 않고 종료합니다: ${DOMAIN} (host id=${HOST_ID})"
|
||||||
|
{
|
||||||
|
echo "host-id=${HOST_ID}"
|
||||||
|
echo "created=false"
|
||||||
|
echo "repaired=false"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$REPAIR" != "true" ]; then
|
||||||
|
echo "이미 존재하는 도메인입니다. 덮어쓰지 않고 종료합니다: ${DOMAIN} (host id=${HOST_ID})"
|
||||||
|
printf '%s' "$EXISTING" \
|
||||||
|
| jq -r '" 주의: certificate_id=\(.certificate_id) nginx_online=\(.meta.nginx_online // "null") — 미완성 상태입니다."'
|
||||||
|
echo " repair: \"true\" 로 실행하면 이어서 복구합니다."
|
||||||
|
{
|
||||||
|
echo "host-id=${HOST_ID}"
|
||||||
|
echo "created=false"
|
||||||
|
echo "repaired=false"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "미완성 상태의 기존 도메인을 복구합니다: ${DOMAIN} (host id=${HOST_ID})"
|
||||||
|
printf '%s' "$EXISTING" \
|
||||||
|
| jq -r '" 현재 상태: certificate_id=\(.certificate_id) nginx_online=\(.meta.nginx_online // "null")"'
|
||||||
|
REPAIRED=true
|
||||||
|
else
|
||||||
|
# -------------------------------------------------------------
|
||||||
|
# 4. HTTP 전용 프록시 호스트 먼저 생성 (SSL 없이)
|
||||||
|
# 도메인이 80포트로 응답해야 Let's Encrypt HTTP-01 검증이 통과합니다.
|
||||||
|
# -------------------------------------------------------------
|
||||||
echo "HTTP 프록시 호스트 생성 중: ${DOMAIN} -> ${FORWARD_SCHEME}://${FORWARD_HOST}:${FORWARD_PORT}"
|
echo "HTTP 프록시 호스트 생성 중: ${DOMAIN} -> ${FORWARD_SCHEME}://${FORWARD_HOST}:${FORWARD_PORT}"
|
||||||
HTTP_PAYLOAD=$(jq -nc \
|
if ! CREATE_RESP="$(api POST "/nginx/proxy-hosts" "$(host_payload 0 false false)")"; then
|
||||||
--arg d "$DOMAIN" \
|
echo "프록시 호스트 생성 실패: ${DOMAIN}" >&2
|
||||||
--arg fh "$FORWARD_HOST" \
|
diagnose_blocked
|
||||||
--arg fp "$FORWARD_PORT" \
|
cleanup_orphan
|
||||||
--arg fs "$FORWARD_SCHEME" \
|
exit 1
|
||||||
--argjson ws "$WS" \
|
fi
|
||||||
'{domain_names:[$d],
|
|
||||||
forward_host:$fh,
|
HOST_ID="$(printf '%s' "$CREATE_RESP" | jq -r '.id // empty')"
|
||||||
forward_port:($fp|tonumber),
|
if [ -z "$HOST_ID" ]; then
|
||||||
forward_scheme:$fs,
|
echo "프록시 호스트 생성 실패: ${DOMAIN} (응답에 id가 없습니다)" >&2
|
||||||
certificate_id:0,
|
cleanup_orphan
|
||||||
ssl_forced:false,
|
exit 1
|
||||||
http2_support:false,
|
fi
|
||||||
hsts_enabled:false,
|
CREATED=true
|
||||||
hsts_subdomains:false,
|
fi
|
||||||
caching_enabled:false,
|
|
||||||
allow_websocket_upgrade:$ws,
|
# ---------------------------------------------------------------
|
||||||
block_exploits:true,
|
# 5. 호스트가 실제로 nginx에 반영됐는지 확인
|
||||||
access_list_id:0,
|
# 여기서 80포트 응답이 확보돼야 Let's Encrypt 검증이 통과합니다.
|
||||||
advanced_config:"",
|
# ---------------------------------------------------------------
|
||||||
locations:[],
|
if ! ensure_online "$HOST_ID"; then
|
||||||
meta:{}}')
|
diagnose_blocked
|
||||||
HOST_ID=$(api POST "/nginx/proxy-hosts" "$HTTP_PAYLOAD" | jq -r '.id')
|
if [ "$CREATED" = "true" ]; then
|
||||||
if [ -z "$HOST_ID" ] || [ "$HOST_ID" = "null" ]; then
|
cleanup_orphan
|
||||||
echo "프록시 호스트 생성 실패: ${DOMAIN}"
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 4. Let's Encrypt 인증서 발급 (HTTP 호스트가 떠 있는 상태에서 검증)
|
# 6. Let's Encrypt 인증서 발급 (HTTP 호스트가 떠 있는 상태에서 검증)
|
||||||
# 같은 도메인 인증서가 이미 있으면 재사용합니다.
|
# 같은 도메인 인증서가 이미 있으면 재사용합니다.
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
CERT_ID=$(api GET "/nginx/certificates" \
|
CERT_ID=$(api GET "/nginx/certificates" \
|
||||||
@@ -178,15 +433,25 @@ runs:
|
|||||||
echo "Let's Encrypt 인증서 발급 중: ${DOMAIN}"
|
echo "Let's Encrypt 인증서 발급 중: ${DOMAIN}"
|
||||||
# 이메일/약관동의는 NPM 설정(Settings > Default Let's Encrypt)에서 가져오므로
|
# 이메일/약관동의는 NPM 설정(Settings > Default Let's Encrypt)에서 가져오므로
|
||||||
# 요청 meta에는 dns_challenge만 보냅니다. (스키마가 그 외 키를 거부함)
|
# 요청 meta에는 dns_challenge만 보냅니다. (스키마가 그 외 키를 거부함)
|
||||||
CERT_ID=$(api POST "/nginx/certificates" \
|
if ! CERT_RESP="$(api POST "/nginx/certificates" \
|
||||||
"$(jq -nc --arg d "$DOMAIN" \
|
"$(jq -nc --arg d "$DOMAIN" \
|
||||||
'{provider:"letsencrypt",
|
'{provider:"letsencrypt",
|
||||||
domain_names:[$d],
|
domain_names:[$d],
|
||||||
meta:{dns_challenge:false}}')" \
|
meta:{dns_challenge:false}}')")"; then
|
||||||
| jq -r '.id')
|
echo "인증서 발급 실패: ${DOMAIN} (도메인 DNS가 NPM을 가리키는지 확인하세요)" >&2
|
||||||
|
if [ "$CREATED" = "true" ]; then
|
||||||
|
echo " SSL 없는 호스트를 남기지 않기 위해 방금 만든 호스트를 정리합니다." >&2
|
||||||
|
cleanup_orphan
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CERT_ID="$(printf '%s' "$CERT_RESP" | jq -r '.id // empty')"
|
||||||
if [ -z "$CERT_ID" ] || [ "$CERT_ID" = "null" ]; then
|
if [ -z "$CERT_ID" ] || [ "$CERT_ID" = "null" ]; then
|
||||||
echo "인증서 발급 실패: ${DOMAIN} (도메인 DNS가 NPM을 가리키는지 확인하세요)"
|
echo "인증서 발급 실패: ${DOMAIN} (도메인 DNS가 NPM을 가리키는지 확인하세요)" >&2
|
||||||
|
if [ "$CREATED" = "true" ]; then
|
||||||
|
cleanup_orphan
|
||||||
|
fi
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@@ -194,34 +459,43 @@ runs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# 5. 프록시 호스트에 SSL 적용 (PUT 업데이트: SSL 강제 + HTTP/2)
|
# 7. 프록시 호스트에 SSL 적용 (PUT 업데이트: SSL 강제 + HTTP/2)
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
echo "프록시 호스트에 SSL 적용 중: ${DOMAIN} (cert id=${CERT_ID})"
|
echo "프록시 호스트에 SSL 적용 중: ${DOMAIN} (cert id=${CERT_ID})"
|
||||||
SSL_PAYLOAD=$(jq -nc \
|
if ! api PUT "/nginx/proxy-hosts/${HOST_ID}" "$(host_payload "$CERT_ID" true true)" >/dev/null; then
|
||||||
--arg d "$DOMAIN" \
|
echo "SSL 적용 요청이 실패했습니다. 반영 상태를 확인합니다." >&2
|
||||||
--arg fh "$FORWARD_HOST" \
|
diagnose_blocked
|
||||||
--arg fp "$FORWARD_PORT" \
|
fi
|
||||||
--arg fs "$FORWARD_SCHEME" \
|
|
||||||
--argjson cert "$CERT_ID" \
|
|
||||||
--argjson ws "$WS" \
|
|
||||||
'{domain_names:[$d],
|
|
||||||
forward_host:$fh,
|
|
||||||
forward_port:($fp|tonumber),
|
|
||||||
forward_scheme:$fs,
|
|
||||||
certificate_id:$cert,
|
|
||||||
ssl_forced:true,
|
|
||||||
http2_support:true,
|
|
||||||
hsts_enabled:false,
|
|
||||||
hsts_subdomains:false,
|
|
||||||
caching_enabled:false,
|
|
||||||
allow_websocket_upgrade:$ws,
|
|
||||||
block_exploits:true,
|
|
||||||
access_list_id:0,
|
|
||||||
advanced_config:"",
|
|
||||||
locations:[],
|
|
||||||
meta:{}}')
|
|
||||||
api PUT "/nginx/proxy-hosts/${HOST_ID}" "$SSL_PAYLOAD" > /dev/null
|
|
||||||
|
|
||||||
echo "도메인 생성/SSL 적용 완료: ${DOMAIN} (host id=${HOST_ID})"
|
# ---------------------------------------------------------------
|
||||||
echo "host-id=${HOST_ID}" >> "$GITHUB_OUTPUT"
|
# 8. 최종 검증 — nginx에 실제로 반영됐고 인증서가 붙었는지 확인
|
||||||
echo "created=true" >> "$GITHUB_OUTPUT"
|
# PUT이 200을 줘도 nginx reload가 실패했을 수 있으므로
|
||||||
|
# meta.nginx_online으로 확인합니다.
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
verify() {
|
||||||
|
api GET "/nginx/proxy-hosts/${HOST_ID}" \
|
||||||
|
| jq -e --argjson c "$CERT_ID" \
|
||||||
|
'.meta.nginx_online == true and .certificate_id == $c' >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! verify; then
|
||||||
|
echo " SSL이 nginx에 반영되지 않았습니다. 복구를 시도합니다."
|
||||||
|
recycle_host "$HOST_ID"
|
||||||
|
REPAIRED=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! verify; then
|
||||||
|
echo "도메인 생성 실패: ${DOMAIN} — SSL이 nginx에 반영되지 않았습니다." >&2
|
||||||
|
api GET "/nginx/proxy-hosts/${HOST_ID}" \
|
||||||
|
| jq -r '" certificate_id=\(.certificate_id) nginx_online=\(.meta.nginx_online // "null")",
|
||||||
|
" nginx_err: \(.meta.nginx_err // "(없음)")"' >&2
|
||||||
|
diagnose_blocked
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "도메인 생성/SSL 적용 완료: ${DOMAIN} (host id=${HOST_ID}, cert id=${CERT_ID})"
|
||||||
|
{
|
||||||
|
echo "host-id=${HOST_ID}"
|
||||||
|
echo "created=${CREATED}"
|
||||||
|
echo "repaired=${REPAIRED}"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|||||||
Reference in New Issue
Block a user