Make admin quotas editable and simplify uploader storage
This commit is contained in:
32
deploy.md
32
deploy.md
@@ -218,18 +218,18 @@ https://hop.noname.company
|
||||
|
||||
| 데이터 | NAS 저장 위치 | 컨테이너 내부 위치 |
|
||||
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------- |
|
||||
| 로그인 중 업로드한 새 파일 | `hop/uploads/originals/user-계정ID/` | `/data/uploads/originals/user-계정ID/` |
|
||||
| 비로그인 업로드 파일 | `hop/uploads/originals/guest-기기세션ID/` | `/data/uploads/originals/guest-기기세션ID/` |
|
||||
| 로그인 중 업로드한 새 파일 | `hop/uploads/originals/계정ID/` | `/data/uploads/originals/계정ID/` |
|
||||
| 비로그인 업로드 파일 | `hop/uploads/originals/기기세션ID/` | `/data/uploads/originals/기기세션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 → originals**로 들어갑니다. 전체 경로는 `/volume1/docker/projects/apps/hop/uploads/originals`입니다. **업로드한 사람** 기준으로 로그인 계정은 `user-계정ID`, 비로그인 기기는 `guest-기기세션ID` 폴더를 사용합니다. 같은 로그인 계정은 기기가 달라도 같은 폴더를 사용합니다. 받은 사람의 보관함은 원본을 참조하므로 여러 명이 받아도 복제하지 않습니다. 계정 ID와 이메일은 DB의 `users`에 있으며 파일명·접근권도 DB에서 연결합니다. 폴더명이 다운로드 권한을 부여하지는 않습니다.
|
||||
NAS 파일 관리자에서 **공유 폴더 → docker → projects → apps → hop → uploads → originals**로 들어갑니다. 전체 경로는 `/volume1/docker/projects/apps/hop/uploads/originals`입니다. **업로드한 사람** 기준으로 로그인 계정은 `계정ID`, 비로그인 기기는 `기기세션ID` 폴더를 사용합니다. 같은 로그인 계정은 기기가 달라도 같은 폴더를 사용합니다. 받은 사람의 보관함은 원본을 참조하므로 여러 명이 받아도 복제하지 않습니다. 계정 ID와 이메일은 DB의 `users`에 있으며 파일명·접근권도 DB에서 연결합니다. 폴더명이 다운로드 권한을 부여하지는 않습니다.
|
||||
|
||||
파일명은 `무작위 식별자.jpg`처럼 안전한 원래 확장자를 붙여 저장하고 다운로드 시 원래 전체 파일명을 제공합니다. 확장자가 없거나 안전한 확장자로 판별할 수 없는 파일에는 임의 확장자를 붙이지 않습니다. 업로드 폴더는 Git과 Docker 이미지에서 제외합니다. NAS 공유 폴더를 컨테이너에 연결하는 방식은 [Docker 공식 설명](https://docs.docker.com/engine/storage/bind-mounts/)과 [UGREEN 공식 안내](https://support.ugnas.com/detail/article/en-US/611)를 참고했습니다.
|
||||
|
||||
로그인한 A가 비로그인 B에게 새 파일을 보내도 A의 보관함에 자동 저장하지 않습니다. 원본은 업로드 소유자의 폴더에서 기존 접근권과 만료일에 따라 유지합니다. `completed`로 옮기는 방식은 사용하지 않습니다. 이전 방식에서는 계정 보관함에 연결된 파일은 다운로드가 끝나도 `originals`에 두었지만, 현재는 다운로드 완료 여부로 폴더를 나누지 않습니다.
|
||||
|
||||
앱 시작 시 기존 평면 `originals` 및 `completed`의 파일을 DB에 기록된 업로드 소유자 폴더로 이동하고 확장자도 적용합니다. 과거 기기 전송의 업로드 소유자가 기기 세션으로만 기록되어 있다면 해당 `guest-기기세션ID` 폴더로 이동하며 로그인 계정을 추정하지 않습니다. 파일 ID·다운로드 주소·만료일·사용량과 기존 자료는 유지합니다. 진행 중인 파일 읽기가 있으면 이동을 미루고 DB 변경 실패 시 원래 경로로 복원합니다. 이동 중 종료되면 숨김 폴더 `.file-moves`의 기록으로 경로와 DB를 맞춥니다. 빈 기존 `completed` 폴더만 제거하며 새 `completed` 폴더는 만들지 않습니다. 저장소 초기화·Compose 수정·폴더 수동 생성은 필요하지 않습니다.
|
||||
앱 시작 시 기존 평면 `originals` 및 `completed`의 파일을 DB에 기록된 업로드 소유자 폴더로 이동하고 확장자도 적용합니다. 과거 기기 전송의 업로드 소유자가 기기 세션으로만 기록되어 있다면 해당 `기기세션ID` 폴더로 이동하며 로그인 계정을 추정하지 않습니다. 파일 ID·다운로드 주소·만료일·사용량과 기존 자료는 유지합니다. 진행 중인 파일 읽기가 있으면 이동을 미루고 DB 변경 실패 시 원래 경로로 복원합니다. 이동 중 종료되면 숨김 폴더 `.file-moves`의 기록으로 경로와 DB를 맞춥니다. 빈 기존 `completed` 폴더만 제거하며 새 `completed` 폴더는 만들지 않습니다. 저장소 초기화·Compose 수정·폴더 수동 생성은 필요하지 않습니다.
|
||||
|
||||
각 사용자 폴더의 `owner-info.json`에서 업로드 요청 시 확인한 국가·지역·도시·접속 IP와 업로드 주체를 확인합니다. 최대 최근 20개·기본 7일의 기록이며 마지막 원본과 대기 업로드가 모두 사라지면 정보 파일과 빈 사용자 폴더도 정리합니다. 좌표·세션 쿠키·CSRF·파일 본문은 저장하지 않습니다. 일반 사용자의 API·보관함·공개 URL로 제공하지 않습니다. 외부 GeoIP 조회 없이 기본 제공 값만 사용하며 미제공 지역과 과거 자료는 확인 불가입니다.
|
||||
|
||||
@@ -481,11 +481,11 @@ sed -i -e 's/^GUEST_REQUESTS_PER_MINUTE=5$/GUEST_REQUESTS_PER_MINUTE=30/' -e 's/
|
||||
|
||||
저장 파일의 업로드 주체·파일별 접속 지역/IP·만료일, 전체 사용량·예약량·디스크 여유와 현재 업로드를 확인할 수 있습니다. 원본 삭제는 확인창을 거치며 모든 사용자의 연결된 항목도 무효화합니다. 이미 응답 중인 다운로드까지 회수하는 기능은 아닙니다. 관리 화면의 지역은 각 파일의 최초 전송 요청 당시 전달된 IP 기반 추정 정보입니다. 이후 다른 파일을 올린 지역으로 덮어쓰지 않습니다. 같은 요청의 여러 파일에는 같은 정보를 사용합니다.
|
||||
|
||||
**용량 설정**에서 동시 업로드 1~16개와 전체 파일 저장 한도를 변경합니다. 화면은 GB(1,000 단위)를 사용하며, 서버 설정 범위인 1~1024GiB는 화면에서 약 1.1~1099.5GB에 해당합니다. 기본은 4개·20GiB이고, 업로드 요청은 같은 계정/기기 주체당 동시에 최대 2개만 처리합니다. 기존 개인·기기 용량 제한, 파일당 크기 제한, 최소 5GiB 디스크 여유와 전송 요청 제한도 유지합니다. 업로드 본문을 읽기 전에 서버 전체 동시 요청 제한을 적용하고, 들어오는 HTTP 요청에 5분 타임아웃을 적용합니다. 큰 파일은 기존 8MiB 조각 전송을 사용합니다.
|
||||
**용량 설정 → 수정**에서 동시 업로드 1~16개, 전체 파일 저장 한도, 계정별 보관함·기기 세션별/전체 업로드·파일 한 개·NAS 최소 여유 공간을 변경합니다. 평소에는 값만 표시하고 수정 중에만 입력창과 저장/취소 버튼을 표시합니다. 개인 한도는 모든 계정·기기에 같은 기준으로 적용하며 계정별 예외 설정은 아닙니다. 화면은 GB(1,000 단위)를 사용하며, 서버 설정 범위인 1~1024GiB는 화면에서 약 1.1~1099.5GB에 해당합니다. 기본은 4개·20GiB이고, 업로드 요청은 같은 계정/기기 주체당 동시에 최대 2개만 처리합니다. 새 용량 항목의 초기값은 기존 `.env`를 따릅니다. 파일/개인/기기 한도와 NAS 최소 여유는 관리자 설정을 저장한 뒤 재시작 없이 적용하고 전송 횟수 제한은 유지합니다. 이미 수락해 공간을 예약한 업로드는 한도를 낮춰도 계속되며, 이후 새 공간 예약에는 변경된 한도를 적용합니다. 업로드 본문을 읽기 전에 서버 전체 동시 요청 제한을 적용하고, 들어오는 HTTP 요청에 5분 타임아웃을 적용합니다. 큰 파일은 기존 8MiB 조각 전송을 사용합니다.
|
||||
|
||||
**신규 업로드 중지**는 새 파일 전송·임시 저장을 거절합니다. 중지 전에 수락한 파일, 이미 저장된 원본의 재전송·다운로드, 링크·텍스트 전송은 계속됩니다. 한도를 낮춰도 기존 자료를 자동 삭제하지 않습니다.
|
||||
|
||||
**네트워크 → 블랙리스트/화이트리스트 → 추가**에서 국가 코드 또는 IPv4/IPv6·CIDR을 한 항목씩 등록합니다. 모달로 추가한 표를 확인하고 네트워크 설정을 저장하며, 제거할 때는 표의 항목을 선택합니다. 비어 있으면 차단하지 않습니다. 저장 즉시 일치하는 새 HTTP·Socket.IO 접속을 거절하고, 진행 중 업로드와 기기 연결도 종료합니다. `CLIENT_IP_SOURCE`와 `TRUSTED_PROXY_CIDRS`로 검증한 원본 IP를 사용하며 원본 IP가 없으면 직접 연결 주소를 기준으로 합니다. 국가 정보는 검증된 Cloudflare 전달 값만 사용합니다. 국가를 모르면 국가 블랙리스트만으로 차단하지 않지만, 화이트리스트가 있으면 허용 IP와 일치해야 접속할 수 있습니다. 현재 관리자의 IP/국가·인접 연결을 차단하는 규칙은 저장할 수 없습니다. 차단 해제는 목록에서 해당 규칙을 지우고 다시 저장합니다. 잘못된 규칙으로 관리자가 차단되면 기존 NAS 접근 권한으로 DB의 `admin_settings`를 복구해야 하므로 실제 규칙을 확인한 뒤 적용합니다.
|
||||
**네트워크 → 수정 → 블랙리스트/화이트리스트 → 추가**에서 국가 코드 또는 IPv4/IPv6·CIDR을 한 항목씩 등록합니다. IPv4는 `203.0.113.*`, `203.0.*.*`처럼 뒤쪽 전체 자리의 와일드카드도 지원하며 각각 `/24`, `/16` CIDR로 저장합니다. `203.*.113.*`나 `113*` 같은 불연속·부분 자리 패턴은 지원하지 않습니다. IPv6는 CIDR을 사용합니다. 모달로 추가한 표를 확인하고 네트워크 설정을 저장하며, 제거할 때는 표의 항목을 선택합니다. 비어 있으면 차단하지 않습니다. 저장 즉시 일치하는 새 HTTP·Socket.IO 접속을 거절하고, 진행 중 업로드와 기기 연결도 종료합니다. `CLIENT_IP_SOURCE`와 `TRUSTED_PROXY_CIDRS`로 검증한 원본 IP를 사용하며 원본 IP가 없으면 직접 연결 주소를 기준으로 합니다. 국가 정보는 검증된 Cloudflare 전달 값만 사용합니다. 국가를 모르면 국가 블랙리스트만으로 차단하지 않지만, 화이트리스트가 있으면 허용 IP와 일치해야 접속할 수 있습니다. 현재 관리자의 IP/국가·인접 연결을 차단하는 규칙은 저장할 수 없습니다. 차단 해제는 목록에서 해당 규칙을 지우고 다시 저장합니다. 잘못된 규칙으로 관리자가 차단되면 기존 NAS 접근 권한으로 DB의 `admin_settings`를 복구해야 하므로 실제 규칙을 확인한 뒤 적용합니다.
|
||||
|
||||
관리 설정은 DB의 `admin_settings`, 변경·원본 삭제 이력은 `admin_audit`에 보관합니다. 이력은 기본 30일이며 설정은 재시작 뒤 유지됩니다. 한 번 저장한 관리 설정은 `.env` 초기값보다 우선하고 이후에도 관리자 화면에서 수정합니다. 화면은 수동 새로고침만 사용하며 상시 목록 조회를 하지 않습니다.
|
||||
|
||||
@@ -525,3 +525,23 @@ Compose 변경이나 DB 초기화 없이 평소처럼 Git pull 후 `sudo docker
|
||||
평소처럼 Git pull 후 `sudo docker compose -f docker-compose.yaml up -d --build`로 적용합니다. 운영 NAS와 Cloudflare 설정은 자동 변경하지 않습니다.
|
||||
|
||||
국가 화이트리스트를 적용하더라도 컨테이너 내부 루프백에서 전달 헤더 없이 호출하는 `/api/health`는 상태 검사를 위해 허용합니다. 다른 API·관리 기능에는 이 예외를 적용하지 않고 외부 프록시에서 온 health 요청도 네트워크 규칙을 따릅니다.
|
||||
|
||||
## 출시 전 전송 자료 초기화 (계정 보존)
|
||||
|
||||
현재 운영 NAS의 전송/보관 자료를 비워도 된다는 운영자 결정에 따라 사용할 일회성 작업입니다. 앱 시작 때 자동 초기화하거나 새 폴더로 이관하는 코드는 추가하지 않습니다. 새 업로드는 접두어 없는 `uploads/originals/업로더UUID/`에 저장합니다. 관리자 파일 목록은 기기명을 생략하고 ID와 로그인 이메일 또는 ‘비로그인’을 표시합니다. 과거 비로그인 기록을 현재 로그인 계정으로 추정하지 않습니다.
|
||||
|
||||
프로젝트 디렉터리에서 최신 소스를 받은 뒤 진행합니다.
|
||||
|
||||
```sh
|
||||
git pull --ff-only origin main
|
||||
sudo docker compose -f docker-compose.yaml stop hop
|
||||
sudo sh scripts/reset-nas-transfer-data.sh
|
||||
```
|
||||
|
||||
초기화 성공 문구를 확인한 다음 **서비스를 중지한 상태에서** NAS 파일 관리자로 `hop/uploads` **안의 내용**을 모두 지웁니다. `uploads` 폴더 자체, `.env`, DB 볼륨은 유지합니다. 숨김 `.file-moves`도 초기화 대상입니다. 마지막으로 빌드·배포합니다.
|
||||
|
||||
```sh
|
||||
sudo docker compose -f docker-compose.yaml up -d --build
|
||||
```
|
||||
|
||||
스크립트는 `hop` 컨테이너가 중지됐는지 확인하고 해당 컨테이너의 DB 볼륨·실제 이미지 ID·실행 UID를 그대로 사용합니다. 외부 네트워크나 앱 프로세스 없이 전송·보관함·예약·기기 세션·재시도 기록만 트랜잭션으로 비우고 외래 키를 검사합니다. 계정·비밀번호·이메일 인증 상태·계정 로그인 세션·계정 설정·접속 기록과 관리자 설정/기록은 보존합니다. 파일은 자동 삭제하지 않습니다. 계정별 보관함의 파일·링크·텍스트와 기기 연결 공간은 모두 초기화됩니다. NAS에서 직접 실행하기 전에는 데이터가 바뀌지 않습니다.
|
||||
|
||||
Reference in New Issue
Block a user