첨부파일 외부 저장소 이동 (bsplus_external_storage)

Rhymix 애드온 lua_external_file을 참고만 하고 모듈로 전면 재설계 — 첨부파일을 로컬 마운트/S3 호환 스토리지/FTP로 자동 이동시켜 서버 용량을 절감

현재 버전 v2.2.0 2026-07-22 ~ 2026-07-28 GPLv2
엿새 동안 v1.0.0 첫 출시부터 v2.2.0까지 총 29번의 버전업이 있었습니다. 처음엔 "저장소 하나 골라서 첨부파일을 그쪽으로 옮기는" 단순한 구조였지만, 실제 운영(Cloudflare R2, 로컬 FileZilla FTP)으로 테스트하면서 발견된 버그를 고치고, "로컬만 쓰던 사람도 S3도 같이 써보고 싶다", "다른 사용자는 계정을 여러 개 쓸 수도 있겠다" 같은 요청을 거치며 로컬+S3+FTP+SFTP/SSH를 동시에 등록하고 우선순위로 자동 전환/failover하는 저장소 체인, 게시판별 저장소 지정(변경 가능), 여기저기 흩어진 파일(레거시 포함)을 여러 게시판 동시에 한 곳으로 모으는 통합 도구까지 갖춘 모듈로 성장했습니다. rhymix.org 배포 게시글에 달린 커뮤니티 댓글 피드백이 v1.8.0·v1.9.0·v1.9.2~v2.0.0의 직접적인 계기가 됐습니다.

변화 타임라인

