# AI Webmaker (WebHoting AI Agent) — v1.7.3 AI 로 웹페이지를 자동 생성하고 같은 서버(nginx)에서 바로 호스팅하는 에이전트. ## 변경 이력 전체 변경 기록은 [CHANGELOG.md](CHANGELOG.md), 오류 점검 목록은 [ERRORS.md](ERRORS.md) 에 있다. - **1.7.3** 도메인 연결 오류 수정(배포 서버에 `log_format wmstats` 가 이미 있으면 중복 정의하지 않고, 중복으로 nginx -t 가 실패하면 우리 파일을 지워 자가 복구 · nginx 1.19.4 미만은 ssl_reject_handshake 대신 자체 서명 인증서 기본 서버) + **메일 사용량 연동**(AriMail 1.0.9 사용량 API: 관리자 콘솔에서 키 발급·보관(보관소 `arimail-usage`), 도메인별 메일함 용량·메일 수·발송/수신 표, 사용자 메일 화면 사용량 행, 사이트 정보 "메일 데이터" = 서버 메일함 용량 + 메일 사용량 구역(30일 막대)). - **1.7.2** 도메인 관리 역할 분리 — 도메인 화면은 구매·갱신·**네임서버 이전 4단계 띠**, DNS 화면은 **사이트 ↔ 도메인 연결 표**(하위 이름 선택·연결 끊기)가 먼저이고 레코드 관리는 그 아래, 메일 화면은 계정 형식·접속 주소·초기 관리자를 한눈에; **사이트 정보 페이지**를 오른쪽 캔버스로 옮겨 방문자 막대·송수신 선 그래프·7일/월간(3개월) 막대·디스크(웹/DB/메일) 그래프를 SVG 로 표시. 두 서버 로그 0건. bear 배포 중단. - **1.6.40** 사용자 화면 전면 재구성 — 작업 흐름 중심의 세 칸 구조(레일: 홈 / 만들기·수정·배포·운영 / 도메인·DNS·메일 / 캐시·충전·설정, 상태 점·배지), **홈**(단계별 상태·다음 할 일·의존 관계 지도), **배포** 화면(준비 점검표 + 발행·원본 서버 반영·검색 등록), **DNS**·**메일** 화면 분리, **캐시·충전** 화면(선불 충전 상품 → 결제 진행은 PG 미연동이라 결제 대기 주문만 → 관리자 "입금 확인 → 적립", 적립 = 결제 금액 + 인센티브), 관리자 콘솔 선불 충전 상품·주문 관리(`lib/prepaid.js`). 로그 점검 두 서버 0건. - **1.6.39** 메일 서버 비밀번호 보호(덮어쓰기 확인 창·**이전 비밀번호로 되돌리기**·Arimail 로그인 잠금 중 "연결 확인" 비활성·credentials.json/config.json/mail-servers.json `.bak`), **사이트 관리자 비밀번호 ↔ 메일(Arimail) 비밀번호 동기화**(로그인 때 자동으로 맞춤, 켤 때 본인 비밀번호 확인, 동기화 상태 열), 연동 관리자 계정 **비밀번호 보기/숨기기·복사·초기화(다른 관리자 계정으로도)·삭제 보호**, Route 53 접속 정보 누락 문구가 빠진 항목을 말함(webmaker.ibit.kr 사례는 1.6.38 배포로 이미 해결 확인); 1.6.38 배포 이후 로그 점검 0건. - **1.6.38** AI 연결 탭에서 키 없이 저장한 제공자가 설정 탭 등급 목록에서 빠지는 이유를 표·저장 문구·빨간 안내로 바로 보여 주고 키 없는 "모델 조회" 는 거절(webmaker.ibit.kr OpenRouter 사례); **메일 서버(Arimail) 등록부** — 다른 서버에 설치된 Arimail 을 여러 대 등록(닉네임·IP 로 구분, 비밀번호는 보관소)하고 사이트마다 고르거나 메일 도메인으로 자동 선택, 고른 서버 호스트가 MX 기본값; 도메인 탭 외부 도메인에 **"네임서버 정보"·"DNS 레코드 정보" 팝업**(어디를 누르는지 단계 안내, A·MX·SPF·DMARC·DKIM 값 클릭 선택·복사 버튼); Route 53 설정 칸 `ACCESS_KEY` 별칭 허용; 로그 점검 0건. 1.6.36·1.6.37 건너뜀. - **1.6.35** 도메인 관리 프로세스 개편: 관리자는 접속 정보·플랫폼 도메인·**서브 도메인 등록 허용/금지** 만 정하고, 도메인 검색·구매(**장바구니** → 최종 선택 구매)·**이미 가진 도메인 등록**(네임서버 이전: 레코드 옮김 → 네임서버 안내 → "네임서버 변경했습니다" 확인 → 전파 중 "DNS 변경 진행 중" 레코드 잠금 / 직접 DNS 관리: 필요한 레코드 안내 → "설정 완료 — 확인" → 미반영이면 배포 불가 안내)·**도메인 상태**(배포·메일이 안 되는 이유 단계별)는 사용자 "도메인" 탭에서 스스로; 관리자 DNS 영역 화면은 조회·정리만; 로그 점검 0건. - **1.6.33** DNS 영역 관리 API 404 회귀 수정(1.5.36 부터 `dns` 경로 누락), "이전된 영역 받기" 가 실패 원인(미등록·위임 없음·네임서버 무응답)을 설명하고 빈 영역은 "zone 파일 구성을 찾을 수 없습니다" 로 안내, 배포 직후 접속 확인 재시도 + `vhost-wait` 단계, 발행 카드 "다시 확인"(해결된 오류 갱신); 로그 점검. - **1.6.32** 등록 업체에 **Amazon Route 53 Domains** 추가(검색·가격·등록·갱신·네임서버, 비동기 등록은 PENDING → 완료 확인 뒤 DNS 영역 자동, 운영 복구 "등록 상태 확인"), 보관소 Route 53 연결 확인에 도메인 등록 권한 표시, 모의 서버 검증 6개; 로그 점검 0건. - **1.6.31** 보안·안정성 개선판: Claude 가드 훅 실효화(서버 절대 경로 설정 생성, Node 가드)·AI 환경 변수 허용 목록·작업 시간/동시 상한·codex fail-closed, 내부 주소 차단 + 허용 목록, 생성 사이트 페이지의 세션 무시 + CSRF 토큰, 고정 관리자 admin/admin 폐지(토큰 1회성·1회용 비밀번호), 로그인 속도 제한·잠금, 원자적 JSON 저장·손상 표시, 캐시 중복 차감 금지, Arimail 계정 등록부, 배포 단계(dns-wait·cert-wait…)·배포본 보관·이전 배포본으로, 도메인 구매 원장, 관리자 **운영 복구**·**작업 기록** 탭, 업데이트 전 백업, `npm test` 자동 검증. 설치: 최초 관리자는 설정 페이지(토큰)에서 만든다 (docs/FIRST-INSTALL.md). - **1.5.36** 좌측 위·아래 영역 구분(배경·경계), 모듈 위젯 다국어(data-title·data-button 등 속성 번역, 파일 선택 버튼 문구표), 등급 표 특징 칸 2줄, **도메인 등록 업체 모듈**(Openprovider·Porkbun) 과 **DNS 공급자 모듈**(Route 53·Google Cloud DNS·Porkbun·Openprovider — 영역·레코드·MX·ACME TXT·이전 영역·감사 기록, 관리자 DNS 영역 관리) — [docs/DOMAIN-DNS-PROVIDERS.md](docs/DOMAIN-DNS-PROVIDERS.md). - **1.5.31** 빌더 위쪽 진행 상자 **한 줄**(상태 + 오른쪽 보조 문구), 배포 결과 상자를 없애고 메시지창으로, 재배포 접속 확인의 301 오판 수정(HTTP→HTTPS 리디렉션 정상 판정, SNI), 서비스 유닛 권한 점검·보정 도구 `scripts/unit-check.js --fix`(드롭인) 와 갱신 스크립트 연동. - **1.5.27** 빌더 위쪽 **메시지창**(결과 메시지 누적·최신 위·삭제), 재배포 때 vhost 가 이미 같으면 sudo 없이 유지(sudo 가 막힌 서버의 "도메인 연결 실패" 경고 제거), 사이트 도메인 vhost 에도 **발행 취소 안내 규칙**(`.unpublished` → 503), 안내 페이지 다국어(`lib/maintpage.js`), 와일드카드 인증서 감지를 nginx 참조 파일·/etc/ssl(Cloudflare 원본 인증서)까지, **회원가입 규칙**(가입 ON/OFF · 관리자 승인 후 이용, 승인/거절), 문의 메일 발신 주소를 검증된 상위 도메인으로(Resend 403 수정). - **1.5.19** 생성 탭 등급 특징 문구(기본 정적 웹페이지 / 중급 UI·UX 심미성 / 고급 복잡한 프런트엔드 아키텍처), 배포 오류 안내 개선 — sudo 가 막힌 **원인**(유닛 `NoNewPrivileges`·`ProtectSystem=strict`·sudoers)을 구분해 안내(`sudoHint`), 종류 local 배포 서버의 DNS 비교를 127.0.0.1 이 아니라 서버 공인 IP 로(`localServerIps`), 설치 점검표 "배포 서버 도메인 연결" 항목, 갱신 스크립트의 유닛 경고. - **1.5.17** 서버에 **미리 발급된 와일드카드 인증서**(`*.<플랫폼 도메인>`, 이름 무관)를 인지해 서브 도메인 사이트에 자동 적용 — SAN 기반 감지(`deploy.findWildcardCert`), 기동 때 HTTP 로 배포돼 있던 사이트 재구성(`ssl.autoApply`), 설치 점검표 항목, `scripts/ssl-wildcard.js`(설치·갱신 스크립트가 호출). - **1.5.16** 관리자 콘솔 — **AI 연결** 탭 신설(Claude 인증·연결 테스트·**사용할 Claude 모델**·AI 제공자를 설정 탭에서 분리; 여기서 켠 모델만 등급·내부 호출에 지정 가능, 서버도 저장 때 검사), "Claude 기본 모델" 을 등급 지정과 같은 목록에서 고르는 **내부 호출 모델**(`internalRef`, 사용자 문의 AI 분석 등)로 대체, 시스템 설정 › 설치 점검표 글자 색을 배경의 보색 계열로 고정(어두운 테마에서 읽을 수 없던 문제). - **1.5.13** 관리자 콘솔 › 배포 서버 — 공개키를 **별도 창에서 버튼으로 복사**(키 생성·재생성·보기), 추가·수정 폼을 **대화상자**로 분리("배포 서버 추가"·"수정" 때만 열림). - **1.5.11** 첫 설치 — **기본 관리자 `admin / admin`**(사용자가 없으면 기동 때 생성, 첫 로그인 뒤 비밀번호 변경 강제), **설치 점검표**(관리자 비밀번호·AI 엔진 인증·공개 주소 — 끝나기 전에는 첫 화면 `/`·`/login` 이 `/setup` 으로 넘어감), `/setup` 을 토큰으로도 열기, 설정 페이지 **기본 포트 8795 · 모든 인터페이스**(겹치면 다음 포트 자동), 설정 페이지에 관리자 계정·AI 엔진 인증 카드, `remote-install.sh` FQDN 없이 IP 로 처음 설치, 회원가입 설정(`service.allowSignup`), 생성 사이트 런타임 `wm-modules.js` 캐시 `no-cache`+ETag ([docs/FIRST-INSTALL.md](docs/FIRST-INSTALL.md)). - **1.5.7** 운영 구조 — **설정 파일 분리**(`data/config.json`, 유닛의 환경 변수를 첫 기동 때 자동 이전), **초기 설정 페이지**(별도 포트 3081 `webmaker-setup`, 토큰/관리자 로그인; `/setup`·관리자 콘솔 시스템 설정 탭과 같은 화면), **연동 모듈**(`lib/connectors/` — Gitea 소스 보관, **Arimail** 메일 서버: 사이트 관리자 계정의 메일함 생성·웹메일 연결), **연동 서비스 설치 스크립트**(`install-services.sh`, `install-arimail.sh`, `install-gitea.sh` 설정 파일 등록, 설치·갱신 스크립트에 포함). 오류 수정: codex CLI 0.159+ 의 `--full-auto` 제거로 OpenAI 서버 로그인 작업이 모두 실패하던 문제, 모듈 키워드(일정관리 등) 수정 지시에 표준 위젯 코드가 빠져 AI 가 가짜 모듈(`schedule`)을 만들던 문제(런타임 별칭으로 기존 페이지도 복구). [docs/CONFIG.md](docs/CONFIG.md) · [docs/ARIMAIL.md](docs/ARIMAIL.md) - **1.4.18** 기본 제공 모듈에 **일정관리(캘린더)** 추가 — 월간·주간·일간 보기, 시·분/일/주 단위 일정, 반복(매일·매주·매월·매년), **만세력 기준 음력 날짜·사주식 간지(병오년 무술월 신유일)·24절기 표시 옵션**, 음력 기준 일정(음력 매년·매월). 음력·간지는 자료표 없이 천문 계산(`lib/lunar.js`, [docs/CALENDAR.md](docs/CALENDAR.md)). - **1.4.17** 미리보기 안에서 페이지를 옮기면 위 도구 줄의 페이지 선택과 소스 편집기가 따라간다 (생성 페이지의 `wm-stats.js` 가 부모 창에 현재 경로를 알림, sandbox iframe 이라 직접 읽을 수 없음). - **1.4.15** 1.4.14 회귀 수정 — 사이트를 고르지 않은 상태의 상태 줄 클래스(`empty`)가 미리보기 빈 화면의 전체 덮개 규칙과 겹쳐 빌더가 클릭에 반응하지 않던 문제. "사용자 첫 화면 페이지" 는 빌더 오른쪽 빈 창에만 보이는 페이지임을 관리자 콘솔 문구와 동작(전용 상자)으로 분명히 했다. - **1.4.14** 빌더 탭을 **생성/수정/관리/도메인/설정** 으로 — 새 "수정" 탭(수정 지시·발행·다국어·검색 최적화·버전·첨부), "관리" 탭은 사이트 관리자 기능·현황 전담이며 오른쪽 창이 사이트 관리 창(사이트 관리자 페이지 + 현황)으로 바뀜, 상태·운영 주소 줄 높이 고정(탭 들썩임 제거). 관리자: OpenAI 서버 로그인 모델 전체 조회(codex 모델 목록 파일), 등급 지정에 추가 모델이 바로 보이도록(제공자별 묶음·빠진 이유 표시). 모델이 바뀌어도 맥락 유지("엔진 변경 안내" 섹션). 사이트 소스 저장소를 서비스 서버의 **로컬 Gitea**(계정 webmaker, 저장소 webmaker/sites)로 — `deploy/install-gitea.sh`·`scripts/migrate-gitstore.sh`, [docs/GITEA-LOCAL.md](docs/GITEA-LOCAL.md). 플랫폼 도메인 **와일드카드 인증서**(`*.ibit.kr`)로 서브 도메인 사이트 HTTPS, 고객 도메인은 사이트별 인증서로 분리 — [docs/SSL-WILDCARD.md](docs/SSL-WILDCARD.md). - **1.4.5** 관리자 지정 사용자 첫 화면 페이지, 숫자 입력·표시 3자리 쉼표, Claude 세션 누적 비용을 작업마다 청구하던 오류 수정(이번 실행 비용 = 누적 차이 또는 토큰 × 단가, 캐시 쓰기 단가·보정 배율 수정, [점검 보고서](reports/token-billing-2026-10-10.md)). - **1.4.3** 도메인 서비스 준비 목록·만드는 방법 문서 [docs/DOMAIN-SETUP.md](docs/DOMAIN-SETUP.md) (Openprovider 계정·접속 정보, `*.ibit.kr` DNS, SSL, 메일, 점검 순서). - **1.4.1** 사이트 복사가 같은 서비스의 다른 사이트를 복사할 때 로그인 화면만 복사되던 문제 수정(시작 폴더 범위만 복사), 관리자 빌더의 "캐시 충전 요청" 안내 제거, 캐시 표시 단위 설정(원·크레딧·D 등), 게시판 [글쓰기] 버튼 한 줄. - **1.4.0** 빌더 "도메인" 탭(Openprovider 도메인 검색·구매·갱신, 가격 = 원가 × 요율 250% 를 사용 전에 표시·캐시 차감, DNS 레코드 관리, 사이트 연결), 계정 종류(서브 도메인 전용 / 자체 도메인 — 서브 도메인은 `이름.ibit.kr` 등록·메일 `admin@이름.ibit.kr`·`/mail`), 메일(MX)·Resend 레코드 자동 설정과 DNS 전파 대기 안내, 블로그 모듈(검색·댓글), 자료실 모듈(파일 내려받기), 다국어 선택 기본 드롭다운, 생성 탭 언어 상자 여백, API 키 재입력·비노출 점검, 다국어 언어 선택 링크 오류(1.3.16) 수정. - **1.3.17** 코드 점검 보고서(2026-10-07) 46건 조치 — 세션 위조(S01)·세션 무효화·미리보기 sandbox·바깥 주소 가져오기 보호(netguard)·가져오기 경로 탈출·번역 마크업 차단 등 보안 14건, 지도 모듈·발행·폐기·청구·편집기 등 기능 32건. 관리자 콘솔 비밀번호 규칙(기본 6자 이상). 대응표 `reports/code-review-2026-10-07-response.md`. - **1.3.16** 관리자 설정 값마다 실행 버튼(청구 마진 저장 안 됨 수정·빈 값 0 저장 방지·저장 안 된 변경 표시), 다국어 로케일 코드 URL 원칙·x-default 지정, 로케일별 메타·구조화 데이터 관리, 생성 사이트 시스템 문구 카탈로그(위젯·폼·오류·동의문·예약·문의·메일 알림 하드코딩 제거), 번역 품질 게이트(누락률·잔존율·링크 대응·검수), 현지 형식(날짜·시간대·통화·주소·전화·이름 순서·법적 고지·RTL), 사이트 관리자 다국어와 서비스 기본 언어 강제(사용자 선택 우선). - **1.3.10** 구글 검색 등록 완전 자동(서비스 계정 키)·네이버 수동 단계 최소화·배포 후 IndexNow 자동, 관리자 사이트 템플릿(디렉터리·해시태그·사용 허용, 생성 탭 "템플릿에서 시작"), 다국어 사이트 생성(/ko/ /en/ 로케일 폴더·같은 구조 번역·RTL·언어별 날짜와 SEO/AEO/GEO·hreflang), 빌더 번역 개선(남은 한국어 제거·언어 파일 형식 2·속도), 관리자 화면 다국어, 좁은 화면 번역문 넘침 수정, SEO 초안 JSON 파싱 오류 수정. - **1.3.5** 생성 옵션 SEO·AEO·GEO(AI 초안 → 관리 탭 검토 후 반영), 관리 탭 검색사이트 등록(구글·네이버 단계·IndexNow, 관리자 검색 등록 탭), 관리자 언어 탭 실제 상태·서비스 버전 표시와 버전 감시, 문의 메일 발송 칸 삭제. - **1.3.4** 생성 탭에서 "사이트 관리하기(서버에서 가져오기)" 삭제, "사이트 복사하기" 만 남기고 상세 설명(기본 HTML 페이지만 복사·기능 미복사·기존 모양을 살린 새 사이트 제작용). - **1.3.3** 가져온·복사한 사이트 미리보기의 "페이지를 찾을 수 없습니다" 수정(루트 기준 주소를 Referer 로 사이트 폴더에 연결), 미리보기 없음 이유 표시. - **1.3.2** 메일 시스템 사이트 단위 켜기(관리자 메일 시스템 탭, "메일시스템 설정중"→"사용 가능"), 키 파일 올리기, 화면 설정 탭(실제 화면 미리보기), 로그 보기(종류·기간·회사·사이트·검색), 사용자 문의(문의 종류·종류별 AI 1차 분석·답변 초안), 관리자 새로고침 수정, 템플릿 문장 번역(패턴). - **1.3.1** 외부 연동 접속 정보 암호화 보관소(`data/credentials.json`, 키 파일 가져오기, 기존 메일·Claude 키 자동 이전), 웹메일 보내기(메일 발송 키·Resend 도메인 등록·확인). - **1.3.0** 사이트 메일 시스템(운영자 켜기/끄기, 아이디@사이트도메인, DNS 안내·확인·자동 재확인, 사이트 관리자 계정과 공유하는 메일 계정, 메일 사용 끄기 시 재확인·비밀번호, `사이트/webmail` 웹메일 — 송수신 백엔드는 mail-gateway 연동 대기), 언어 파일 업로드 "JSON 파싱 실패" 수정. - **1.2.0** 생성 탭 "기존 사이트 관리" 분리(사이트 복사하기 신규·사이트 관리하기), 가져오기·복사 후 사이트 현황 분석, 웹서버 설정(nginx·Apache)에서 서비스 중인 사이트 찾기·선택, 크기 제한을 서버 용량 10% 기준으로, 위쪽 사이트 선택·잔여 캐시만 표시, 제안·오류 3단계 흐름(답변 시 처리 완료), 생성 코드 주석(화면 언어), AI 입력창 강조, 관리자 화면 외형(색·구성) 설정, 캐시 상세 가로 스크롤 제거(사용자 제안 반영). - **1.1.6** 캐시 소진 즉시 중지(모델별 비용 보정·부분 스트리밍 반영·97% 기준)와 파이프라인·기획 단계 이어하기, 캐시 상세 폭 조정, 관리자 충전 폼 위치, 제안·오류 신고 환경 정보 수집(사전 고지), 관리자 "다음 작업 반영" 결정과 작업 서버 수집(`scripts/collect-backlog.js`). - **1.1.4** 좌측 창 상하 분할(상태 창 위쪽), 발행 카드 간소화, 발행 취소 403 수정(.unpublished 표식·작업 폴더 보호), 수정 완료 배너 간소화, 검증 작업 표시, 캐시 내역 5행 스크롤 + 상세 보기(일·주·월), 사이트 관리자 계정 생성 갱신·직접 주소 표시, ? 풍선 화면 안 배치. - **1.1.2** 다국어(원본 카탈로그 내려받기 → 번역 파일·ZIP 드래그앤드롭 업로드, 언어 켜기/끄기, 구버전 번역 자동 숨김), 등급 표 간결화, 페이지 수 옵션, 재배포 버튼 안전장치, 수정 완료 배너·상세 내역, 작업 단위 캐시 합계, 사용자 제안·오류 신고와 AI 자동 분석(관리자 탭). - **1.1.0** 오류 점검(엔진 준비 확인·안내), 버전 표시, 발행 취소 시 "수정 중" 안내 페이지, 수정마다 모델 등급 선택과 맥락 유지, ? 도움말, 생성 탭 독립·중단 후 새로 만들기, 수정 진행 단계 표시, 제품명 AI Webmaker, 사이트 폐기, 큰 수정 지시 입력창, 재배포 버튼 정리, 모바일 미리보기, 모델 등급 비용 가이드. - **1.0.3** 배포 후 운영 주소 표시, 공통 기능 모듈(회원·게시판·문의·설문·예약·갤러리·지도·후기·FAQ + 사이트 관리자 페이지, `modules/`), AI에게 맡기기 단순화(추가 입력만 받고 끝까지 자동 생성, 수정 횟수 한도·발행 대기), 생성/관리/설정 탭, 내가 고르기 속도 개선·사실정보 단순화·버튼 정리, 수정 요청 오류 수정. - **1.0.2** 신규 회원 기본 캐시, 사이트별 배포 위치 지정(관리자 전용), 문의 메일 실제 발송 검증·재발송, 사이트 접속 통계(서버 로그 측정 + 브라우저 비컨 추정), 사용자 화면의 "사이트 정보" 카드(접속·네트워크 인/아웃·디스크·DB 사용량). - **1.0.1** 캐시(크레딧) 도입: 관리자가 사용자별 캐시를 설정·추가하고 작업 비용이 캐시에서 차감됨. 사용자 화면의 토큰·예상 금액 표시를 캐시 사용액/잔액으로 대체. 캐시 소진 시 작업을 멈추고(지금까지의 작업 저장) 충전되면 자동으로 이어서 진행. 모델은 관리자가 지정하는 등급(기본·중급·고급)으로만 선택하며 모델명은 사용자에게 보이지 않음. 사용자 자신의 LLM 연결 기능 삭제. 외부 서비스용 캐시 조정 API(`X-Service-Key`). - UI: `http://192.168.20.66/` (로그인 필요, 계정이 없으면 회원가입: 아이디·비밀번호·이름(회사명)) - 생성된 사이트: `http://192.168.20.66/<32자리 16진 ID>/` (메인 `index.html`), 하위 페이지는 `//page.html` ## 구조 ``` 브라우저 ──> nginx:80 ─┬─ / ──> Node 서버 127.0.0.1:3080 (UI + API + SSE) └─ /<32hex>/... ──> /var/www/sites/<32hex>/ 정적 파일 Node 서버 ──> claude -p --model claude-opus-5-5 (작업 폴더 = /var/www/sites/) ``` | 경로 | 역할 | |---|---| | `server.js` | HTTP 서버. 사이트 생성/수정 API, SSE 진행 스트림, 정적 UI | | `lib/claude.js` | Claude Code 헤드리스 실행(`stream-json` 파싱, 토큰 사용량 정규화) | | `lib/prompt.js` | 입력 항목(제목·목적·주요 컨텐츠·디자인·기타)을 프롬프트로 정리 | | `lib/store.js` | `data/sites/.json` 에 명령 이력·첨부 목록·토큰 합계 저장 | | `lib/auth.js` | 회원(scrypt 해시) 저장 `data/users.json`, HMAC 서명 세션 쿠키 | | `lib/gitstore.js` | 사이트별 git 저장소(`data/git/`) 커밋과 Gitea 브랜치 푸시 | | `lib/uploads.js` | 첨부 파일 저장(`/var/www/sites//uploads/`), 형식·용량 검사, docx 텍스트 추출 | | `hooks/guard.sh` | PreToolUse 훅. 사이트 폴더 밖 Write/Edit 차단 | | `public/` | 좌측 탭(생성·수정·관리·도메인·설정, v1.4.14)·진행 상태 / 우측 미리보기 iframe(관리 탭에서는 사이트 관리 창 `manage-ui.js`). 상세 진행 로그는 고객에게 보이지 않고 스피너(진행 중)/체크(완료)/경고(오류) 표시기만 노출 | | `lib/config.js` · `scripts/config.js` | v1.5.7 설정 파일(`data/config.json`) — 항목표·우선순위·환경 변수 이전, 명령줄 도구 ([docs/CONFIG.md](docs/CONFIG.md)) | | `setup-server.js` · `public/setup.*` · `lib/setupapi.js` | v1.5.7 초기 설정 페이지(별도 포트, systemd `webmaker-setup`; v1.5.11 기본 8795·0.0.0.0, 겹치면 다음 포트)와 설정·연동 상태 API (서비스 `/setup`·관리자 콘솔 시스템 설정 탭과 공용) | | `lib/setupstate.js` · `lib/setupauth.js` | v1.5.11 설치 점검표(관리자 비밀번호·AI 인증·공개 주소 → 첫 화면 포워딩)와 설정 페이지 인증(접속 토큰·세션·시도 제한, 두 서버 공용) ([docs/FIRST-INSTALL.md](docs/FIRST-INSTALL.md)) | | `lib/connectors/` | v1.5.7 외부 서비스 연동 모듈 — `gitea.js`(소스 보관), `arimail.js`(메일 서버), `index.js`(등록소) ([docs/ARIMAIL.md](docs/ARIMAIL.md)) | | `deploy/` | nginx 설정, systemd 유닛(`webhosting-agent`, `webmaker-setup`), 설치 스크립트(`install.sh`, `remote-install.sh`, `remote-update.sh`, `install-services.sh`, `install-gitea.sh`, `install-arimail.sh`) | ## 로그인과 사용자별 사이트 - `/login` 에서 로그인하거나 회원가입한다. 회원가입은 아이디(영문 소문자·숫자 3~32자), 비밀번호(6자 이상), 이름(회사명)만 받는다. - 비밀번호는 scrypt 로 해시해 `data/users.json` 에 저장하고, 세션은 `data/session.secret` 로 서명한 HttpOnly 쿠키(30일)다. - 사이트에는 `ownerId` 가 기록되어 목록·조회·수정 모두 본인 것만 가능하다. 회원의 이름(회사명)은 생성 프롬프트에 "운영 주체"로 전달된다. - 생성된 페이지 자체(`//`)는 공개 URL 이므로 로그인 없이 열린다. ## Gitea 소스 보관 — v1.4.14 서비스 서버의 로컬 Gitea 사이트마다 로컬 git 저장소(`data/git/`, work-tree 는 `/var/www/sites/`, 사이트 폴더에 `.git` 없음)를 두고, 변경이 생길 때마다 커밋해 Gitea 로 푸시한다. - 원격(`SITES_GIT_REMOTE`): **v1.4.14 부터 서비스 서버 안의 전용 Gitea** — Docker 컨테이너 `webmaker-gitea`(HTTP `:3030`, SSH `127.0.0.1:2230`), 서비스 전용 계정 `webmaker`, 비공개 저장소 `webmaker/sites`, ssh 별칭 `webmaker-gitea`. `deploy/install-gitea.sh` 가 컨테이너·계정·저장소·키 등록·서비스 유닛 설정을 한 번에 하고, `--migrate-from <옛 원격>` 으로 1.4.5 까지 회사 소스 저장소(`source.ibit.kr` `OZ-Group/WebHosting_AI_agent`)에 섞여 있던 `sites/*` 브랜치를 옮긴다(`scripts/migrate-gitstore.sh`, 옛 브랜치는 지우지 않음). 구성·확인·백업: [docs/GITEA-LOCAL.md](docs/GITEA-LOCAL.md). 관리자 콘솔 › 설정 › 소스 저장소에서 연결 상태와 이 서버의 공개키를 본다. - 브랜치 구조(사이트 랜덤코드로 분리): - `sites//main` — 항상 최신 - `sites//changes/0001-create`, `0002-ai-update`, `0003-manual-edit` … — 생성, AI 수정 지시, 소스 편집기 저장 등 변경마다 하나 - 커밋 작성자는 회원 이름(회사명)과 `<아이디>@webhosting.local`. 브랜치·푸시 상태는 사용자에게 보이지 않고, UI 에는 "이전 버전으로 되돌리기" 목록만 표시된다. 되돌리기(`POST /api/sites/:id/versions/:no/restore`)는 그 시점의 파일(과 Brief 스냅샷)을 복원하고 복원 자체를 새 버전(`restore`)으로 기록한다. 푸시 실패분은 다음 변경 때 자동 재시도한다. ## 관리자 콘솔 (`/admin`) v1.3.2 탭: 사이트 관리 · 로그 보기(종류·기간·회사·사이트·검색) · 회원·청구 · 배포 서버 · 사용자 문의(문의/제안/오류/요구, AI 1차 분석·답변 초안) · 메일 시스템(사이트 단위) · 언어 · 화면 설정(실제 화면 미리보기) · 설정. "새로고침" 은 지금 탭과 개요를 다시 불러오며, 열린 탭은 주소 #탭 으로 유지된다. 관리자 계정은 로그인 후 곧바로 `/admin` 으로 이동하며 사이트 생성 화면은 쓰지 않는다(사이트 관리 전용). 일반 화면(밝은 배경·파란 포인트)과 구분되도록 짙은 남색 배경·호박색 포인트의 다크 테마다. - **사이트 관리**: 모든 회원의 사이트 목록·상태·오류·토큰·비용, 열기/중단/삭제, 상세(명령 이력·모델·상세 로그·프롬프트·버전·기획 상태·검증 리포트). - **오류 로그**: 사용자에게 숨긴 상세 원인 전체. - **회원 · 청구**: 모델별 호출·토큰·비용(USD)과 단가, 회원별 비용(USD) → 원화 정산 → 청구 금액(마진 포함) → 선결제 충전 → 잔액. 회원을 누르면 일자별 정산(적용 환율 표시)과 충전 내역, 충전 추가/취소. 역할 변경. - **제안 · 오류**(v1.2.0 흐름): 새 글(접수) → AI 분석이 붙으면 검토 중 → 관리자가 답변을 저장하면 처리 완료(반려 없음). 답변은 사용자 설정 탭 "내 제안 · 오류 신고" 에 펼쳐 보인다. "다음 수정에 반영" 으로 결정한 글은 작업 서버가 수집하고, 릴리스에 반영하면 `node scripts/collect-backlog.js --implemented --version ` 로 반영 완료 표시한다. - **설정 › Webmaker 화면 외형**(v1.2.0): 색 구성 미리 설정·색 10가지·왼쪽 패널 폭·글자 크기·모서리·간격·패널 위치·위쪽 영역 스타일. `/theme.css` 로 사용자 화면·로그인·사이트 관리자 페이지에 적용(저장 전에는 기존 화면 그대로). - **AI 연결**(v1.5.16, 설정 탭에서 분리): Claude 모델 연결 방식(서버 로그인 / `claude setup-token` 으로 만든 OAuth 토큰 / Anthropic API 키) 저장과 연결 테스트, **사용할 Claude 모델** 체크(`settings.claudeModels`), AI 제공자(OpenAI·Gemini·Groq·OpenRouter·호환 서버) 키·모델 조회·제공 모델 선택. 여기서 켠 모델만 설정 탭의 등급·내부 호출 모델에 지정할 수 있다(서버도 저장 때 검사). - **설정 › 회원가입 규칙**(v1.5.27): 회원가입 허용 ON/OFF(`service.allowSignup`)와 가입 후 이용 방식(`settings.signupApproval` — 즉시 / 관리자 승인). 승인 방식이면 회원 · 청구 탭에서 "가입 승인"·"거절(삭제)", 탭 배지에 대기 수. - **설정**: 모델 등급 지정과 **내부 호출 모델**(v1.5.16 — 등급과 무관한 서비스 내부 호출(사용자 문의 AI 분석 등)에 쓰는 모델, 등급 지정과 같은 목록에서 선택; 1.5.13 까지의 "Claude 기본 모델" 대체), 환율 모드(자동: ECB·시장 중간환율 + 스프레드 % / 직접 입력: 당일 USD 구입 가격)와 마진 %. 청구 계산: 명령별 비용(USD, Claude Code 보고값) × 그 명령 실행일(KST)의 USD 구입 가격(원) × (1 + 마진%). 환율은 `data/fx.json` 에 날짜별로 캐시된다(자동 모드: frankfurter.dev → open.er-api 순, 조회 실패 시 직전 값). 선결제는 `data/billing.json` 에 충전 내역으로 기록된다. 설정은 `data/settings.json`(0600)에 저장되며 토큰/키는 화면에 마스킹된다. - 회원에는 `role`(`user`|`admin`)이 있다. 관리자는 `ADMIN_USERS` 환경변수(아이디 쉼표 구분)나 `node scripts/admin.js grant ` 로 지정한다. 관리자 계정은 메인 화면 상단의 "관리자" 링크로 `/admin` 에 들어간다. - 관리자 화면: 모든 회원의 사이트 목록(방식·단계·상태·명령 수·오류·토큰·비용), 사이트별 명령 이력과 **상세 로그**(도구 호출, 도구 오류, 프롬프트, 실시간 스트림), 전체 오류 로그, 회원 역할 변경. - 사용자 화면에는 상세 로그와 원인 메시지가 보이지 않는다. 사용자에게는 "작업을 완료하지 못했습니다. 다시 시도해 주세요." 같은 일반 안내만 표시되고, 원인(`도구 오류: self signed certificate`, 종료 코드 등)은 `debug` 레벨로 명령 이력(`commands[].log`)에 남아 관리자만 본다. SSE 도 역할에 따라 필터링된다. ## 참고 사이트 수집 참고 사이트와 기존 사이트 URL 은 AI 가 WebFetch 로 직접 열지 않고, 서버가 먼저 본문을 가져와 사이트 폴더의 `.refs/ref-N.txt` 에 저장한 뒤 AI 가 Read 로 읽는다(`lib/refs.js`). 인증서 검증 실패(자체 서명·만료), EUC-KR 인코딩, 리디렉션을 처리하며 실패하면 사용자가 적은 참고 포인트만 반영한다. WebFetch 는 보조 수단이며, 국내 사이트의 인증서 문제로 실패하지 않도록 Claude 실행 환경에 `NODE_TLS_REJECT_UNAUTHORIZED=0` 을 준다(`CLAUDE_ALLOW_INSECURE_TLS=0` 으로 끌 수 있음). `.refs/` 는 nginx 가 서비스하지 않고 git 보관에서도 제외된다. ## 문의 접수 위젯 (사이트 안에서 메일 발송) 생성 사이트의 문의·예약 폼은 `mailto:` 대신 **표준 위젯**(`templates/contact-widget.html`)을 그대로 삽입한다. 생성·수정 프롬프트에 사이트별 엔드포인트(`<폼 접수 서버>/api/forms/<사이트ID>/contact`)가 채워진 위젯 코드가 포함되고, AI 는 data-* 속성과 script 를 바꾸지 않고 CSS 로만 꾸민다. 검증은 `mailto:` 사용과 위젯 존재 여부를 검사하며, 자동 수정은 mailto 를 위젯으로 대체한다. 접수 흐름: 방문자 제출 → 서비스의 공개 엔드포인트(CORS: 서비스·사이트 도메인·배포 서버 공개 URL 출처만 허용, 허니팟, IP/사이트별 속도 제한, 길이 검증) → Resend API 로 메일 발송(발신 `webmaster@사이트도메인`이 Resend 검증 도메인이면 그것, 아니면 기본 발신 도메인; `reply_to` 는 문의자) → `data/inquiries/<사이트ID>.json` 에 기록. 수신 주소는 사이트의 "문의 수신 이메일"(기본 사항 / Brief 사실정보 `email`)이며 HTML 에는 노출되지 않는다. 사용자 화면의 "문의 접수 내역" 카드에서 접수·발송 여부를 본다. 관리자 콘솔 › 설정 › 메일 발송: 메일 에이전트 키 파일 경로(.key 의 `RESEND_API_KEY`) 또는 키 직접 입력(암호화 저장), 기본 발신 도메인, 발신자 이름, 폼 접수 서버 주소(공개 URL; 사설 IP 서비스에서는 공개 인스턴스 주소를 지정), 테스트 메일 발송. ## 오류 페이지 존재하지 않는 페이지·숨김 파일·서버 오류는 서비스 서버 nginx, 생성되는 호스팅 vhost, Node 앱 모두 "페이지를 찾을 수 없습니다" / "일시적인 오류가 발생했습니다" 라는 일반 안내 페이지(`public/404.html` 과 동일 내용)로 응답하고 `server_tokens off` 로 서버 버전을 숨긴다. 다른 서비스의 내용이나 서버 종류가 드러나지 않는다. ## 배포 서버 (여러 웹호스팅 서버 관리) 관리자 콘솔 › 배포 서버에서 사이트를 실제로 호스팅할 서버를 여러 개 등록하고 기본 배포 서버를 지정한다. 종류는 두 가지다. - **로컬**: 이 서비스 서버의 디렉터리(예: `/var/www/sites`, 즉 지금 서비스되는 위치 — 같은 경로면 이미 배포된 것으로 처리). - **SSH 원격**: 주소·포트·사용자·사이트 저장 경로(공개 URL 은 선택)를 입력하고, "키 생성"으로 이 서버가 만든 전용 ed25519 키의 공개키를 원격 서버 `~/.ssh/authorized_keys` 에 등록하거나 기존 개인키를 붙여넣는다. "연결 테스트"는 접속 → 사용자 → 저장 경로 존재/생성 → 쓰기 권한 → tar 순으로 진단하고, 실패 시 원인과 서버에서 실행할 명령(예: `sudo chown <경로>`)을 알려 준다. 배포는 `tar | ssh tar` 로 전송한 뒤 임시 디렉터리를 원자적으로 교체한다(`.git`, `.claude`, `.refs` 제외). 원격 서버에는 `tar` 만 있으면 된다. 사용자가 "최종 확정(발행)"하면 사이트의 지정 서버(없으면 기본 서버)로 자동 배포되고 결과 URL 이 표시된다. 확정 전에 **도메인을 여러 개**(예: `www.ibit.kr`, `ibit.kr`) 입력하면 배포 서버의 nginx 에 `server_name` 으로 모두 등록한 vhost(`/etc/nginx/sites-available/wh-.conf` 또는 `conf.d/`)를 만들어 어느 도메인으로 접속해도 열리게 한다(대상 서버에 nginx 와 비밀번호 없는 `sudo` 필요, 배포 서버 설정의 "vhost 자동 설정"으로 끌 수 있음). DNS 는 운영자가 배포 서버로 가리켜야 한다. **기본 오류 서버**: 매 배포마다(sudo 허용·vhost 자동 설정인 서버) 배포 서버 nginx 에 `default_server` 가 있는지 점검하고, 없으면 `wh-default.conf` 로 80 포트는 "페이지를 찾을 수 없습니다" 안내 페이지(404)를, 443 포트는 `ssl_reject_handshake` 로 미등록 호스트의 HTTPS 요청을 거부하도록 설정한 뒤, 미등록 호스트 요청이 실제로 안내/거부되는지 검증해 결과를 배포 결과에 표시한다. 기존 default_server 가 있으면 유지하되 다른 내용이 노출되면 경고한다. 관리자 배포 서버 탭의 "오류 서버 점검"으로 수동 점검도 가능하다. **첫 배포와 재배포**: 같은 서버에 성공한 배포가 있으면 재배포로 처리해 파일 갱신·기본 오류 서버 점검·vhost 재적용만 하고, 대상 서버에 이미 사이트 인증서가 있고 모든 도메인을 포함하면 HTTPS 설정을 유지한 채 인증서 재발급을 건너뛴다(도메인이 추가되었거나 인증서가 없을 때만 발급). 도메인 연결이 끝나면 **Let's Encrypt SSL** 을 자동 발급한다(배포 서버 설정 "SSL 자동 발급", 기본 켜짐): DNS 가 배포 서버를 가리키는 도메인만 골라 `certbot --nginx --redirect` 로 인증서를 받고 HTTPS 서버 블록과 HTTP→HTTPS 리디렉션을 만든다(certbot 이 없으면 apt 로 설치 시도, 갱신은 certbot 타이머). 알림 이메일은 설정 탭에서 지정한다. nginx 설정 쓰기와 certbot 은 **sudo** 가 필요하므로 배포 서버마다 "sudo 사용 허용"을 운영자가 켜야 하며, 관리자 배포 시 허용되지 않은 서버면 확인 대화상자에서 허용 여부를 묻는다. 허용하지 않으면 파일만 배포된다. 배포 후에는 접속 확인을 자동으로 수행한다: 도메인별 DNS(A 레코드가 배포 서버를 가리키는지)와 배포 서버의 vhost 응답, 공개 URL 응답을 검사해 관리자 배포 결과 패널과 사용자 발행 상자에 ✓/✗ 와 조치 방법(예: A 레코드 추가)을 표시한다. 관리자 사이트 상세의 "접속 확인"으로 언제든 다시 검사할 수 있다. 배포 서버의 공개 URL 은 그 서버의 웹서버가 저장 경로를 `공개URL//` 로 직접 서비스할 때만 설정한다. 배포가 완료되면 되돌리기용 이전 버전(로컬·Gitea 의 `sites//changes/*` 브랜치)을 모두 삭제하고 현재 배포본 하나만 남긴다(`sites//main` 강제 갱신). 이후 수정 과정의 버전은 다음 배포 때 다시 정리된다. 관리자는 사이트 상세에서 서버를 바꾸거나 즉시 배포할 수 있으며, 사이트를 삭제하면 배포본도 제거한다. 설정은 `data/deploy-targets.json`, 키는 `data/deploy-keys/`(0600). 원격 호스팅 서버의 nginx 예시(저장 경로 `/var/www/sites`, 공개 URL `https://host.example.com`): ```nginx server { listen 80; server_name host.example.com; location ~ "^/[a-f0-9]{32}(/|$)" { root /var/www/sites; index index.html; try_files $uri $uri/ =404; location ~ /\. { deny all; } } } ``` ### 도메인 HTTPS — 사이트별 인증서와 플랫폼 와일드카드 인증서 (v1.4.14) - **고객 도메인**(example.com, www.example.com): 발행(배포) 때 DNS 가 배포 서버를 가리키는 도메인에 Let's Encrypt 인증서(`wh-<사이트ID>`, webroot)를 발급하고 HTTP→HTTPS 로 돌린다(1.0.x 부터). - **플랫폼 서브 도메인**(이름.ibit.kr): 사이트마다 받지 않고 **와일드카드 인증서 `*.ibit.kr`**(`wh-wild-ibit.kr`) 하나로 HTTPS. 관리자 콘솔 › 설정 › SSL 에서 운영자가 발급·갱신한다(DNS 인증 — 플랫폼 DNS 가 Openprovider 면 자동, 수동이면 화면에 뜨는 TXT 를 DNS 에 넣음). 와일드카드가 없으면 서브 도메인은 HTTP 로 열리고 발급되면 재배포 없이 HTTPS 가 적용된다. **v1.5.17**: 서버에 다른 절차로 받아 둔 `*.<도메인>` 인증서(이름 무관)도 SAN 으로 찾아 같은 방식으로 쓰며, 서비스 기동 때 HTTP 로 배포돼 있던 서브 도메인 사이트를 자동으로 HTTPS 로 바꾼다(`node scripts/ssl-wildcard.js --apply`). nginx 설정은 두 묶음을 별도 server 블록으로 쓴다(`lib/deploy.js vhostConf`, `lib/ssl.js`, `scripts/acme-dns-hook.js`). 절차·메시지: [docs/SSL-WILDCARD.md](docs/SSL-WILDCARD.md). ## 여러 AI 제공자와 사용자 자기 모델 - 관리자 콘솔 › **AI 연결** 탭(v1.5.16, 이전 설정 탭) › **AI 제공자**: OpenAI(ChatGPT), Google Gemini, Groq, OpenRouter, 기타 OpenAI 호환 서버의 API 키를 저장하고 "모델 조회"로 목록을 불러온 뒤 이 서비스에서 쓸 모델과 단가($/1M)를 고른다. 키가 있고 활성화된 제공자의 선택 모델만 등급·내부 호출 모델 목록에 나타난다(Claude 는 같은 탭의 "Claude 모델 연결"·"사용할 Claude 모델" 을 따름). 키는 AES-256-GCM 으로 암호화해 `data/settings.json` 에 저장한다. - 사용자 화면 상단 **AI 모델** 선택: 사이트마다 사용할 모델을 고르며(`modelRef`, 예: `openai:gpt-5.2`), 생성·수정·기획 단계 모두 그 모델로 실행된다. - **내 AI 연결**: 사용자가 자기 API 키(OpenAI·Gemini·Groq·OpenRouter·호환 서버) 또는 Claude 구독의 `claude setup-token` 토큰을 직접 등록한다. "연결 확인"으로 모델 목록을 받아 쓸 모델을 고르고 저장하면 `data/user-connections.json`(암호화, 0600)에 보관되어 다음에도 재사용된다. 내 연결 모델로 실행한 작업은 사용자 키로 과금되므로 청구액이 0 원으로 기록된다. - OpenAI 는 API 키 대신 **서버 로그인**(OpenAI Codex CLI 의 ChatGPT 계정 로그인)으로도 쓸 수 있다. 서버에서 `npm i -g @openai/codex` 후 `codex login --device-auth` 로 로그인하고, 관리자 콘솔의 OpenAI 제공자에서 "서버 로그인"을 고르면 정적 모델 목록(gpt-5.2-codex, gpt-5.2, gpt-5.1 등)이 제공되며 `lib/engine-codex.js` 가 `codex exec --json` 으로 실행한다(파일 변경 사용량은 turn.completed 의 토큰으로 집계). - 서버 로그인 **모델 조회**(v1.4.14): codex CLI 가 백엔드에서 받아 두는 모델 목록 파일(`~/.codex/models_cache.json`)을 읽어 그 계정에 보이는 모델 전체(이름·설명·숨김·종료 예정)를 한 번에 보인다. 파일이 없으면 기본 모델로 한 번 실행해 만든다. - 실행 엔진: Anthropic 은 Claude Code CLI(파일 도구), 그 외는 `lib/engine-openai.js` 의 OpenAI 호환 Chat Completions 도구 호출 에이전트(list/read/write/edit/delete_file, 사이트 폴더 밖 접근 차단)로 같은 프롬프트·진행 이벤트·사용량 집계를 제공한다. Gemini 는 OpenAI 호환 엔드포인트를 사용한다. ## 인증(Claude)과 모델 API 키 대신 서버에 로그인된 Claude Code CLI(구독 계정)를 그대로 사용한다. 모델은 `CLAUDE_MODEL`(기본 `claude-opus-5-5`). 모델에 노출되는 도구는 `Write, Edit, MultiEdit, Read, Glob, Grep` 뿐이며(`--tools`), Bash·웹 도구는 비활성화된다. ## 다국어 — v1.1.2 (v1.3.2 템플릿 패턴) - v1.3.2: 값이 들어가는 문장(템플릿 문자열)은 카탈로그에 `현재 사이트: {1} · 화면: {2}` 같은 패턴(pattern: true)으로 들어간다. 번역문에도 같은 {숫자}를 넣어야 하며(순서 변경 가능), 다르면 업로드 때 제외된다. 런타임은 정확한 문장이 없으면 패턴으로 맞춰 값을 끼워 넣는다. - 번역 키 대신 **원문(한국어) 자체**를 키로 쓴다. `lib/i18n.js` 가 `public/index.html`, `login.html`, `site-admin.html`, `app.js`, `wizard.js`, `import.js`, `modules-ui.js`, `site-admin.js` 에서 한글이 든 텍스트·속성(placeholder/title/data-tip/alt)·문자열 리터럴을 추출해 카탈로그를 만든다(변수가 든 템플릿 문자열은 제외). `node scripts/i18n-extract.js` 로 `locales/ko.json` 을 갱신해 소스에 함께 둔다. - 관리자 콘솔 › 언어에서 원본 파일을 내려받아 `translation` 을 채우고 `lang`/`name` 을 적어 올린다(JSON 여러 개·ZIP, 드래그앤드롭). 저장 위치 `data/locales/.json`. `sourceVersion` 이 서비스 버전보다 낮으면 구버전으로 숨겨진다. - 화면은 `public/i18n.js` 가 사전으로 치환한다(텍스트 노드·속성, MutationObserver). 언어 선택은 헤더 `#langSelect`(언어가 둘 이상일 때만 표시), `localStorage.wm_lang`, `?lang=`. - v1.3.10: 관리자 콘솔(`admin.html`·`admin.js`)·사이트 관리자도 번역 대상(관리자 콘솔에 언어 선택). 서버 목록·메시지 파일(`SERVER_FILES`)과 백틱·HTML 템플릿까지 추출해 사이트 안 모든 한국어가 `locales/ko.json` 에 들어간다. AI 프롬프트·사이트 파일 내용은 `// i18n-ignore-start` … `// i18n-ignore-end` 로 제외. - v1.3.10 언어 파일 형식 2: `guide`(번역 방법)·`stats`, 항목마다 `status`("번역 필요"/"번역됨")·`group`·`placeholders`. 화면 코드는 값과 문구를 나눠 `T('문구')`, `TF('{1}원', 값)`, 날짜·숫자는 `uiLocale()` 로 쓰고, 언어가 바뀌면 `wm:i18n` 이벤트로 다시 그린다. 서버가 쿠키 `wm_lang` 으로 첫 HTML 을 미리 번역해 보내고, 사전은 브라우저에 버전 표시와 함께 저장한다. ## 접속 통계와 사이트 정보 — v1.0.2 - 측정(서버 로그): `deploy/nginx-wmstats.conf` 의 `wmstats` 형식을 `/etc/nginx/conf.d/` 에 두고, 서비스 서버의 사이트 경로 블록은 `/var/log/nginx/webmaker-sites.log`, 원격 배포 서버의 사이트 vhost 는 `/var/log/nginx/wh-.access.log` 로 남긴다. `lib/stats.js` 가 로컬 5분·원격 15분(SSH, sudo 허용 서버만) 주기로 새 부분만 읽어 `data/stats/.json` 에 일자별 요청·페이지뷰·방문자(일자+IP+UA 해시)·수신/송신 바이트·상위 페이지·유입 경로를 집계한다. 봇 UA 는 따로 센다. 서비스 사용자는 `adm` 그룹이어야 로그를 읽을 수 있다(Ubuntu 기본 nginx 로그 권한 0640 www-data:adm). - 추정(비컨): 모든 HTML 에 `