당신의 배포 절차가 "라이브 파일을 받아서 고치고 다시 올리는" 방식이라면, 그 절차를 몇 번 반복했는지 세어 본 적 있습니까?
앱 하나의 privacy.html을 열어보다가 이상한 걸 발견했습니다. <header class="site-header">가 6번 나오는데, 페이지를 닫는 </html>은 1개뿐이었습니다. 처음엔 다국어 전환 스크립트가 언어별 사본을 DOM에 다 심어두고 토글하는 구조인가 생각했습니다. 그런데 assets/app-i18n.js를 읽어보니 그 스크립트는 [data-i18n] 속성이 붙은 텍스트 노드만 바꾸고 lang 속성만 토글할 뿐, DOM에 사본을 여러 개 만들지 않았습니다. 이건 언어 전환 메커니즘이 아니라 진짜 결함이었습니다.
당신이라면?
한 파일에서 이 증상을 봤다면, 그 파일 하나만 고치고 넘어가겠습니까, 아니면 같은 배포 절차를 쓰는 다른 파일도 전수 확인하겠습니까? 41개 앱을 SSH로 한 번에 훑는 건 몇 분이면 됩니다. 고치고 나서 다른 39개에서 같은 증상이 또 나오는 것보다는 쌉니다.
ssh ... 'grep -rc "site-header" public_html/*/privacy.html public_html/*/terms.html public_html/*/support.html'결과: 41개 앱의 privacy/terms/support.html, 123개 파일 전부 동일하게 6중첩. 같은 앱의 index.html을 비롯한 다른 페이지는 멀쩡했습니다 — 반복 편집 사이클이 걸린 건 딱 이 3개 파일뿐이었습니다.
원인
이 3개 파일에는 앱마다 원문을 보존하는 legal-copy 섹션이 있습니다. 배포 절차는 이런 모양이었을 것으로 보입니다: 라이브 페이지를 내려받는다 → 새 사본을 legal-copy 안에 넣는다 → 다시 올린다. 이 절차가 반복될 때, 기존 내용을 걷어내지 않고 그 안에 이전 전체 페이지를 통째로 밀어넣은 것으로 추정됩니다. 매 사이클마다 껍질이 한 겹씩 쌓였습니다. 진짜 법적 본문은 가장 안쪽, 여섯 번째 사본에만 남아 있었습니다.
수정
각 파일에서 마지막(6번째, 가장 안쪽) legal-copy 열림 태그 다음부터 그 다음 첫 </section>까지가 진짜 원문이라는 규칙을 세웠습니다. 이를 추출해 outer shell 1겹 + 진짜 원문 + 닫는 태그들 + footer로 재조립하는 파이썬 스크립트를 짜서 123개 파일에 일괄 적용했습니다. 본문은 원본과 라인 단위 diff로 무손상을 확인했습니다.
여기서 함정이 하나 더 있었습니다. 앱마다 "원문"의 형태가 달랐습니다. 대부분은 순수 텍스트(<p class="meta">...)였지만, 세 개 앱은 그 앱의 구버전 자체 페이지 전체(자체 header·main 포함)가 원문으로 통째로 보존돼 있었고, 다른 한 앱은 다국어 정적 페이지 전체가 원문이었습니다. "다음 첫 </section>" 경계 로직이 이 임베드들까지 안전하게 잘라내는지 미리 알 수 없었습니다 — 다행히 이 임베드들 내부에는 <section> 태그가 없어서, 태그 균형을 세어 검증한 뒤에야 안전을 확인했습니다.
배포도 같은 사고를 피해야 했다
123개 파일을 다시 올리는 작업 자체가, 이 버그를 만든 것과 같은 종류의 반복 작업입니다. scp를 파일마다 반복하면 8개쯤에서 연결이 끊기는 걸 이미 겪은 적이 있어서, 이번엔 단일 세션 tar 파이프로 123개를 한 번에 밀었습니다. 검증은 세 겹으로 했습니다.
- 서버에서 헤더 개수 1,
</html>개수 1 — 123개 전부 통과. - 로컬 수정본과 서버본 md5 123개 전수 일치.
- 브라우저 렌더 확인(대표 앱 몇 개, Cloudflare 챌린지를 통과하는 방식으로).
- 내부 링크가 전부
.html로 끝나는지 확인 — 임베드된 구버전 페이지 안의 링크는 원문의 일부라 그대로 두었습니다. 본문을 훼손하지 않는 게 원칙이었으니까요.
자가진단
"라이브 받아서 고쳐 되올리기"를 반복하는 배포 절차가 있다면 세 가지를 점검하세요.
- 원문 보존 섹션에 새 내용을 넣기 전에, 그 섹션을 먼저 비웁니까? 비우지 않으면 이번 것과 같은 자기중첩이 매 사이클마다 한 겹씩 쌓입니다.
- 한 파일에서 이상 징후를 봤을 때, 같은 절차를 쓰는 다른 파일 전체를 즉시 훑습니까? 이번엔 41개 앱 123개 파일이 동시에 걸려 있었습니다 — 하나만 고치고 끝냈다면 나머지 122개는 그대로 라이브에 남았을 겁니다.
- "원문"이라 부르는 것이 실제로 항상 같은 형태입니까? 순수 텍스트라고 가정한 경계 로직이, 어떤 앱에서는 페이지 전체를 담은 임베드와 마주칠 수 있습니다.
이 버그를 만든 생성기 자체는 아직 못 찾았습니다. 원문 보존 로직이 새 페이지를 삽입하기 전에 기존 legal-copy 내용을 먼저 비우지 않는 구조라면, 다음 배포 사이클에 같은 3개 파일이 다시 6중첩될 수 있습니다. 배포하려던 리포가 라이브보다 한 세대 낡아 있었습니다는 같은 "라이브를 직접 편집하는" 배포 구조에서 나온 또 다른 사고였습니다 — 근원은 같고 증상은 달랐습니다. 당신의 배포 절차가 "받아서 고쳐 되올리기"라면, 그 절차가 몇 겹까지 쌓일 수 있는지 지금 세어 보세요.