배포 파이프라인 결함 3건 수정
All checks were successful
deploy-dev / deploy (push) Successful in 39s

2026-07-30 나눔고딕 글꼴 배포 중 드러난 문제들이다.

1) build-and-upload.sh: 업로드 검증이 없어 잘린 WAR이 통과했다
   pscp가 대용량 전송 중 약 30초 지점에서 조용히 끊기며 exit 0을 반환해,
   194MB WAR이 66MB/84MB로 잘린 채 "업로드 완료"로 처리됐다. 그 WAR로
   배포가 돌아 실패했다. 30MB 조각 전송 + 조각별 크기 확인(3회 재시도) +
   서버 결합 후 size/md5 대조를 통과할 때만 arko.war 로 교체하도록 바꿨다.

2) build-and-upload.sh: mvn -ntp 는 Maven 3.6.1+ 전용
   PATH에 3.5.4가 잡히면 "Unrecognized option: -ntp"로 빌드가 즉시 죽는다.
   진행률 억제 옵션일 뿐이라 제거했다.

3) arko-dev-deploy.sh: WAR 검증이 파이프+pipefail 조합으로 취약
   정상 WAR인데 Actions 실행 중 "WEB-INF/web.xml 없음"으로 오판해 배포가
   멈췄다(서버에서 동일 명령 재실행 시 20/20 통과). unzip 목록을 한 번만
   읽어 변수에 담고 파이프 없이 검사한다.
   ※ 서버의 /opt/arko-dev/bin/ 사본은 워크플로가 갱신하지 않으므로 별도 반영 필요.

덧붙여 워크플로 주석의 "빈 커밋으로 재배포" 안내를 정정했다 —
paths-ignore 때문에 변경 경로가 없는 커밋은 워크플로가 실행되지 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
infraurp 2026-07-30 13:44:50 +09:00
parent 10cb87ff42
commit e4973fb777
3 changed files with 48 additions and 6 deletions

View File

@ -17,7 +17,10 @@
# ※ Gitea 1.22.3 기준으로 작성했다. 이 버전은 workflow_dispatch(수동 실행 버튼)와
# inputs 컨텍스트를 지원하지 않는다 — 넣으면 워크플로 자체가 목록에 뜨지 않는다.
# 그래서 트리거는 push 하나만 둔다.
# 재배포는 커밋 없이도 `git commit --allow-empty` 후 push 하면 되고,
# ※ 빈 커밋(`git commit --allow-empty`)으로는 재배포되지 않는다 — 변경 경로가 없어
# 아래 paths-ignore 조건에서 걸러지고 워크플로가 아예 실행되지 않는다(2026-07-30 확인).
# 재배포가 필요하면 아래 무시 대상이 아닌 파일을 실제로 변경해 push 하거나,
# 서버에서 `bash /opt/arko-dev/bin/arko-dev-deploy.sh /opt/arko-dev/incoming/arko.war` 를 직접 실행한다.
# 롤백은 서버에서 직접 수행한다(deploy/dev/README.md 참고).
name: deploy-dev

View File

@ -38,8 +38,11 @@ mkdir -p "$BACKUP_DIR" "$LOG_DIR"
# ---------------------------------------------------------------- 1. WAR 검증
[ -n "$WAR_SRC" ] || die "사용법: $0 <arko.war 경로>"
[ -f "$WAR_SRC" ] || die "WAR 파일 없음: $WAR_SRC"
unzip -l "$WAR_SRC" > /dev/null 2>&1 || die "WAR 파일이 손상됨: $WAR_SRC"
unzip -l "$WAR_SRC" | grep -q "WEB-INF/web.xml" || die "WEB-INF/web.xml 없음 — 정상 WAR 아님"
# unzip을 두 번 돌리고 grep -q로 파이프하면 set -o pipefail과 맞물려 취약하다
# (2026-07-30: 정상 WAR인데 Actions 실행 중 "web.xml 없음"으로 오판해 배포가 멈췄다).
# 목록을 한 번만 읽어 변수에 담고 파이프 없이 검사한다.
WAR_LIST=$(unzip -l "$WAR_SRC") || die "WAR 파일이 손상됨: $WAR_SRC"
grep -q "WEB-INF/web.xml" <<< "$WAR_LIST" || die "WEB-INF/web.xml 없음 — 정상 WAR 아님"
WAR_SIZE=$(du -h "$WAR_SRC" | cut -f1)
log "배포 시작: $WAR_SRC ($WAR_SIZE)"

View File