v2.2.02026-07-28
추가: "저장 용량 자랑" 위젯 (bsplus_storage_widget)
이 모듈을 쓰는 사이트 운영자가 방문자에게 "이만큼 서버 용량을 아꼈다"를 보여줄 수 있는 위젯 요청. 저장소 계정별 "용량 한도(GB)" 입력 필드를 설정 화면에 추가(API로는 버킷/서버 총 용량을 알 수 없어 관리자가 직접 입력, 비워두면 사용량만 표시)하고, 저장소 종류별 실사용량을 집계하는 모델 함수를 새로 만든 뒤, 스킨 2종(원형 도넛 게이지 / 가로 막대형)의 신규 위젯으로 사이트에 실제 설정된 저장소만 자동 나열해 보여줌.
v2.1.02026-07-27 · 커뮤니티 제공 패치
추가: "원 서버로 복원" 통합 옵션 + 처리 건수 관리자 설정
팔공산 님이 v2.0.0 게시판 선택 UI 패치에 이어 "데이터 이전 및 통합" 화면 자체를 수정해서 공유 — 본인은 이걸로 약 3,600건(1GB)을 SFTP로 옮겨 쓰고 계심. 목표 저장소 선택지에 "↩ 원 서버(호스팅) 기본 경로로 복원" 추가 — 다른 목표들과 반대로 이미 이동된 파일을 원래 자리로 도로 내려받고 매핑을 완전히 삭제해 이 모듈 관여 이전 상태로 되돌림(옮긴 곳이 서비스 종료될 경우 대비). 파일이 새 위치에 실제로 자리잡은 걸 먼저 확인한 뒤에만 원격 원본을 지우도록 순서를 지켜 데이터 유실 방지. 한 번에 처리할 건수(매핑된 파일/레거시 파일)도 고정값 대신 화면에서 직접 조절 가능하도록 개선.
v2.0.02026-07-26 · 커뮤니티 제공 패치
개선: "데이터 이전 및 통합"의 대상 게시판 선택 UI
팔공산 님이 v1.9.9 썸네일 문제 해결에 감사하시며, 대상 게시판 선택 UI를 직접 고친 consolidate.html을 공유해주심 — 기존 네이티브 <select multiple>(Ctrl/Cmd+클릭 다중 선택)이 특히 모바일/터치 환경에서 다중 선택이 사실상 불가능했던 문제를 해결한 패치. 체크박스 그리드 + "전체 게시판" 마스터 체크박스(클릭 시 전체 선택/해제 토글) 방식으로 교체 — 폼 필드명은 그대로라 백엔드 수정 없이 그대로 적용됨.
v1.9.92026-07-26 · 실사용 버그 리포트
버그 수정: "기존 썸네일 재생성" 버튼이 항상 같은 30건만 반복 처리하던 문제
팔공산 님이 v1.9.7의 재생성 버튼을 여러 번 눌러도 특정 글의 썸네일이 끝내 고쳐지지 않아 UPDATE xe_files SET thumbnail_filename = ''로 코어 컬럼을 직접 지워서 우회 — 이 방식은 실제로 원격 이전된(로컬 원본이 이미 없는) 파일에는 위험하지만, 이 리포트를 계기로 재생성 버튼의 진짜 결함을 발견함. 후보 조회 쿼리가 "이전됨 + 썸네일 있음"만 조건으로 걸었는데, 재생성에 성공해도 썸네일은 여전히 "있는" 상태라 이 조건에서 절대 빠지지 않아 매번 map_srl 오름차순 맨 앞 30건만 반복 처리하고 뒤에 있는 파일은 버튼을 아무리 눌러도 처리되지 않는 구조였음. thumb_fixed 컬럼을 추가해 재생성 성공한 항목을 표시하고 후보 조회에서 제외 — 이제 반복해서 누르면 뒤에 있는 파일들로 순서대로 넘어감.
v1.9.82026-07-26 · 실사용 피드백 반영
추가: 썸네일 원본 비율 유지 옵션
v1.9.7까지의 크롭 여백 수정/재생성 도구로도 팔공산 님이 "이전 후 썸네일이 예전처럼 안 나온다"고 재차 리포트. 라이믹스 코어 구조상(document.item.phpgetThumbnail()) 화면에 보이는 썸네일은 항상 thumbnail_filename(이 모듈이 만든 로컬 미리보기)을 게시판 설정의 크롭 타입으로 다시 리사이즈한 결과라, "원본 파일 그 자체"가 그대로 표시되는 경로가 코어에 없다는 걸 확인 — 대신 "잘라내지 않고 비율 그대로(레터박스)" 표시하는 옵션을 신규 추가. 관리자 설정에 체크박스 추가(기본 꺼짐), 신규 트리거 document.getThumbnail/comment.getThumbnail(before)로 이전된 첨부파일이 있는 글/댓글의 크롭 타입만 골라 'ratio'로 강제.
v1.9.72026-07-25 · 실사용 버그 리포트
추가: 기존(여백 낀) 썸네일 재생성 도구
v1.9.5에서 앞으로 새로 만드는 로컬 미리보기의 크롭 여백 문제는 고쳤지만, 이미 만들어진 미리보기는 자동으로 다시 만들어지지 않는다는 게 팔공산 님 재테스트로 확인됨 — files/thumbnails(코어의 표시 캐시)만 지우고 재시도해도 여전했던 이유는, 이 모듈이 만들어둔 원본 미리보기 자체가 옛날(레터박스) 방식 그대로 남아있었기 때문. ensureLocalThumbnail()thumbnail_filename이 이미 있으면 건드리지 않고 넘어가는 구조라 자동 복구가 안 됐음. [매핑 목록 / 레거시 정리] 탭에 "기존 썸네일 재생성 (여백 문제 수정)" 버튼 신규 추가 — 기존 미리보기 삭제 + DB 값 비움 → 원격 원본 재다운로드 → v1.9.5 새 로직으로 재생성. 한 번에 최대 30건.
v1.9.62026-07-25 · 실사용 피드백 반영
UX 개선 2건: 게시판별 저장소 현황 표시 + 대상 게시판 목록 확대
팔공산 님 피드백. "이 게시판은 어느 저장소로 옮겨졌는지 구별되면 좋겠다" → "데이터 이전 및 통합" 화면에 게시판별 미이동/로컬/S3 계정1~3/FTP 서버1~3/SFTP 현황 표 추가 — 한 게시판이 여러 칸에 걸쳐 있으면 아직 한 곳으로 안 모인 상태임을 바로 확인 가능. "대상 게시판 목록이 전체 게시판 다 보이면 편하겠다" → 목록 자체는 이미 전체 게시판(최대 500개)을 포함했지만 선택창이 고정 6줄이라 스크롤해야 했던 것을, 게시판 개수에 맞춰 자동으로 늘어나도록(최소 6~최대 20줄) 변경.
v1.9.52026-07-25 · 실사용 버그 리포트
버그 수정: 크롭 지정 썸네일이 원본 비율대로 안 잘리던 문제
팔공산 님 실사용 리포트: 썸네일 표시 방식을 "크롭"으로 지정해도 이 모듈이 만든 로컬 미리보기에서는 흰 여백이 낀 채로 나오고 원본이 박스에 꽉 차게 안 잘림. 원인은 ensureLocalThumbnail()이 만드는 로컬 미리보기가 최종 썸네일이 아니라 라이믹스 코어가 실제 썸네일을 만들 때 "원본 대신 읽는 소스"인데, 이걸 고정 500×500 정사각형에 레터박스(여백) 모드로 만들고 있었던 것 — 원본이 정사각형이 아니면 흰 여백이 미리보기 자체에 박혀서, 코어가 나중에 "크롭"으로 다시 잘라도 그 여백까지 함께 잘려 들어감. 고정 정사각형 대신 원본 비율을 유지한 채 긴 변만 최대 1600px로 축소하도록 변경 — 어떤 모드로 다시 리사이즈되든 여백·왜곡 없이 정상 동작.
v1.9.42026-07-25 · 실사용 버그 리포트
버그 수정: "데이터 통합"에서 일부 게시판만 썸네일이 안 생기던 문제
팔공산 님 실사용 리포트: 게시판들을 통합 이전한 뒤 어떤 게시판은 썸네일이 정상 생성되는데 어떤 게시판은 안 됨 — 안 되는 게시판도 글을 하나하나 수정저장하면 그때는 생김. 원인은 "데이터 이전 및 통합"의 두 경로(① 매핑 자체가 없던 레거시 파일 이동 ② 이미 다른 저장소로 매핑돼 있던 파일을 새 목표로 재이동) 중, v1.9.2에서 썸네일 생성 로직을 넣을 때 ②번 경로만 빠뜨렸던 것 — 이 경로가 다루는 매핑 레코드에는 애초에 파일 정보(thumbnail_filename 등)가 없어서 검토 때도 놓쳤음. 그 결과 마침 "이미 매핑돼 있던" 파일이 있던 게시판만 재이동해도 썸네일이 안 생기고, 매핑이 아예 없던 순수 레거시 게시판은 정상적으로 생겨서 "게시판마다 다르다"고 느껴진 것. ②번 경로에서 원격 원본을 임시 파일로 내려받은 직후 FileModel::getFile()로 파일 정보를 조회해 썸네일 생성을 호출하도록 추가.
v1.9.32026-07-25 · 실사용 버그 리포트
버그 수정: "누락된 썸네일 생성" 버튼이 일정 개수 이후로 멈추던 문제
팔공산 님 실사용 리포트: 약 1800개 파일을 이전한 뒤 백필 버튼을 눌렀더니 처음엔 몇 개가 살아나다가 그 뒤로는 지연도 없이 아무리 눌러도 효과가 없었음. 원인은 후보를 가져오는 쿼리가 map_srl 오름차순으로 가장 오래된 300건만 매번 조회하고 오프셋도 없었으며, "썸네일 없음" 조건도 SQL이 아니라 PHP에서만 걸렀던 것 — 그 결과 전체 1800건 중 처음 300건만 영원히 반복해서 들여다보는 구조였고, 301번째 이후 파일은 이 버튼으로 애초에 도달할 방법이 없었음. getBsplus_external_storageMigratedImageList.xml에 "썸네일 없음"과 "이미지 파일만(mime_type)" 조건을 SQL 단으로 옮겨, 성공적으로 고친 파일이 다음 조회부터 자동으로 빠지고 뒤 순번 파일이 자연스럽게 올라오도록 수정. 덧붙여 "목표 저장소 파일을 수동으로 지워도 매핑 목록이 안 바뀌는" 현상은 버그가 아니라 설계상 의도된 동작(원격 파일 수천 개의 실시간 존재 확인은 너무 느려서 매핑 테이블 기록만 신뢰)임을 확인해 안내.
v1.9.22026-07-24 · 실사용 버그 리포트
버그 수정: 썸네일이 1페이지 이후로 안 보이던 문제
팔공산 님 실사용 리포트: "썸네일들이 첫페이지는 보이는데 2페이지부터 보이질 않네요". 원인은 라이믹스가 thumbnail_filename이 비어있는 이미지는 게시판 목록에 처음 표시할 때 원본을 읽어 그 자리에서 썸네일을 생성하는데, 이 모듈이 원본을 원격 이동 직후 로컬에서 지워버려서 아직 한 번도 화면에 안 뜬 이미지는 그 생성이 조용히 실패하던 것 — 코드 주석엔 "썸네일은 항상 로컬에 유지"라고 적혀 있었지만 실제로 그걸 보장하는 코드가 없었음(구버전 애드온의 방식만 주석으로 옮기고 실제 로직은 이식 안 함). 원본 이동 직전 로컬 미리보기를 만들어두는 ensureLocalThumbnail()을 구버전 애드온에서 이식해 3곳(업로드 트리거·레거시 마이그레이션·데이터 통합)에 적용. 이미 이동되어 이미 망가진 이미지를 위한 "누락된 썸네일 생성" 백필 도구도 추가. 최종 검토에서 이 수정이 처음엔 core files 테이블을 raw DB 쿼리로 직접 갱신하고 있었는데(다른 모듈의 실제 이중 접두어 버그 사례와 유사한 패턴으로 지적받음 — 조사 결과 이 코드 자체는 안전했지만) 일관성을 위해 정식 쿼리 XML 방식으로 교체.
v1.9.12026-07-24 · 최종 검토 중 발견
보안 수정: 번들 SFTP 라이브러리(vendor/phpseclib3) 직접 접근 노출
더 깊은 교차검증 도구(module.xml↔실제 코드, executeQuery↔쿼리 XML 실존 등)로 최종 재검토하던 중 발견. v1.8.0에서 SFTP 지원을 위해 번들한 phpseclib3 파일 339개는 전부 클래스 정의 파일이라 라이믹스의 __XE__ 가드가 없었는데, 확인해보니 라이믹스 코어 루트 .htaccessmodules/ 안의 일반 .php 파일 직접 실행까지는 막아주지 않는 구조라 그 경로를 직접 요청하면 부트스트랩 없이 실행될 수 있었음. 339개 파일 각각에 가드를 넣는 대신(업스트림 라이브러리라 건드리면 이후 버전업 시 diff가 꼬임) vendor/.htaccess로 그 디렉토리 전체의 외부 HTTP 접근을 차단(PHP 내부 require/오토로더 로딩에는 영향 없음). 이 항목 외 재검토에서 추가로 발견된 실제 결함은 없음(나머지는 관리자 액션 파일 위치 관련 휴리스틱 오탐 12건과 vendor 라이브러리 내 기존 오탐들).
v1.9.02026-07-24 · 실사용 피드백 반영
"데이터 이전 및 통합" UX 개선 4건
v1.8.0을 실제로 써보신 팔공산 님이 SFTP/SSH 정상 동작을 확인해주시며 남긴 피드백. "실행할 때마다 선택값이 초기화돼서 여러 번 누르기 불편하다" → 목표 저장소/경로/게시판 선택을 화면에 그대로 유지하도록 수정. "게시물 많은 게시판/전체 게시판은 30건씩이면 하루 종일 클릭해야 한다" → 레거시 파일(매핑 없는 파일) 처리 한도를 이미 매핑된 파일(30건, 다운로드+업로드 왕복이라 무거움)과 분리해 150건으로 확대(레거시는 로컬 원본을 한 번만 올리면 되니 훨씬 가벼움). "대상 게시판을 여러 개 선택하고 싶다" → 멀티 셀렉트로 전환. "지정된 저장소를 수정할 수 있는 버튼이 있으면 좋겠다" → [저장소 지정] 탭에 "변경" 버튼 추가. "files/attach 폴더를 통째로 복사"하는 요청은 매핑 기록 없이 옮기면 이후 서빙이 깨질 위험이 있어 채택하지 않고, 위 배치 한도 확대로 같은 문제를 더 안전하게 해결.
v1.8.02026-07-24 · 커뮤니티 피드백 반영
SFTP/SSH 저장소 지원 + "데이터 이전 및 통합"의 레거시 파일 처리 버그 수정
rhymix.org 배포 게시글 댓글(팔공산 님)에서 받은 두 가지 요청을 반영. ① 저장소 종류에 SFTP/SSH 추가 — 구버전 애드온(lua_external_file)에서 이미 검증된 phpseclib3를 이 모듈 자체 vendor/에 번들해 서버에 ssh2 확장 모듈이 없어도 동작하도록 구현, 계정 1개만 지원(SSH·SFTP는 보통 같은 계정을 쓰므로). 우선순위 슬롯도 7단계 → 8단계로 확장. ② "이 게시판 전체를 저 저장소로 모아달라"고 실행해도 실제로는 안 됐던 문제 — getConsolidationCandidates이미 매핑된 파일만 대상으로 삼고, 모듈 설치 이전부터 있던 매핑 없는 레거시 파일은 건드리지 못하던 구조적 갭이었음. 이미 매핑된 파일 처리 후 남는 처리 개수 한도 안에서 레거시 파일도 함께 이동하도록 수정.
v1.7.52026-07-23 · 외부 검토 반영
LocalDriver 경로 검증의 고전적인 prefix 매치 우회 패턴 보강
외부 검토에서 지적받은 사항. streamFile()/downloadToLocal()strpos($real, $realStorage) !== 0로만 경로 포함 여부를 확인하고 있어, $realStorage/data/storage일 때 /data/storage_evil/x 같은 형제 디렉터리도 prefix가 겹쳐 통과할 수 있는 구조였음. 현재는 $remotePath가 사용자 직접 입력이 아니라 매핑 테이블 값이라 실사용 위험은 낮지만, 향후 재사용 대비 사전 방어로 구분자 포함 비교 방식으로 교체. CLI로 정상 접근/traversal 차단 양쪽 다 직접 검증.
v1.7.42026-07-23 · 전체 파일 재검토
트리거 예외 처리 강화 + 경로 처리 정확도 개선
PHP 파일 12개·쿼리 XML 16개 전부를 다시 읽으며 진행한 재검토. 4개 트리거가 catch(\Exception)로만 감싸여 있어 PHP의 Error/TypeError 계열은 놓치고 있던 것을 catch(\Throwable)로 방어 범위 확장. 점검 중 v1.7.2에서 고친 시나리오가 getMapByOriginalPath 쿼리의 status='migrated' 필터 덕에 애초에 그 경로로는 도달하지 않는다는 것도 재확인(그래도 "트리거가 끝까지 못 돌아 상태 갱신이 누락된" 케이스에는 여전히 유효한 방어). buildRemotePath()ltrim() 마스크 사용 방식도 더 정확한 방식으로 교체(현재 데이터로는 문제 된 적 없는 사전 예방적 수정).
v1.7.32026-07-23 · 재검토 중 발견
데이터 이전 및 통합이 "여유 공간 확보량" 예약분을 무시하던 문제
새 파일 자동 저장은 여유 공간 확보량(GB) 예약분을 항상 지키는데, "데이터 이전 및 통합"으로 로컬 목표를 고른 경우엔 이 확인 없이 그냥 밀어넣고 있었음 — 반복 실행하면 백업용으로 남겨두려던 공간까지 채울 수 있었음. 파일을 올리기 직전에 여유 공간을 확인해서, 예약분 밑으로 내려가면 그 시점에서 중단하고 안내하도록 수정.
v1.7.22026-07-23 · 보안 수정
최종 점검 중 발견 — 삭제된 게시글의 원격 삭제 실패 파일이 계속 서빙되던 문제
게시글/댓글을 삭제하면 코어가 files 테이블 행을 즉시 지우지만, 원격(S3/FTP) 삭제가 실패해 delete_failed로 매핑이 남아있는 파일은 권한 체크 로직이 "레코드 없음 = 확인 불필요 = 허용"으로 잘못 판단해 삭제된 게시글의 첨부파일이 URL만 알면 계속 열람 가능했음. files 행이 없으면 항상 차단하도록 수정. 같은 점검에서 배지/안내문 카운트가 1000건을 넘으면 실제보다 적게 표시되던 부정확도 함께 수정.
v1.7.12026-07-23 · 개선
미이동 파일 개수 표시
"데이터 이전 및 통합" 현황 표에 "기본 홈페이지 디렉토리(미이동)" 열 추가 — 지금 화면 아래 스크린샷 참고.
v1.7.02026-07-23 · UX 개선
탭 배지 + 현재 탭 표시 + 우선순위 7단계
처리 필요 항목 수를 탭에 배지로 표시, 현재 탭 강조, 저장소 종류가 7개로 늘어난 만큼 우선순위도 7단계까지 지정 가능하도록 확장.
v1.6.02026-07-23 · 신규 기능
S3/FTP 계정 3개까지 등록
"다른 이용자는 Cloudflare R2 + Google Cloud를 동시에 쓰고 싶을 수도 있겠다"는 논의 끝에, S3/FTP를 각각 3개 계정까지 등록 가능하도록 확장. 1번 계정은 기존 필드 그대로 사용해 하위호환 100%.
v1.5.02026-07-23 · 신규 기능
데이터 이전 및 통합
여기저기 흩어진 파일을 목표 저장소 하나로 모으는 4번째 탭. 다운로드→업로드 왕복 작업이라 트래픽 경고 문구 포함, 한 번에 최대 30건 처리.
v1.4.02026-07-23 · 신규 기능
게시판별 저장소 지정
특정 게시판만 사이트 전체 설정과 다른 저장소를 쓰도록 지정하는 3번째 관리 탭 "저장소 지정" 추가.
v1.3.02026-07-23 · 신규 기능
FTP 공개 URL 옵션 + 보안 경고
FTP도 S3처럼 "PHP 스트리밍" / "공개 URL" 선택 가능. 단, 공개 URL은 권한 체크를 완전히 우회하므로 관리자 화면에 경고 문구 명시.
v1.2.12026-07-23 · 실전 버그 수정
Cloudflare R2 실연동 테스트로 발견된 버그 3건
설정 저장이 조용히 막히던 문제(댓글로 재현 확인), 게시글 본문 이미지가 S3 이동 후 깨지던 문제, "공개 URL" 옵션이 실제론 작동 안 하던 문제 — 전부 실제 계정으로 테스트하며 발견하고 수정.
v1.2.02026-07-22 · 신규 기능
로컬 + S3 + FTP 동시 사용 (우선순위 체인)
단일 선택 라디오 → 1~3순위 드롭다운으로 전면 개편. "1순위=로컬, 2순위=S3, 3순위=FTP"처럼 자유롭게 조합 가능해짐. 로컬은 여유공간 사전 체크, S3/FTP는 업로드 시도 후 실패하면 자동으로 다음 순위로.
v1.1.02026-07-22 · 신규 기능
로컬 다중 경로 + 용량 자동 전환
로컬/마운트 경로를 여러 개 등록, 여유 공간이 부족하면 자동으로 다음 경로 사용. 용량 게이지, "여유 공간 확보량(GB)" 설정, # 접두어로 즉시 일시중지 기능 추가.
v1.0.12026-07-22 · 버그 수정
"활성 설정 기준" → "파일별 실제 위치 기준"으로 전환
저장소 종류를 바꾸면 이미 이동해둔 파일들의 삭제/다운로드/이미지 표시가 깨지던 근본 문제 수정. 이후 모든 기능이 이 원칙(파일마다 실제 저장 위치를 기록하고 그걸 기준으로 동작) 위에서 만들어짐.
v1.0.02026-07-22 · 첫 출시
모듈로 전면 재설계
저장소 4종(사용 안 함/로컬·마운트/S3/FTP), 업로드 직후 자동 이동, 다운로드·정적 서빙 양쪽에 권한 체크 적용. 이 시점엔 저장소를 하나만 고를 수 있었음.

