리뷰 지적 추가 반영(서버 전용).
- trust proxy 를 항상 켜던 것을 TRUST_PROXY=true 일 때만 켜도록 변경. 직접 노출
시 X-Forwarded-For 조작으로 로그인 rate limit 을 우회하던 문제 차단(프록시 뒤면
TRUST_PROXY=true 설정).
- account.local.json 을 0o600(소유자 전용)으로 저장.
- 서버 시작 시 account.local.json 이 없으면 account.json 에서 시드(0o600). 이렇게
하면 재배포 직후(로그인 전에도) 로컬 계정 파일이 항상 존재해, 이후 account.json
을 안전하게 추적 해제할 수 있다.
- account.example.json 템플릿 추가.
account.json 자체의 git 추적 해제는 서버가 한 번 재배포되어 account.local.json 이
생성된 뒤 후속 커밋에서 처리(그 전에 지우면 pull 시 삭제되어 로그인이 막힘).
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
리뷰 지적 4건 반영(서버 전용, exe 영향 없음).
- 로그인 IP 기준 실패 제한(15분 창 10회 → 15분 차단). 브루트포스 + scrypt CPU
남용 방지. 인메모리, 무한 성장 가드 포함.
- 운영 계정을 gitignore 된 account.local.json 으로 이전. readAccounts 는 local
우선, 없으면 추적되는 account.json 을 시드로 읽음. writeAccounts 는 local 에만
기록 → 첫 로그인 자동 해시 업그레이드부터는 추적 평문 파일을 더 쓰지 않음.
(account.json 을 git rm --cached 하면 서버 pull 시 삭제되는 위험이 있어 추적 자체는
건드리지 않고, 실질 사용 파일만 분리.)
- 세션 쿠키 secure 를 SESSION_COOKIE_SECURE=true 로 켤 수 있게(HTTPS 배포용).
- 평문→해시 자동 업그레이드 저장 실패를 조용히 무시하지 않고 console.error 로 기록.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- 운영자 로그인 비밀번호를 평문 비교(===)에서 Node 내장 scrypt 해시 + 상수시간
비교(verifyPassword)로 전환. 새 파일 src/server/password.ts. 기존 account.json
의 평문 비밀번호는 그대로 검증되며, 로그인 성공 시 scrypt 해시로 자동 업그레이드
후 저장(writeAccounts 추가). 외부 의존성 없음.
- 세션 시크릿을 하드코딩 폴백('...dev-secret') 대신, 환경변수 우선 → 없으면
.session-secret 파일에 영구 랜덤값 생성/보관하도록 변경(세션 위조 방지, 재시작
후에도 세션 유지). .session-secret 은 .gitignore 에 추가.
서버(사이트) 전용 변경이라 설치기 exe 는 영향 없음(재빌드 불필요).
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
전체 코드 재검토 기반의 안전한 개선 1차 배치.
- 메인 설치기에 전역 uncaughtException/unhandledRejection 가드 추가. nat-upnp
(detectExternalIpUpnp)의 비동기 소켓 오류로 앱이 조용히 종료되던 잠재 크래시
방지(포트포워딩 도구 v0.3.18 과 동일 대비).
- 서버: fetchVideoMeta/fetchPlaylistEntries 에 http(s) URL 검증 추가(운영자 입력이
yt-dlp 플래그로 오인되는 인자 주입 차단). /file/mods/:folder/index.json 의 async
throw → next(error) 로 위임(unhandledRejection 방지). index 라우트 pack 정의
병렬 로드.
- 죽은 i18n 키 정리: installer 로케일의 UPnP/run.bat 관련 19개 + pf 로케일 2개
제거(코드에서 미참조 확인). 크래시 가드용 log.internalError, youtube.invalidUrl 추가.
v0.3.23.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
- _meta.json: customLabels -> terms.{label,showInInstaller,showInInstallerRp}
- Drop builtin protection; any term kind can be deleted/added/toggled
- New public route /manifest/terms/<pack>/index.json for installer term lists
- Installers fetch terms:list dynamically; skip agreement step if list empty
- Term editor: 2 visibility checkboxes (설치기 / 리소스팩 설치기), multi-select
- Migration from old schema preserves custom labels (default: visible in both)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- public route `/manifest/terms/:packKey/:fileName` 가 sendFile 전에
`ensurePackTermsDir(packKey)` 를 호출하도록 수정. 관리자가 사이트 약관
페이지를 한 번도 열지 않은 fresh 배포에서도 설치기가 정상적으로 약관을
받을 수 있다. `loadPackDefinition` 으로 실제 pack 만 허용해 임의 키로
빈 폴더가 생성되는 것을 차단.
- `renamePack`: pack JSON 이름이 바뀌면 `manifest/terms/<oldKey>/` 도
`<newKey>/` 로 함께 rename.
- `deletePackKeys`: pack 삭제 시 약관 폴더도 `fs.rm` 으로 정리 — 동일 key
재생성 시 옛 약관 부활 방지.
- `ensurePackTermsDir` export.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- store.ts: 약관을 manifest/terms/<packKey>/ 폴더별로 저장. 첫 접근 시
legacy 전역 .md 파일을 시드로 자동 복사한다.
- importTerms() 추가: 다른 음악퀴즈의 .md + _meta.json 을 현재 pack 으로
복사한다. 동일 kind 는 source 값으로 덮어쓴다.
- /op/agreement 라우트를 세 단계로 분리:
· /op/agreement → 음악퀴즈 카드 선택 페이지
· /op/agreement/:packName → 해당 pack 의 약관 목록 + 추가 + 불러오기
· /op/agreement/:packName/:kind → 에디터
- 공개 라우트도 /manifest/terms/:packKey/:fileName 으로 변경.
- 설치기 main.ts: state.selectedKey 를 약관 URL 에 포함하도록 수정 (메인 +
rp 양쪽). pack 미선택 상태에서는 에러 반환.
- termsEditor.js: PACK_KEY 를 받아 저장 URL 에 포함.
- 다른 음악퀴즈 후보 select + 확인 모달 + locale 추가.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 약관 편집기 배경/슬래시 메뉴를 사이트 다크 팔레트로 통일 (흰 배경 + 흰 글씨 가시성 문제 해결)
- 약관 목록을 가로 풀폭 1줄씩 세로로 쌓이는 레이아웃으로 변경
- 사용자 정의 약관 추가/삭제 지원
- manifest/terms/_meta.json 에 라벨 저장
- builtin 5종(map/resourcepack/mod/installer/installer-rp)은 삭제 불가, "기본" 배지 표시
- kind 식별자 규칙: 소문자/숫자/하이픈 32자 이내
- 공개 라우트 /manifest/terms/<file>.md 는 isPublicTermsFile() 로 _meta.json 차단
- 0.3.0 → 0.3.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 5종 약관(map/resourcepack/mod/installer/installer-rp) markdown 시드 + manifest/terms/ 노출
- 사이트 /op/agreement 목록 + Notion 스타일 markdown 에디터 (슬래시 명령어, 미리보기)
- 메인 installer: 음악퀴즈 선택 직후 약관 동의 페이지(맵·모드·설치기) 추가
- rp installer: 음악퀴즈 선택 직후 약관 동의 페이지(리소스팩·설치기) 추가
- rp installer 취소 버그 수정: buildResourcepackZip 단계간 + archive.abort() 폴링
- rp installer 취소 UX: 즉시 "취소 중…" 표시, 취소 시 installFailed 알림 생략
- 0.2.6 → 0.3.0 (큰 기능)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- src/shared/i18n.ts: 공용 i18n 로더 (dotted-key + {{placeholder}} 보간)
- locales/server/ko-kr.json: 사이트 + 라우터 + 데이터팩 출력 사전
- EJS 뷰는 res.locals.t 미들웨어로 일괄 적용
- listEditor.js 등 클라이언트 JS 는 사전을 inline <script> 로 주입받아 tt() 헬퍼 사용
- dotenv 도입, src/shared/env.ts 추가
- loadEnv() 가 프로젝트 루트 .env 를 로드 (override=false: 쉘 env 우선)
- getSiteBaseUrl() / getManifestUrl() 헬퍼
- 서버/설치기/리소스팩설치기 진입점에서 loadEnv() 호출
- 설치기 두 종의 기본 MANIFEST_URL 을 SITE_BASE_URL 기반으로 변경
(운영 도메인을 한 곳에서만 바꾸면 됨)
- .env.example 템플릿 + .gitignore 에 .env 추가
- README / docs/admin-site.md 에 환경변수 표·사용법 추가
- installer/renderer.js: 4단계 완료 후 자동 종료 다시 활성화
VS Code surfaced the TS deprecation:
'moduleResolution=node10' is deprecated and won't work in TS 7.0.
Fix: switch the root tsconfig.json from
module: CommonJS, moduleResolution: node
to
module: Node16, moduleResolution: Node16
TypeScript 7 only supports node16/nodenext/bundler. node16 matches the
runtime semantics we already use (Node ≥ 16, CommonJS output via the
absence of "type": "module" in package.json), so the emit is unchanged.
Side effect of Node16 resolution: relative imports must carry the .js
extension. Added .js to every relative import across src/* (17 sites,
8 files). Bare module specifiers (express, electron, node:fs, ...) are
unaffected.
Verified:
- tsc -p tsconfig.server.json — 0 errors
- tsc -p tsconfig.installer.json — 0 errors
- node dist/server/app.js boots; /op login → 302, /op/list → 200
- PackDefinition: replace mods[]/resourcepacks[] with modsFolder (string) + resourcepackPath (string); drop PackAsset
- Editor: replace dynamic add/remove lists with two single inputs; remove the now-dead JS for adding/removing rows
- Server: expose GET /file/mods/<folder>/index.json that returns the list of .jar names; folder name restricted to [a-zA-Z0-9_-]+
- Installer: fetch the listing JSON and download each jar from /file/mods/<folder>/<file>.jar; download the single resourcepack from /file/resourcepacks/<file>.zip directly into resourcepacks/
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step 2 (싱글/멀티): replace auto-advance with a card selection plus a
"다음" button so the user can review their choice before moving on.
Step 4-1 (모드 플랫폼): replace the "설치 / 건너뛰기" buttons with two
cards — "권장 플랫폼 설치" and "기본 마인크래프트로 설치". Selection
only records intent; the actual platform install fires in 4-2 along
with mods/resourcepacks (already handled by installer:client install).
Server: default HOST to 127.0.0.1 instead of 0.0.0.0 so the startup
log prints a Ctrl+클릭으로 바로 열 수 있는 URL. Set HOST=0.0.0.0
externally when public exposure is needed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The installer pulls each pack JSON from /manifest/<file>.json, but the
server was blocking every /manifest/ path. Add a strict whitelist
route that only serves /manifest/<a-zA-Z0-9_-name>.json files (no
directory listing, no path traversal, no other extensions); keep the
catch-all 404 for anything else under /manifest/.
Also strip the manifest URL input and "목록 새로고침" button from
installer step 1 — packs auto-load on page render, only the selectable
list remains.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>