@ -20,7 +20,9 @@ SERVER_USER=root
SERVER_PW='1q2w3e!Q'
HOSTKEY="SHA256:Zm5aAv5DJ/EnKiObKSadOxA5nKuXgToCGhNiyhVhAjY"
REMOTE_INCOMING=/opt/arko-dev/incoming
REMOTE_PARTS=/opt/arko-dev/incoming/parts
REMOTE_DEPLOY=/opt/arko-dev/bin/arko-dev-deploy.sh
CHUNK_SIZE=30m # pscp 연결이 30초 부근에서 끊기는 사례가 있어 조각 단위로 올린다
SITE_URL=https://csv.zioinfo.co.kr/
# arko는 maven-compiler-plugin이 source/target 1.7 → JDK 8로만 빌드된다 (17/21 불가)
@ -53,7 +55,9 @@ if [ "$DO_BUILD" = "1" ]; then
[ -x "$JDK8_HOME/bin/javac" ] || { echo "JDK 8을 찾을 수 없다: $JDK8_HOME (JDK8_HOME 환경변수로 지정)"; exit 1; }
export JAVA_HOME="$JDK8_HOME"
cd "$PROJECT_ROOT"
mvn -B -ntp clean package -DskipTests
# -ntp(--no-transfer-progress)는 Maven 3.6.1+ 전용이다. PATH에 3.5.4가 잡히면
# "Unrecognized option: -ntp"로 즉시 죽으므로 쓰지 않는다.
mvn -B clean package -DskipTests
[ -f "$WAR" ] || { echo "WAR 생성 실패: $WAR"; exit 1; }
@ -78,8 +82,40 @@ WAR_MB=$(( $(du -k "$WAR" | cut -f1) / 1024 ))
echo "서버 여유 ${FREE_MB}MB / WAR ${WAR_MB}MB"
[ "$FREE_MB" -gt $(( WAR_MB * 3 )) ] || { echo "서버 디스크 부족"; exit 1; }
# 전송 중 중단되어도 반쪽 WAR이 배포되지 않도록 임시 이름으로 올린 뒤 교체
PSCP "$WAR" "$SERVER_USER@$SERVER_IP:$REMOTE_INCOMING/arko.war.uploading"
# pscp가 대용량 전송 중 약 30초 지점에서 조용히 끊기면서 exit 0을 반환한 사례가 있다
# (2026-07-30: 194MB WAR이 66MB/84MB로 잘린 채 "업로드 완료"로 처리되어 배포가 깨졌다).
# 그래서 ① 조각으로 나눠 올리고 ② 조각마다 크기를 확인하고 ③ 서버에서 합친 뒤
# ④ size+md5가 로컬과 일치할 때만 arko.war 로 교체한다. 검증 실패 시 기존 WAR은 그대로 둔다.
LOCAL_SIZE=$(stat -c %s "$WAR")
LOCAL_MD5=$(md5sum "$WAR" | cut -d' ' -f1)
echo "로컬 size=$LOCAL_SIZE md5=$LOCAL_MD5"
PARTS_DIR="$PROJECT_ROOT/target/warparts"
rm -rf "$PARTS_DIR"; mkdir -p "$PARTS_DIR"
split -b "$CHUNK_SIZE" -d -a 2 "$WAR" "$PARTS_DIR/part_"
PART_CNT=$(ls "$PARTS_DIR" | wc -l)
echo "분할 ${PART_CNT}개 ($CHUNK_SIZE)"
PLINK "rm -rf $REMOTE_PARTS && mkdir -p $REMOTE_PARTS"
IDX=0
for P in "$PARTS_DIR"/part_*; do
IDX=$((IDX+1)); NAME=$(basename "$P"); LSZ=$(stat -c %s "$P"); OK=0
for TRY in 1 2 3; do
PSCP "$P" "$SERVER_USER@$SERVER_IP:$REMOTE_PARTS/$NAME" || true
RSZ=$(PLINK "stat -c %s $REMOTE_PARTS/$NAME 2>/dev/null || echo 0" | tr -d '\r')
if [ "$RSZ" = "$LSZ" ]; then echo " [$IDX/$PART_CNT] $NAME OK ($LSZ bytes)"; OK=1; break; fi
echo " [$IDX/$PART_CNT] $NAME 크기 불일치 (remote=$RSZ local=$LSZ) — 재시도 $TRY/3"
done
[ "$OK" = "1" ] || { echo "조각 전송 3회 실패: $NAME — 중단"; exit 1; }
done
PLINK "cat $REMOTE_PARTS/part_* > $REMOTE_INCOMING/arko.war.uploading && rm -rf $REMOTE_PARTS"
R_SIZE=$(PLINK "stat -c %s $REMOTE_INCOMING/arko.war.uploading" | tr -d '\r')
R_MD5=$(PLINK "md5sum $REMOTE_INCOMING/arko.war.uploading | cut -d' ' -f1" | tr -d '\r')
echo "서버 size=$R_SIZE md5=$R_MD5"
[ "$R_SIZE" = "$LOCAL_SIZE" ] || { echo "업로드 크기 불일치 — arko.war 를 교체하지 않고 중단"; exit 1; }
[ "$R_MD5" = "$LOCAL_MD5" ] || { echo "업로드 md5 불일치 — arko.war 를 교체하지 않고 중단"; exit 1; }
rm -rf "$PARTS_DIR"
PLINK "mv -f $REMOTE_INCOMING/arko.war.uploading $REMOTE_INCOMING/arko.war && ls -lh $REMOTE_INCOMING/arko.war"
# ------------------------------------------------------------- 3. 배포