현재 화면 (v1.9.1 촬영)

저장소 설정 화면
① 저장소 설정 — 우선순위 8단계(SFTP/SSH 포함), 로컬 저장소 현황(용량 게이지, 사용률 31.9%), S3/FTP 계정 3개씩 접이식 등록 + SFTP/SSH 섹션. 지금 실제로 로컬(1순위)·S3 계정1(2순위)·FTP 서버1(3순위)이 살아있는 상태.
저장소 지정 화면
② 저장소 지정 — 게시판별로 다른 저장소를 지정하는 화면. 아직 지정된 게시판은 없는 상태(전부 사이트 전체 설정을 따름).
데이터 이전 및 통합 화면
③ 데이터 이전 및 통합 — 저장소별 파일 개수 요약(로컬 3077건, S3 계정1 4건, FTP 서버1 2건, SFTP/SSH 0건, 미이동 0건), 대상 게시판 멀티 셀렉트("전체 게시판" 선택됨), 통합 실행 폼. 접이식 도움말도 함께 제공.

전체 패치리스트

v2.2.02026-07-28
추가: "저장 용량 자랑" 위젯 (bsplus_storage_widget)
  • 배경: 이 모듈을 쓰는 사이트 운영자가 방문자에게 "이만큼 서버 용량을 아꼈다"를 보여줄 수 있는 위젯을 요청.
  • 추가: S3/FTP(각 최대 3개 계정) + SFTP 계정별로 "용량 한도(GB)" 입력 필드를 설정 화면에 추가. API로는 버킷/서버의 총 용량을 알 수 없어 관리자가 직접 입력하며, 비워두면 그 저장소는 잔여 %없이 사용량만 표시됨. 로컬/마운트 경로는 기존처럼 실제 디스크 total/free를 그대로 사용.
  • 추가: 저장소 종류별 실사용량(이전된 파일 크기 합계, files 테이블 join) 집계 모델 함수 getStorageUsageStats() 추가.
  • 추가: 신규 위젯 bsplus_storage_widget — 스킨 2종(원형 도넛 게이지 / 가로 막대형)으로 사이트에 실제 설정된 저장소만 자동 나열. 모듈이 설치되지 않은 사이트에 위젯만 공유되는 경우를 대비해 is_dir() 물리 확인 후에만 모델을 호출하도록 방어.
  • 검증: 신규 쿼리 XML validate_query_xml 통과, 신규 PHP 파일 check_php_syntax 통과. review_module이 위젯 디렉토리에 "conf/module.xml 누락"을 오탐으로 보고했으나, 위젯은 모듈과 달리 conf/info.xml만 있으면 되는 정상 구조라 무시.
