Organize uploader storage and restore transfer readiness

This commit is contained in:
2026-10-14 19:15:46 +09:00
parent 69e7449ba7
commit c3573b605c
24 changed files with 966 additions and 449 deletions

View File

@@ -213,18 +213,24 @@ https://hop.noname.company
현재 흐름은 **수락 → 서버로 업로드 → NAS에 원본 저장 → 수신자가 다운로드 → 보관 기간 종료 후 정리**입니다. 다운로드 완료 직후 삭제하는 방식이 아닙니다. 임시 저장과 받은 항목도 같은 파일 저장소를 사용하며 DB의 접근 권한으로 사용자별 목록을 구분합니다.
| 데이터 | NAS 저장 위치 | 컨테이너 내부 위치 |
| ------------------------------------- | ----------------------------------------------- | --------------------------------------------------- |
| 보관함 연결 또는 다운로드 미완료 파일 | `hop/uploads/originals/` | `/data/uploads/originals/` |
| 다운로드 완료·보관함 미연결 파일 | `hop/uploads/completed/` | `/data/uploads/completed/` |
| 업로드 중인 파일·조각 | `hop/uploads/temporary/`, `hop/uploads/chunks/` | `/data/uploads/temporary/`, `/data/uploads/chunks/` |
| 계정·파일명·소유자·만료·접근 권한 | `hop_hop_database` Docker 볼륨 | `/data/database/hop.sqlite` |
| 데이터 | NAS 저장 위치 | 컨테이너 내부 위치 |
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------- |
| 로그인 중 업로드한 새 파일 | `hop/uploads/originals/user-계정ID/` | `/data/uploads/originals/user-계정ID/` |
| 비로그인 업로드 파일 | `hop/uploads/originals/guest-기기세션ID/` | `/data/uploads/originals/guest-기기세션ID/` |
| 업로드 중인 파일·조각 | `hop/uploads/temporary/`, `hop/uploads/chunks/` | `/data/uploads/temporary/`, `/data/uploads/chunks/` |
| 계정·파일명·소유자·만료·접근 권한 | `hop_hop_database` Docker 볼륨 | `/data/database/hop.sqlite` |
NAS 파일 관리자에서 **공유 폴더 → docker → projects → apps → hop → uploads**로 들어갑니다. 전체 경로는 `/volume1/docker/projects/apps/hop/uploads`입니다. 파일명은 `무작위 식별자.jpg`처럼 원래 확장자를 붙여 저장하고, 원래 전체 파일명과 사용자 연결 정보는 DB에 둡니다. 확장자가 없거나 안전한 확장자로 판별할 수 없는 파일에는 확장자를 임의로 붙이지 않습니다. 업로드 폴더는 Git과 Docker 이미지에서 제외합니다. NAS 공유 폴더를 컨테이너에 연결하는 방식은 [Docker 공식 설명](https://docs.docker.com/engine/storage/bind-mounts/)과 [UGREEN 공식 안내](https://support.ugnas.com/detail/article/en-US/611)를 참고했습니다.
NAS 파일 관리자에서 **공유 폴더 → docker → projects → apps → hop → uploads → originals**로 들어갑니다. 전체 경로는 `/volume1/docker/projects/apps/hop/uploads/originals`입니다. **업로드한 사람** 기준으로 로그인 계정은 `user-계정ID`, 비로그인 기기는 `guest-기기세션ID` 폴더를 사용합니다. 같은 로그인 계정은 기기가 달라도 같은 폴더를 사용합니다. 받은 사람의 보관함은 원본을 참조하므로 여러 명이 받아도 복제하지 않습니다. 계정 ID와 이메일은 DB의 `users`에 있으며 파일명·접근권도 DB에서 연결합니다. 폴더명이 다운로드 권한을 부여하지는 않습니다.
로그인한 A가 비로그인 B에게 새 파일을 보내도 A의 보관함에 자동 저장하지 않습니다. B에게 다운로드 응답을 끝낸 파일에 유효한 계정 보관함 참조가 없으면 `completed`로 옮기며, 로그인한 수신자의 받은 항목이나 임시 저장에 연결된 파일은 `originals`에 유지합니다. 다른 다운로드·미리보기가 진행 중이면 이동을 기다립니다. 폴더 분류만 변경하며 기존 접근권·재다운로드·용량 계산·기본 7일 만료일은 유지합니다. 계정 참조가 복원되면 다음 정리 시 `originals`로 돌려놓습니다. 서버의 응답 완료는 브라우저가 기기에 저장했다는 보증은 아닙니다.
파일명은 `무작위 식별자.jpg`처럼 안전한 원래 확장자를 붙여 저장하고 다운로드 시 원래 전체 파일명을 제공합니다. 확장자가 없거나 안전한 확장자로 판별할 수 없는 파일에는 임의 확장자를 붙이지 않습니다. 업로드 폴더는 Git과 Docker 이미지에서 제외합니다. NAS 공유 폴더를 컨테이너에 연결하는 방식은 [Docker 공식 설명](https://docs.docker.com/engine/storage/bind-mounts/)과 [UGREEN 공식 안내](https://support.ugnas.com/detail/article/en-US/611)를 참고했습니다.
앱 시작 시 확장자 없는 기존 파일은 DB와 함께 이름을 변경합니다. 이전 버전의 다운로드 완료 여부는 소급 확인할 수 없으므로 기존 파일은 수신자가 다시 다운로드할 때 `completed`로 분류합니다. 이동 중 종료되면 숨김 폴더 `.file-moves`의 복구 기록으로 파일 경로와 DB를 맞춥니다. 새 배포에 저장소 초기화·Compose 수정·폴더 수동 생성은 필요하지 않습니다.
로그인한 A가 비로그인 B에게 새 파일을 보내도 A의 보관함에 자동 저장하지 않습니다. 원본은 업로드 소유자의 폴더에서 기존 접근권과 만료일에 따라 유지합니다. `completed`로 옮기는 방식은 사용하지 않습니다. 이전 방식에서는 계정 보관함에 연결된 파일은 다운로드가 끝나도 `originals`에 두었지만, 현재는 다운로드 완료 여부로 폴더를 나누지 않습니다.
앱 시작 시 기존 평면 `originals` 및 `completed`의 파일을 DB에 기록된 업로드 소유자 폴더로 이동하고 확장자도 적용합니다. 과거 기기 전송의 업로드 소유자가 기기 세션으로만 기록되어 있다면 해당 `guest-기기세션ID` 폴더로 이동하며 로그인 계정을 추정하지 않습니다. 파일 ID·다운로드 주소·만료일·사용량과 기존 자료는 유지합니다. 진행 중인 파일 읽기가 있으면 이동을 미루고 DB 변경 실패 시 원래 경로로 복원합니다. 이동 중 종료되면 숨김 폴더 `.file-moves`의 기록으로 경로와 DB를 맞춥니다. 빈 기존 `completed` 폴더만 제거하며 새 `completed` 폴더는 만들지 않습니다. 저장소 초기화·Compose 수정·폴더 수동 생성은 필요하지 않습니다.
각 사용자 폴더의 `owner-info.json`에서 업로드 요청 시 확인한 국가·지역·도시·접속 IP와 업로드 주체를 확인합니다. 최대 최근 20개·기본 7일의 기록이며 마지막 원본과 대기 업로드가 모두 사라지면 정보 파일도 정리합니다. 좌표·세션 쿠키·CSRF·파일 본문은 저장하지 않습니다. 일반 사용자의 API·보관함·공개 URL로 제공하지 않습니다. 외부 GeoIP 조회 없이 기본 제공 값만 사용하며 미제공 지역과 과거 자료는 확인 불가입니다.
지역은 `CLIENT_IP_SOURCE=cloudflare`일 때 검증된 인접 프록시에서 온 Cloudflare 헤더만 사용합니다. 국가 정보는 IP geolocation, 도시 정보는 **Cloudflare → Rules → Settings/Managed Transforms → Add visitor location headers**를 활성화하면 전달받을 수 있습니다. 현재 대시보드 메뉴가 다르면 [Cloudflare 공식 설정 문서](https://developers.cloudflare.com/rules/transform/managed-transforms/reference/#add-visitor-location-headers)를 참고하세요. CF 좌표·우편번호는 무시합니다. VPN·통신사망 등으로 실제 거주지와 다를 수 있습니다. 악용 대응용 관리자 화면·국가/IP 차단은 별도로 관리하며 실제 규칙은 운영자가 지정합니다.
파일 관리자로 원본을 수동 삭제하면 다음 보관함·용량 조회, 다운로드·재전송 시 확인하거나 약 1분 간격의 정리 작업에서 감지합니다. 같은 원본의 모든 계정·기기 항목은 만료 처리해 목록과 전송 선택에서 제외하고 사용량을 회수합니다. 열린 화면에는 갱신 이벤트를 보내며 이미 열린 다운로드·전송 화면에서는 삭제 안내를 표시합니다. 권한 오류는 503 안내를 반환하고 DB 기록을 삭제하지 않습니다. 원본을 수동으로 넣거나 이름을 바꿔도 새 보관함 항목을 자동 생성하지 않습니다. `temporary`·`chunks`는 진행 중 업로드에 사용하는 폴더입니다.