v2.1.02026-07-27
추가: "원 서버로 복원" 통합 옵션 + 처리 건수 관리자 설정 (팔공산 님 제공 패치)
  • 배경: 팔공산 님이 v2.0.0 게시판 선택 UI 패치에 이어 "데이터 이전 및 통합" 화면 자체를 수정해서 공유해주심. 본인은 이 기능으로 약 3,600건(1GB)을 SFTP로 옮겨 쓰고 계심.
  • 추가: 목표 저장소 선택지에 "↩ 원 서버(호스팅) 기본 경로로 복원 (이동 해제)" 추가 — 이미 외부로 이동된 파일을 원래 있던 자리(원 서버의 기본 업로드 경로)로 도로 내려받아 놓고 매핑을 완전히 삭제해, 이 모듈이 관여하기 이전과 동일한 순수 로컬 상태로 되돌림. 옮긴 곳이 서비스 종료될 경우 등을 대비한 안전판.
  • 안전장치: 매핑을 지우기 전에 파일이 새 위치(원 서버)에 실제로 자리잡은 걸 먼저 확인(rename() 실패 시 copy()+unlink() 폴백, 기존 LocalDriver::upload()와 동일 패턴)한 뒤에만 원격 원본을 삭제하도록 순서를 지켜 데이터 유실을 방지. 레거시 파일(원래부터 원 서버에 있던 파일)은 이미 목표 위치에 있으므로 자연히 처리 대상에서 제외됨.
  • 추가: "데이터 이전 및 통합" 실행 시 한 번에 처리할 건수(매핑된 파일/레거시 파일)를 화면에서 직접 입력해 조절 가능하도록 변경 — 기존엔 30건/150건으로 고정이었음. 확인 대화상자 문구도 실제 입력값을 그대로 반영하도록 JS 수정.
  • 검증: 문법 검사·PHP 7.4/8 호환성 검사 통과. 기존에 있던 in_array() 관련 경고 5건은 전부 이 패치 이전부터 있던, 값이 배열 리터럴로 미리 초기화된 known false-positive라 이 패치와 무관함을 확인.
v2.0.02026-07-26
개선: "데이터 이전 및 통합"의 대상 게시판 선택 UI (팔공산 님 제공 패치)
  • 배경: 팔공산 님이 v1.9.9 썸네일 문제 해결에 감사하며, 대상 게시판 선택 UI를 직접 고친 consolidate.html을 공유해주심. 기존 네이티브 <select multiple>(Ctrl/Cmd+클릭으로 다중 선택)이 특히 모바일/터치 환경에서 다중 선택이 사실상 불가능해 불편했던 부분을 해결한 패치.
  • 개선: 대상 게시판 선택을 <select multiple>에서 체크박스 그리드 + "전체 게시판" 마스터 체크박스(클릭 시 전체 선택/해제 토글, JS) 방식으로 교체. 폼 필드명(consolidate_module_srl[])은 그대로라 백엔드(procBsplus_external_storageAdminConsolidate) 수정 없이 적용됨.
v1.9.92026-07-26
버그 수정: "기존 썸네일 재생성" 버튼이 항상 같은 30건만 반복 처리하던 문제 (팔공산 님 실사용 리포트)
  • 배경: 팔공산 님이 v1.9.7의 재생성 버튼을 여러 번 눌러도 특정 글(대구앞산 크리스마스축제)의 썸네일이 끝내 고쳐지지 않아, 직접 UPDATE xe_files SET thumbnail_filename = ''로 라이믹스 코어 컬럼을 지워서 우회함. 그 방식 자체는 실제로 원격 이전된(로컬 원본이 이미 없는) 파일에는 위험하지만(코어가 대체 소스를 못 찾아 썸네일이 아예 안 뜨게 될 수 있음), 이 리포트를 계기로 재생성 버튼의 진짜 결함을 발견함.
  • 원인: getBsplus_external_storageThumbnailedImageList.xml(재생성 후보 조회)은 "이전됨 + 썸네일 있음"만 조건으로 걸었는데, 재생성에 성공해도 썸네일은 여전히 "있는" 상태이므로 이 조건에서 절대 빠지지 않음. 그 결과 map_srl 오름차순으로 매번 똑같은 맨 앞 30건만 반복 재생성하고, 그보다 뒤에 있는(아직 안 고친) 파일은 버튼을 아무리 눌러도 영원히 처리되지 않는 구조였음.
  • bsplus_external_storage_map 테이블에 thumb_fixed 컬럼 추가(기존 설치는 moduleUpdate()에서 자동 추가). 재생성 성공 시 해당 매핑을 thumb_fixed = 'Y'로 표시(updateBsplus_external_storageMapThumbFixed.xml 신규) + 후보 조회 쿼리에 thumb_fixed != 'Y' 조건 추가. 이제 한 번 고친 파일은 후보에서 자연히 빠지고, 버튼을 반복해서 누르면 뒤에 있는 파일들로 순서대로 넘어감.
v1.9.82026-07-26
추가: 썸네일 원본 비율 유지 옵션 (팔공산 님 실사용 리포트)
  • 배경: v1.9.7까지의 크롭 여백 수정/재생성 도구로도 팔공산 님이 여전히 "이전 후 썸네일이 예전처럼 안 나온다"고 리포트. 코어 구조상 화면에 보이는 썸네일은 항상 thumbnail_filename(이 모듈이 만든 로컬 미리보기)을 게시판 설정의 크롭 타입(crop/fill 등)으로 다시 리사이즈한 결과라, "원본 파일 그 자체"가 그대로 표시되는 경로가 코어에 없음을 확인. 대안으로 "잘라내지 않고 비율 그대로(레터박스)" 표시하는 옵션을 추가.
  • 추가: 관리자 설정 화면에 "이전된 파일은 썸네일을 자르지 않고 원본 비율 그대로 표시" 체크박스 추가(기본 꺼짐 — 켜기 전까지는 기존 동작 그대로).
  • 신규 트리거 document.getThumbnail/comment.getThumbnail(둘 다 before) → triggerOverrideThumbnailType(): 옵션이 켜져 있고 대상 글/댓글에 이전(migrated)된 첨부파일이 있으면 크롭 타입을 'ratio'로 강제. FileHandler::createImageFile()ratio 모드는 잘라내지 않고 긴 변 기준으로 축소만 하므로 전체 이미지가 항상 보임. 이전되지 않은(로컬에만 있는) 파일에는 관여하지 않음.
v1.9.72026-07-25
추가: 기존(여백 낀) 썸네일 재생성 도구 (팔공산 님 실사용 리포트)
  • 배경: v1.9.5로 앞으로 새로 만드는 로컬 미리보기의 크롭 여백 문제는 고쳤지만, 이미 만들어진 미리보기는 자동으로 재생성되지 않음 — ensureLocalThumbnail()thumbnail_filename이 이미 있으면 그냥 넘어가는 구조라서다. 팔공산 님이 코어의 표시 캐시(files/thumbnails)만 지우고 재시도했지만 여전했던 건, 이 모듈이 만들어둔 원본 미리보기 자체가 옛날 방식 그대로였기 때문으로 확인됨.
  • 신규 관리자 액션 procBsplus_external_storageAdminRegenerateThumbnails + 쿼리 getBsplus_external_storageThumbnailedImageList.xml 추가. [매핑 목록 / 레거시 정리] 탭에 "기존 썸네일 재생성 (여백 문제 수정)" 버튼 신규 추가.
  • 동작: 기존 로컬 미리보기 파일 삭제 + DB의 thumbnail_filename 비움 → 원격 원본을 다시 내려받아 v1.9.5의 새 로직(원본 비율 유지)으로 재생성. 한 번에 최대 30건(원격 다운로드 재수반이라 무거움), 실패한 항목은 기존 "누락된 썸네일 생성" 버튼으로도 재시도됨.
v1.9.62026-07-25
UX 개선 2건: 게시판별 저장소 현황 표시 + 대상 게시판 목록 확대 (팔공산 님 실사용 피드백)
  • 추가: "이 게시판은 어느 저장소로 옮겨졌는지 구별되면 좋겠다"는 요청 반영 — "데이터 이전 및 통합" 화면에 새 표를 추가해, 이 모듈이 관리 중인(레거시 포함) 파일이 하나라도 있는 게시판마다 미이동/로컬/S3 계정1~3/FTP 서버1~3/SFTP 각 칸에 몇 건씩 있는지 표시. 한 게시판이 여러 칸에 걸쳐 있으면 아직 한 곳으로 안 모인 상태라는 걸 바로 알 수 있음. 신규 쿼리 2개(GROUP BY 집계) + 모델 함수 getBoardStorageBreakdown() 추가.
  • 개선: "대상 게시판 목록이 전체 게시판 다 보이면 편하겠다"는 요청 반영 — 목록 자체는 이미 전체 게시판(최대 500개)을 포함하고 있었지만 선택창 높이가 고정 6줄이라 스크롤해야 나머지가 보였던 것을, 게시판 개수에 맞춰 자동으로 늘어나도록(최소 6줄~최대 20줄) 변경.
v1.9.52026-07-25
버그 수정: 크롭 지정 썸네일이 원본 비율대로 안 잘리던 문제 (팔공산 님 실사용 리포트)
  • 증상: 게시판 목록 등에서 썸네일 표시 방식을 "크롭"으로 지정했는데도, 이 모듈이 만든 로컬 미리보기 이미지에서는 흰 여백이 낀 채로 나오고 원본이 박스에 꽉 차게 잘리지 않음.
  • 원인: ensureLocalThumbnail()이 만드는 로컬 미리보기는 최종 화면에 뜨는 썸네일이 아니라, 라이믹스 코어(document.item.phpgetThumbnail())가 게시판 목록 등에서 실제 썸네일을 만들 때 "원본 대신 읽는 소스"로 쓰는 파일이다. 코어는 files.thumbnail_filename이 있으면 그걸 원본처럼 취급해 그때그때 요청된 크기/모드(크롭 포함)로 다시 리사이즈함. 그런데 이 미리보기를 고정 500×500 정사각형에 'ratio'(레터박스) 모드로 만들고 있었음 — 원본이 정사각형이 아니면 흰 여백이 미리보기 자체에 이미 박제되고, 코어가 나중에 이걸 "크롭" 모드로 다시 잘라도 그 흰 여백까지 함께 잘려 들어가 버림.
  • ensureLocalThumbnail(): 고정 500×500 대신, 원본 비율을 그대로 유지한 채 긴 변만 최대 1600px로 축소(원본이 더 작으면 확대하지 않음)해서 저장하도록 변경. 목표 박스 크기 자체가 원본과 같은 비율이라 어떤 모드(크롭/레터박스/채우기)로 다시 리사이즈되든 왜곡·여백·잘못된 크롭 없이 정상 동작. 신규 업로드/레거시 마이그레이션/백필 버튼 전부 이 함수 하나를 공유하므로 별도 수정 없이 다 함께 고쳐짐.
v1.9.42026-07-25
버그 수정: "데이터 통합"에서 일부 게시판만 썸네일이 안 생기던 문제 (팔공산 님 실사용 리포트)
  • 증상: 게시판들을 통합 이전한 뒤 어떤 게시판은 썸네일이 정상 생성되는데 어떤 게시판은 안 됨. 안 되는 게시판도 글을 하나하나 수정저장하면 그때는 썸네일이 생김.
  • 원인: "데이터 이전 및 통합"에는 두 경로가 있음 — ① 매핑조차 없던 레거시 파일 이동 ② 이미 다른 저장소로 매핑돼 있던 파일을 새 목표 저장소로 재이동. v1.9.2에서 ensureLocalThumbnail()을 업로드 트리거·레거시 마이그레이션·①번 경로 3곳에 넣었는데 ②번 경로에는 빠져 있었음 — 이 경로가 다루는 map 레코드에는 애초에 files 테이블 정보(thumbnail_filename 등)가 없어서 검토 때도 놓친 부분.
  • 결과적으로 마침 그 시점에 "이미 매핑된 상태"였던 게시판의 파일들은 재이동해도 로컬 미리보기가 채워지지 않고 계속 깨진 상태로 남고, 매핑이 아예 없던(순수 레거시) 게시판은 ①번 경로를 타서 정상적으로 썸네일이 생김 — 이게 "게시판마다 다른" 것처럼 보인 이유.
  • procBsplus_external_storageAdminConsolidate의 ②번 경로에서, 원격 원본을 임시 파일로 내려받은 직후 FileModel::getFile()로 해당 파일의 files 레코드를 조회해 ensureLocalThumbnail()을 호출하도록 추가. 임시 파일이 아직 로컬에 있는 시점(업로드 전)에 처리하므로 별도 다운로드가 필요 없음.
v1.9.32026-07-25
버그 수정: "누락된 썸네일 생성" 버튼이 일정 개수 이후로 멈추던 문제 (팔공산 님 실사용 리포트)
  • 원인: procBsplus_external_storageAdminFixThumbnails가 후보를 가져올 때 쓰던 쿼리가 항상 map_srl 오름차순으로 가장 오래된 상위 300건만 조회하고, 오프셋도 없고 "이미 썸네일이 있는지" 조건도 SQL이 아니라 PHP에서만 걸렀음. 그 결과 전체 이전 파일(팔공산 님 기준 약 1800개) 중 처음 300개만 영원히 반복해서 들여다보는 구조였고, 그 안에서 고칠 수 있는 건 몇 번 클릭하면 끝나거나 영구 실패로 남고 그 뒤로는 매번 같은 300개를 훑으며 스킵/실패만 반복(그래서 지연 없이 효과가 없었던 것). 301번째 이후 파일은 이 버튼으로 애초에 도달 불가능했음.
  • queries/getBsplus_external_storageMigratedImageList.xml: "썸네일 없음"(thumbnail_filename IS NULL OR '')과 "이미지 파일만"(mime_type LIKE 'image/%') 조건을 SQL 단으로 이동. 성공적으로 고친 파일은 다음 조회부터 자동으로 결과에서 빠지므로, 매번 새로운(뒤쪽 순번의) 후보가 자연스럽게 올라와 전체 목록을 끝까지 훑을 수 있게 됨.
  • bsplus_external_storage.admin.controller.php: SQL이 이미 이미지+미보유만 걸러주므로 PHP 쪽 중복 필터(확장자 정규식, 빈값 체크)를 제거해 정리.
  • 참고 안내: 목표 저장소의 파일을 모듈 밖에서(FTP로 직접 삭제 등) 건드려도 매핑 목록/개수가 안 바뀌는 현상은 버그가 아니라 설계상 의도된 동작 — 원격 파일 수천 개의 실제 존재 여부를 화면 열 때마다 매번 확인하면 너무 느려져서(특히 SFTP/FTP), 모듈은 자체 매핑 테이블 기록만 신뢰함. 어긋난 경우 매핑 테이블을 직접 정리해야 함.
v1.9.22026-07-24
버그 수정: 썸네일이 1페이지 이후로 안 보이던 문제 (팔공산 님 실사용 리포트)
  • 원인: 라이믹스는 업로드 시 클라이언트가 미리보기를 안 보내면 thumbnail_filename을 비워두고, 게시판 목록 등에 처음 표시할 때 원본을 읽어 그 자리에서 썸네일을 생성함. 이 모듈이 원본을 원격 이동 직후 로컬에서 지워버려서, 아직 한 번도 화면에 표시된 적 없는 이미지는 그 생성이 조용히 실패했음. 코드 주석엔 "썸네일은 항상 로컬에 유지한다"고 적혀 있었지만 실제로 그걸 보장하는 코드가 없었던 게 근본 원인 — 구버전 애드온(lua_external_file)의 동작 방식만 주석으로 옮기고, 그걸 실제로 만드는 코드(ensureLocalThumbnail)는 이식하지 않았음.
  • bsplus_external_storage.class.phpensureLocalThumbnail() 추가 — thumbnail_filename이 비어있는 이미지는 원본을 지우기 직전에 FileHandler::createImageFile()로 500x500 이하 미리보기를 별도 생성해 로컬에 남기고(이 미리보기는 절대 이동시키지 않음) DB를 갱신. 구버전 애드온의 LefHooks::ensureLocalThumbnail()과 동일한 접근. 원본을 옮기고 지우는 3곳(업로드 트리거, 레거시 마이그레이션, 데이터 통합의 레거시 파일 처리) 전부에 적용.
  • 추가: 이미 이동되어 썸네일이 누락된 상태로 남은 이미지를 위한 백필 도구 — [매핑 목록 / 레거시 정리] 탭에 "누락된 썸네일 생성" 버튼 추가(procBsplus_external_storageAdminFixThumbnails). 원격 원본을 임시로 내려받아 미리보기만 만들고 임시 파일은 즉시 삭제, 한 번에 최대 50건.
  • 최종 검토 중 개선: ensureLocalThumbnail()이 처음엔 core files 테이블을 DB::getInstance()->query()로 직접 갱신했는데, review_module이 "다른 모듈(slimmer)에서 실제 이중 접두어 버그가 났던 패턴"이라고 경고. 직접 조사한 결과 slimmer 버그는 SQL에 접두어를 수동으로 붙인 뒤 또 자동 접두어가 붙어 xe_xe_...가 된 경우였고, 이 코드는 수동 접두어를 안 붙이므로 실제로는 안전했음 — 그래도 일관성을 위해 신규 쿼리 updateBsplus_external_storageFileThumbnail.xml로 교체(동작 동일).
v1.9.12026-07-24
최종 검토 중 발견: 번들 SFTP 라이브러리 직접 접근 노출
  • 보안 수정: vendor/phpseclib3 파일 339개가 __XE__ 가드 없이 직접 웹 접근 가능하던 문제. 클래스 정의 파일이라 가드가 없는 게 정상이지만, 라이믹스 코어 루트 .htaccessmodules/ 안의 일반 .php 실행 자체는 막지 않아 그 경로를 직접 요청하면 부트스트랩 없이 실행될 수 있었음.
  • 339개 파일에 개별 가드를 추가하는 대신(업스트림 라이브러리를 건드리면 이후 phpseclib 버전업 시 diff가 꼬임) vendor/.htaccess로 디렉토리 전체의 외부 HTTP 접근을 차단(Require all denied / 구버전 Apache용 Deny from all 병기). PHP 내부의 require/오토로더 로딩에는 영향 없음.
  • review_module(module.xml↔실제 코드 교차검증 포함 심층 검토)로 재검증 — 이 항목 외 추가 발견된 실제 결함 없음.
v1.9.02026-07-24
"데이터 이전 및 통합" UX 개선 4건 (팔공산 님 실사용 피드백 반영)
  • 개선: 통합 실행 후 방금 고른 목표 저장소/로컬 경로/대상 게시판이 화면에 그대로 유지되도록 수정. 이전에는 실행할 때마다 초기화돼서, 여러 번 나눠 눌러야 하는 상황마다 매번 다시 선택해야 했음. procBsplus_external_storageAdminConsolidate가 선택값을 리다이렉트 URL에 실어 넘기고 dispBsplus_external_storageAdminConsolidate가 그대로 읽어 화면에 반영.
  • 개선: 레거시 파일(매핑 없는 파일) 처리 한도를 이미 매핑된 파일(30건, 다운로드+업로드 왕복이라 무거움)과 분리해 150건으로 확대. 레거시 파일은 로컬 원본을 한 번만 올리면 끝나 훨씬 가벼움 — 게시물 많은 사이트에서 클릭 횟수를 크게 줄임. ("files/attach 폴더 통째 복사" 요청은 매핑 기록 없이 옮기면 이후 다운로드/서빙이 깨질 위험이 있어 채택하지 않고, 이 방식으로 같은 문제를 더 안전하게 해결.)
  • 추가: "데이터 이전 및 통합"의 대상 게시판을 여러 개 동시 선택 가능(멀티 셀렉트)하도록 변경. 쿼리 2개(getBsplus_external_storageConsolidationCandidates.xml, getBsplus_external_storageLegacyFileList.xml)에 module_srl in-조건 추가.
  • 추가: [저장소 지정] 탭의 이미 지정된 게시판 행에 "변경" 버튼 추가 — 기존엔 삭제 후 재추가해야만 저장소를 바꿀 수 있었음. procBsplus_external_storageAdminAddBoardOverride가 이미 upsert(삭제 후 삽입) 구조였어서 백엔드 변경 없이 UI만 추가.
v1.8.02026-07-24
신규 기능 + 버그 수정: SFTP/SSH 지원, "데이터 이전 및 통합" 레거시 파일 처리 (커뮤니티 피드백 반영)
  • 추가: 저장소 종류에 SFTP/SSH 추가. rhymix.org 배포 게시글 댓글에서 요청받은 기능 — 보통 SSH와 SFTP는 같은 계정/포트(22)를 쓰므로 계정 1개로 통합해서 지원(S3/FTP처럼 3개 슬롯은 두지 않음).
  • 구현은 lib/SftpDriver.php 신규 작성. 순수 PHP 구현체 phpseclib3를 이 모듈 자체 vendor/에 번들(서버에 ssh2 확장 모듈이 없어도 동작) — 구버전 애드온(lua_external_file)에서 이미 검증된 동일 라이브러리를 재사용.
  • 저장소 종류가 8개(로컬+S3 3개+FTP 3개+SFTP/SSH 1개)로 늘어난 만큼 우선순위 슬롯도 7단계 → 8단계로 확장. 게시판별 저장소 지정, 매핑 목록 필터, 데이터 통합 목표 선택에도 전부 SFTP 옵션 반영.
  • 수정: "데이터 이전 및 통합"이 대상 게시판의 레거시 파일을 전혀 이동시키지 못하던 문제. 커뮤니티 피드백으로 재현됨 — "대상 게시판 지정 + 목표 저장소 지정으로 통합을 돌렸는데 전혀 안 됨".
  • 원인: procBsplus_external_storageAdminConsolidatebsplus_external_storage_map에 이미 매핑된(=한 번이라도 이 모듈이 이동시킨) 파일만 대상으로 삼고 있었음. 모듈 설치 이전부터 있던, 매핑 자체가 없는 레거시 파일은 이 화면에서 전혀 처리되지 않고 별도의 "레거시 마이그레이션" 버튼(목표 저장소를 임의로 지정할 수 없는 기능)에서만 다뤄지고 있었음.
  • 이미 매핑된 파일 처리 후 남은 처리 개수 한도 안에서, 지정한 게시판(또는 전체)의 레거시 파일도 같은 목표 저장소로 함께 이동시키도록 수정. getBsplus_external_storageLegacyFileList.xmlmodule_srl 선택 필터 추가.
v1.7.52026-07-23
외부 검토 반영: LocalDriver 경로 검증의 고전적인 prefix 매치 우회 패턴 보강
  • streamFile()/downloadToLocal()realpath() 결과가 저장소 경로 안에 있는지 strpos($real, $realStorage) !== 0로만 확인 — $realStorage/data/storage일 때 /data/storage_evil/x 같은 형제 디렉터리도 문자열 prefix가 겹쳐 통과하는 고전적인 우회 패턴이었음.
  • 현재 코드 흐름상 $remotePath는 사용자가 직접 넘기는 값이 아니라 매핑 테이블에 기록된 값이라 실제 악용 가능성은 낮으나, 향후 이 드라이버가 다른 입력 경로에 재사용될 경우를 대비해 strpos($real, $realStorage . DIRECTORY_SEPARATOR) !== 0 && $real !== $realStorage 방식으로 교체.
  • CLI 스크립트로 직접 검증: 정상 파일 접근은 그대로 동작, 형제 디렉터리를 노리는 traversal 시도는 새 로직에서 정확히 차단됨을 확인.
v1.7.42026-07-23
전체 파일 재검토(쿼리 16개 전부 포함) — 트리거 예외 처리 강화 + 경로 처리 정확도 개선
  • 4개 트리거가 전부 catch(\Exception $e)로만 감싸여 있었음. PHP 7+의 TypeError/Error 계열은 \Exception이 아니라 \Throwable이라 이 catch에 걸리지 않아, 드라이버 쪽 Error 발생 시 트리거가 도중에 끊기고(삭제 트리거는 delete_failed 기록도 못 남긴 채) 요청 전체가 죽을 수 있었음. catch(\Throwable $e)로 전부 교체.
  • 점검 확인: v1.7.2의 delete_failed 시나리오는 getMapByOriginalPath 쿼리가 status='migrated'만 조회하도록 이미 필터링돼 있어 그 경로로는 도달하지 않음을 재확인 — 이번 예외 처리 강화와 결합했을 때 여전히 유효한 방어(트리거가 끝까지 못 돌아 상태 갱신이 누락되는 "유령 매핑" 케이스).
  • buildRemotePath()ltrim($str, './')는 "./" 리터럴이 아니라 문자 마스크로 동작해 과도하게 잘릴 여지가 있었음(현재 실제 경로 형식에서는 문제 된 적 없음) — substr() 기반 리터럴 접두어 제거로 교체.
  • PHP 파일 12개 전체, 쿼리 XML 16개 전체, 스키마 2개, 관리자 템플릿 4개, guest 접근 가능한 공개 스킨, lang 파일, module.xml 권한 테이블(액션 12개) 전부 재확인 — 추가 문제 없음.
v1.7.32026-07-23
수정: 데이터 이전 및 통합이 "여유 공간 확보량" 예약분을 무시하던 문제 (재검토 중 발견)
  • 새 파일이 자동으로 로컬 경로에 저장될 때는 pickLocalStorageRoot()가 "여유 공간 확보량(GB)"을 항상 지켜서 그 밑으로는 저장하지 않음. 그런데 "데이터 이전 및 통합"으로 로컬 경로를 목표로 고르면 이 확인 없이 그냥 밀어넣고 있었음 — 반복 실행 시 백업 등을 위해 남겨두려던 예약 공간까지 채울 수 있었음.
  • 파일 하나를 목표 로컬 경로에 올리기 직전에 여유 공간을 확인하도록 추가. 예약분 밑으로 내려가면 그 시점에서 중단하고 안내 메시지 표시.
v1.7.22026-07-23
보안 수정: 삭제된 게시글의 원격 삭제 실패 파일이 계속 서빙되던 문제 (최종 점검 중 발견)
  • 게시글/댓글이 삭제되면 코어가 files 테이블 행을 즉시 삭제(action="delete" 쿼리 직접 확인)하지만, 원격(S3/FTP) 삭제가 실패해 delete_failed 상태로 매핑이 남아있으면 정적 서빙 폴백의 isServeFilePermitted()가 "파일 레코드를 못 찾음 → 권한 체크 대상 아님 → 허용"으로 잘못 처리하고 있었음. 결과적으로 게시글이 삭제됐어도 원격 삭제만 실패한 첨부파일은 URL을 아는 사람에게 계속 노출되는 상태였음.
  • files 행이 없으면 이제 항상 차단하도록 수정 — 관리자가 매핑 목록에서 "삭제 재시도"로 원격 삭제를 완료하기 전까지는 아무에게도 서빙되지 않음.
  • 같은 점검에서 탭 배지·레거시 안내문의 건수가 최대 1000건까지만 세는 리스트 조회를 재사용하고 있어 1000건을 넘으면 실제보다 적게 표시되던 문제도 함께 발견 → count 전용 쿼리(getLegacyFileCount())로 교체.
v1.7.12026-07-23
개선: "데이터 이전 및 통합" 현황에 미이동 파일 개수 추가
  • 저장소별 파일 개수 요약 표에 "기본 홈페이지 디렉토리(미이동)" 열 추가 — 아직 모듈이 관리하지 않는(설치 이전 파일 + "이동 안 함" 게시판 지정) 파일 개수를 정확히 표시.
  • 배지용 카운트(최대 1000건 제한)와 별도로, count 쿼리 기반의 정확한 전체 건수를 반환하는 getLegacyFileCount() 추가.
v1.7.02026-07-23
관리자 화면 UX 개선: 탭 배지 + 현재 탭 표시 + 우선순위 7단계
  • "매핑 목록 / 레거시 정리" 탭에 처리 필요 항목 수(레거시+실패+삭제실패)를 빨간 배지로 표시.
  • 4개 관리자 탭 전부 현재 보고 있는 탭을 파란색 밑줄로 강조.
  • 저장소 우선순위를 3단계 → 7단계로 확장 (저장소 종류가 최대 7개로 늘어난 것에 맞춤). 기존 3단계 설정은 하위호환 유지.
v1.6.02026-07-23
신규 기능: S3/FTP 계정 3개까지 등록
  • S3 호환 스토리지, FTP 서버를 각각 3개까지 동시 등록 (예: 계정1=Cloudflare R2, 계정2=Google Cloud Storage, 계정3=Backblaze B2).
  • 우선순위/게시판 지정/데이터 통합 화면의 저장소 선택지가 "S3 계정 1/2/3", "FTP 서버 1/2/3"으로 세분화.
  • 1번 계정은 기존 필드 그대로 사용 — 하위호환 100%, 마이그레이션 불필요. 2/3번만 s3_2_*/s3_3_* 등 새 접두어 필드 사용.
  • 설정 화면에 계정별 접이식(<details>) 섹션 제공.
v1.5.02026-07-23
신규 기능: 데이터 이전 및 통합
  • 4번째 탭 "데이터 이전 및 통합" 추가 — 로컬/S3/FTP에 뒤섞인 파일을 목표 저장소 하나로 이전.
  • 동작 방식: 기존 위치에서 다운로드 → 목표 위치에 업로드 → 성공 확인 후에만 기존 위치에서 삭제 → 매핑 갱신.
  • 각 드라이버에 downloadToLocal() 추가 (S3=인증 GET, FTP=ftp_get(), 로컬=파일 복사).
  • 경고 문구: 다운로드+업로드 왕복으로 트래픽/시간 소모됨을 명시(AWS S3·Backblaze B2 과금 가능성, 홈서버 대역폭 점유 가능성). 한 번에 최대 30건.
  • 웹호스팅 사용자도 사용 가능(임시 폴더만 사용) — "로컬(지정 경로)" 목표만 홈서버 전용.
v1.4.02026-07-23
신규 기능: 게시판별 저장소 지정
  • 3번째 탭 "저장소 지정" 추가 — 게시판마다 이동 안 함/로컬/S3/FTP 중 하나를 개별 지정.
  • 지정된 게시판은 사이트 전체 우선순위 체인을 무시하고 지정된 저장소만 사용.
  • 새 테이블 bsplus_external_storage_board 추가 (관리자 화면에서 "설치" 버튼 클릭 필요했던 변경).
  • 레거시 마이그레이션 실행 시에도 게시판별 지정 반영.
  • 게시판 추가는 "아직 지정 안 된 게시판" 드롭다운에서 하나씩 선택하는 방식으로 단순화.
v1.3.02026-07-23
신규 기능: FTP 공개 URL 옵션
  • FTP도 S3처럼 "PHP 스트리밍(기본)" / "공개 URL" 선택 가능 — FTP 루트가 동시에 웹 루트인 저가형 호스팅에서 서버 부담 절감 목적.
  • 보안 경고: 공개 URL은 권한 체크(isDownloadable(), 게시글/댓글 읽기 권한)를 완전히 우회함을 관리자 화면에 명시. 완전 공개 게시판에서만 권장.
v1.2.12026-07-23
중요 수정 (실제 R2 연동 테스트로 발견)
  • 설정 저장이 조용히 막히던 버그 — 우선순위에 넣었지만 세부 정보가 아직 부족한 저장소가 있으면 저장 자체를 막고 있었음. 이제 항상 저장되고 참고 메시지만 표시.
  • S3로 이동된 파일이 게시글 본문에서 깨지던 버그 — 다운로드 버튼 경로에만 있던 signed URL 리다이렉트를 정적 서빙 폴백(triggerCheckServeFile)에도 추가.
  • S3 "공개 URL" 옵션이 실제로는 동작하지 않던 문제 — S3Driver가 관련 설정을 아예 읽지 않고 있었음. 실제로 반영되도록 수정.
v1.2.02026-07-22
신규 기능: 로컬 + S3 + FTP 동시 사용 (우선순위 체인)
  • 단일 선택(라디오) → 1/2/3순위 드롭다운 3개로 전면 개편.
  • 로컬은 여유공간 사전 확인, S3/FTP는 업로드 시도 후 실패 시 자동으로 다음 순위 전환 (attemptChainUpload()).
  • 각 파일은 실제로 성공한 저장소를 그대로 기록 — 활성 설정이 아니라 시도 결과 기준.
  • 연결 테스트 버튼이 로컬/S3/FTP 섹션별로 독립 동작.
  • 예전 단일 storage_type 설정은 하위호환으로 1순위 자동 승격.
v1.1.02026-07-22
신규 기능: 로컬/마운트 다중 경로 (용량 부족 자동 대응)
  • 로컬/마운트 저장소를 여러 개(우선순위 순) 등록 가능. 기존 단일 local_path는 하위호환.
  • disk_free_space()로 여유 공간 확인 → 부족하면 자동으로 다음 순위 경로 사용. 전부 꽉 차면 failed 상태로 사전 감지.
  • 매핑 테이블에 storage_root 컬럼 추가 — 파일별 실제 저장 경로 기록.
  • 저장소 현황(용량 게이지, 70%↑ 주황·90%↑ 빨강) 추가.
  • # 접두어로 경로를 즉시 일시중지하는 기능 추가.
  • "여유 공간 확보량(GB)" 설정 추가(기본 5GB) — 백업 등을 위한 여유 공간 확보 목적.
v1.0.12026-07-22
중요 수정
  • 저장소 종류를 바꾸면 기존 파일이 깨지던 버그 — 트리거들이 "현재 활성 저장소" 기준으로 드라이버를 생성하고 있었음. StorageDriverFactory::create()에 저장소 종류를 명시적으로 넘기고, 각 트리거는 파일별 기록된 storage_type을 사용하도록 수정.
v1.0.02026-07-22
첫 출시 — 애드온을 참고만 하고 모듈로 완전히 새로 설계
  • 핵심 기능: 저장소 4종(사용 안 함/로컬·마운트/S3/FTP), 업로드 직후 자동 이동, 삭제 시 원격 동기화, 썸네일은 항상 로컬 보관.
  • 보안: 정적 서빙 폴백과 다운로드 버튼 양쪽에 isDownloadable() + 게시글/댓글 읽기 권한 체크, 경로 순회 방지.
  • 이번 버전 버그 수정: 404 유발하던 존재하지 않는 함수 호출, printNavigation() 미존재, foreach(range()) 템플릿 컴파일 오류, class="admin" 오용, 다운로드 버튼 원격 파일 미지원, 관리자 탭 미연결, FTP 확장 미설치 가드.
  • 알려진 제한: S3 다운로드 파일명 강제 불가, 다중 저장소 미지원(이후 v1.2.0에서 해소), WebDAV/SFTP 미지원, 한국어 언어팩만 존재.

지원 환경

PHP7.4 이상 (Rhymix 공식 최소 요구사항), 8.2 이상 권장. 상한선 없음.
Rhymix2.0.0 이상 권장. 실제 설치·테스트는 2.1.35에서 진행.
SFTP/SSH서버 ssh2 확장 모듈 불필요 — 순수 PHP 구현체 phpseclib3를 모듈 자체 vendor/에 번들. mbstring/openssl 확장이 있으면 더 빠르게 동작하지만 없어도 pure-PHP 폴백으로 정상 작동.
라이선스GPLv2 (Rhymix 코어 및 다른 BSplus 모듈과 동일)