49 KiB
hop. 유그린 NAS 배포 순서
운영 설정은 Git에서 관리하는 docker-compose.yaml 하나를 사용합니다. UGREEN Docker 앱과 NAS 터미널 모두 같은 파일을 사용합니다.
접속 경로: https://hop.noname.company → 기존 Cloudflare Tunnel → NPM(기존 8080) → NAS 내부 IP:50189 → hop.
아래 코드 블록을 하나씩 복사하고 Enter를 누르세요. 오류가 나오면 다음 단계로 넘어가지 마세요.
파일은 hop 폴더 안의 uploads, DB는 기존 hop_hop_database 볼륨에 저장합니다. 현재 정상 운영 중인 설치에서 Compose 파일만 통합할 때는 7-0. 기존 파일을 단일 운영 파일로 전환을 한 번 진행합니다. 이후에는 7. 다음 버전으로 업데이트만 진행하며 계정·파일·DB를 초기화하지 않습니다.
‘허용되지 않은 접속 경로’가 나오면: 주소창을 https://hop.noname.company로 바꾸세요. http://는 운영 APP_URL과 달라 차단됩니다. 5-1단계에서 HTTP를 HTTPS로 자동 이동시킵니다.
1. SSH에서 소스 받기
NAS에서 프로젝트를 둘 상위 폴더까지 이동한 상태에서 실행합니다.
git --version
docker compose version
openssl version
명령이 없으면 해당 단계의 오류를 알려주세요. NAS에는 Node.js나 npm을 설치할 필요가 없습니다.
git clone https://git.sori.studio/noname.company/hop.noname.company.git hop
Git 로그인을 물으면 Git 서버의 로그인 아이디와 비밀번호 또는 액세스 토큰을 입력합니다. 비밀번호 입력 시 화면에 글자가 안 나오는 것은 정상입니다.
cd hop
git branch --show-current
main이 나오면 다음으로 넘어갑니다.
2. NAS 설정 만들기
sudo sh scripts/configure-nas.sh
질문이 나오면 순서대로 입력합니다.
- NAS 내부 IPv4 주소: NAS 설정에 표시된 실제 내부 IP 입력 → Enter. NPM도 이 IP로 연결합니다.
- Resend API 키: 기존
hello@noname.company발신에 사용하는 키 붙여넣기 → Enter. 입력 문자는 숨겨집니다. 메일 기능 없이 시작하려면 Enter만 누릅니다. - 회원가입 활성화: 가입까지 테스트하려면
y→ Enter. 비활성화하려면 Enter만 누릅니다. 키를 생략하면 이 질문 없이 가입이 비활성화됩니다.
uploads 폴더를 만들고 NAS 로그인 사용자의 UID/GID로 저장 권한을 준비합니다. Docker 앱도 같은 UID/GID로 실행하므로 새로 받은 파일을 NAS 파일 관리자에서 읽을 수 있습니다. 처음에는 권한 준비용 작은 Alpine 이미지가 다운로드될 수 있습니다. 기존 프로젝트가 실행 중이면 먼저 중지하세요.
SESSION_SECRET과 DISCOVERY_SECRET은 자동 생성됩니다. NAS 포트와 컨테이너 포트는 모두 50189를 사용합니다.
마지막에 표시된 docker-compose.yaml 경로를 복사해 둡니다. .env는 NAS에만 남고 Git에 올라가지 않습니다. 기존 .env의 비밀값은 유지합니다. 이 스크립트는 .env와 저장 권한을 준비하고 Compose 문법을 검사하며, Git에서 관리하는 Compose 파일을 생성하거나 덮어쓰지 않습니다.
3. Docker 앱과 터미널에서 같은 파일로 배포
- NAS의 Docker 앱 → 프로젝트 → 생성 누르기.
- 프로젝트 이름은 hop, 프로젝트 폴더는
/volume1/docker/projects/apps/hop으로 지정. docker-compose.yaml선택. 앱 상단의 구성 파일 경로가 hop 폴더의 이 파일인지 확인.- 앱에서 배포 누르기. 기존 프로젝트의 시작·중지·로그 확인도 계속 이 프로젝트에서 진행합니다.
터미널에서 빌드·배포할 때도 hop 폴더에서 같은 파일을 사용합니다:
sudo docker compose -f docker-compose.yaml up -d --build
name: hop은 기존 프로젝트를 지정합니다. 컨테이너는 hop, 네트워크는 hop_hop_network, DB 볼륨은 hop_hop_database를 그대로 사용합니다. 포트·주소·UID/GID는 기존 .env 값을 읽으며, 현재 NAS의 값은 192.168.50.146:50189:50189와 1000:10입니다. build.context: ., env_file: .env, ./uploads는 Compose 파일이 있는 hop 폴더를 기준으로 해석하므로 NAS의 기존 절대 경로와 같습니다.
hop.noname.company:latest는 소스로 빌드하는 로컬 이미지이며 Docker Hub에 등록된 이미지가 아닙니다. **pull_policy: build**와 --build로 소스를 빌드합니다. 앱의 최신 이미지 다운로드는 다른 동작이므로 외부에서 이미지를 찾다가 실패할 수 있습니다. Docker 공식 문서.
Git으로 받은 파일을 운영 기준으로 사용합니다. 앱 편집기가 YAML을 저장하면서 형식을 바꾼 경우 git diff -- docker-compose.yaml로 변경 내용을 확인하세요. 필요한 운영 변경은 이 파일에 반영해 Git에도 함께 기록합니다. 프로젝트 관리 방식은 UGREEN 프로젝트 공식 안내를 참고했습니다.
4. Nginx Proxy Manager 설정
기존 NPM 관리 화면에서 진행합니다.
- Hosts → Proxy Hosts → Add Proxy Host 누르기. 이미 hop. 항목이 있으면 Edit.
- 다음 값 입력.
| 항목 | 입력값 |
|---|---|
| Domain Names | hop.noname.company |
| Scheme | http |
| Forward Hostname / IP | 2단계에서 입력한 NAS 내부 IP |
| Forward Port | 50189 |
| Cache Assets | 끄기 |
| Websockets Support | 켜기 |
- SSH에서 다음 명령을 실행합니다. 사용자 NAS의 실제
cloudflared-tunnel네트워크 주소로 hop. 전용 설정을 만듭니다.
sh scripts/print-hop-proxy-config.sh > hop.proxy.conf
cat hop.proxy.conf
- 나온 내용을 hop.noname.company의 Advanced에 붙여넣습니다. 기존의 같은 세 항목은 출력된 내용으로 교체합니다. 다른 도메인의 설정은 바꾸지 않습니다.
- Save 누르기.
이 설정은 Tunnel이 연결하는 Docker 주소를 허용하고 외부에서 원본 IP 헤더를 흉내 내어 들어오는 경로를 차단합니다. 실제 전달 헤더는 Cloudflare의 CF-Connecting-IP를 사용합니다. nginx-proxy-manager-app-1과 cloudflared-tunnel의 이름은 현재 NAS에서 확인한 값입니다. 호스트 네트워크 모드라 네트워크 주소를 얻을 수 없으면 설정을 넓히지 말고 오류를 알려주세요.
NPM이 Docker 컨테이너라면 Forward IP에 127.0.0.1을 입력하지 마세요. 여기서는 NAS 내부 IP를 사용합니다. 기존 Tunnel이 NPM의 8080으로 들어오는 설정은 그대로 유지합니다. NPM에 별도 인증서나 Force SSL을 추가하는 단계는 필요하지 않습니다. 공개 HTTPS는 현재 Cloudflare 경로를 사용합니다. NPM은 공식 문서의 도메인별 전달 기능을 사용합니다.
5. Cloudflare Tunnel에 주소 연결
- 기존 터널의 Published application routes / Public Hostnames 열기.
hop.noname.company가 기존 NPM으로 전달되는지 확인.- 주소가 없다면
hop.noname.company추가. - Service는 다른 서비스에서 이미 사용 중인 NPM의 8080 주소 그대로 입력.
- 저장.
8080을 50189로 바꾸지 않습니다. Tunnel은 NPM으로, NPM의 hop. 항목은 50189로 전달합니다.
5-1. HTTP를 HTTPS로 자동 이동
- Cloudflare →
noname.company→ Rules → Redirect Rules → Create rule. - 이름:
hop HTTPS. - Custom filter expression에 입력:
(http.host eq "hop.noname.company" and http.request.scheme eq "http")
- URL redirect 타입: Dynamic.
- Target URL expression:
concat("https://hop.noname.company", http.request.uri.path)
- 상태 코드 301, Preserve query string 켜기 → Deploy.
http://hop.noname.company를 열어 주소창이https://hop.noname.company로 바뀌는지 확인.
hop.에만 적용합니다. Tunnel → NPM 구간을 HTTP로 사용하는 기존 설정은 유지합니다. Cloudflare 공식 리디렉션 설정.
5-2. 주변 자동 발견 활성화
새 버전의 컨테이너가 실행 중이고 4단계 NPM 설정을 저장한 뒤, NAS의 hop 폴더에서 실행합니다.
sh scripts/enable-nas-discovery.sh
이 명령은 현재 운영 주소를 통한 실제 요청으로 인접 프록시와 Cloudflare 원본 IP 전달을 확인하고, 그 프록시 IP만 .env에 지정합니다. 운영 진단 토큰은 설정 스크립트가 생성하며 화면이나 Git에 공개하지 않습니다.
- 명령이 성공하면 같은 파일로 새
.env설정을 적용합니다:
sudo docker compose -f docker-compose.yaml up -d --build
- PC와 휴대폰을 같은 Wi-Fi에 연결하고
https://hop.noname.company를 열기. - QR 없이 상대 기기가 표시되는지 확인.
- 휴대폰 Wi-Fi를 끄고 모바일 데이터로 전환 → 다른 네트워크 기기가 목록에서 사라지는지 확인.
확인 중 오류가 나면 자동 발견 설정을 임의로 넓히지 마세요. 오류와 Docker 로그를 알려주세요. 주변 목록은 같은 공인 IP 기준이며 Bluetooth 탐색은 아닙니다. IPv6 주소가 서로 다르거나 VPN을 사용하면 같은 Wi-Fi에서도 자동 목록이 다를 수 있습니다.
NPM 또는 Tunnel 컨테이너를 재생성해 Docker IP가 바뀌었다면 4단계 설정을 다시 출력하여 저장한 뒤 5-2단계를 다시 실행합니다.
6. 동작 확인
브라우저에서 다음 주소를 엽니다.
https://hop.noname.company
- PC와 휴대폰을 같은 Wi-Fi에서 열어 주변 기기가 자동으로 보이는지 확인.
- 다른 네트워크의 기기는 다른 기기 연결 → 연결 공간 만들기 → 만들기. 승인 여부는 만들기 화면에서 선택합니다.
- 해당 기기에서 QR을 스캔하거나 다른 기기 연결 → 연결 공간 참여 → 4자리 초대 코드 입력 → 참여하기.
- 참여 후 QR 모달이 닫히고 상대 기기가 보이는지 확인.
- 상대 기기 선택 → 링크 전송 → 상대에서 수락 → 링크가 열리는지 확인.
- 작은 파일 전송 → 상대에서 수락 → 다운로드 확인.
- 가입을 활성화했다면 로그인 → 회원가입에서 실제 이메일로 가입 → Resend 인증 메일 확인 → 로그인.
- 로그인 상태로 받은 자료가 보관함 → 받은 항목에 표시되는지 확인.
주변 자동 발견은 5-2단계를 실행해 활성화합니다. 임의로 TRUSTED_PROXY_CIDRS에 모든 IP를 넣지 마세요.
가입 비밀번호는 8자 이상입니다. 이메일 인증 완료 후 해당 계정으로 자동 로그인하고 보관함의 받은 항목을 표시합니다. 다른 계정으로 로그인한 브라우저에서는 이전 로그인 세션을 종료하고 인증한 계정으로 전환합니다. 개발 DB와 테스트 계정은 NAS에 복사되지 않습니다.
모바일에서는 상단 설치 버튼을 누릅니다. Android 등 지원 브라우저는 설치 창이 열리고, iPhone은 Safari의 공유 → 홈 화면에 추가 안내가 열립니다. 설치한 앱 안에서는 설치 버튼을 숨깁니다. 전송은 앱을 열고 인터넷에 연결된 상태에서 합니다. PWA는 파일·전송 기록·API 응답을 오프라인 캐시에 저장하지 않습니다.
용량 설정
환경변수의 MB는 MiB 기준입니다. 2048은 2GiB입니다.
| 설정 | 기준 |
|---|---|
MAX_FILE_SIZE_MB=2048 |
파일 한 개당 최대 2GiB |
USER_STORAGE_LIMIT_MB=2048 |
로그인 계정 개인별 보관함 2GiB. 임시 저장·받은 파일 합산, 같은 원본은 한 계정에서 한 번 계산 |
GUEST_STORAGE_LIMIT_MB=2048 |
비로그인 기기 세션이 서버에 올리는 파일 총량 2GiB. 로그인 후 기기 간 전송도 해당 기기 세션을 사용 |
TOTAL_GUEST_STORAGE_LIMIT_MB=20480 |
서버 전체 익명 업로드 원본의 합계 20GiB |
TOTAL_STORAGE_LIMIT_MB=20480 |
로그인·비로그인 전체 원본과 유효 업로드 예약 합계 20GiB. 관리자 설정의 초기값 |
MAX_CONCURRENT_UPLOADS=4 |
동시에 처리할 업로드 HTTP 요청·파일 저장 작업의 상한. 관리자 설정의 초기값 |
AUDIT_RETENTION_DAYS=30 |
관리자 설정 변경·원본 삭제 기록의 보관 일수 |
MIN_FREE_DISK_MB=5120 |
NAS에 최소 5GiB 여유 공간 확보 |
DEFAULT_FILE_RETENTION_DAYS=7 |
정상 저장한 파일 최대 7일 후 삭제 |
ORPHAN_UPLOAD_RETENTION_HOURS=24 |
중단·방치한 업로드 조각 정리 기준 24시간 |
비로그인은 내역 화면이 없습니다. 파일은 서버로 업로드한 뒤 상대가 다운로드하므로, 전송과 정리를 위한 임시 서버 파일은 필요합니다. 로그인한 개인 보관함과 익명 전체 용량은 별도입니다. 직접 보관함 파일을 삭제하거나 만료되면 개인 사용량에서 빠지며, 다른 사람에게 공유된 원본은 마지막 유효 접근권이 끝날 때 물리적으로 삭제됩니다.
전송 파일이 저장되는 곳과 정리 시점
현재 흐름은 수락 → 서버로 업로드 → NAS에 원본 저장 → 수신자가 다운로드 → 보관 기간 종료 후 정리입니다. 다운로드 완료 직후 삭제하는 방식이 아닙니다. 임시 저장과 받은 항목도 같은 파일 저장소를 사용하며 DB의 접근 권한으로 사용자별 목록을 구분합니다.
| 데이터 | 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 → originals로 들어갑니다. 전체 경로는 /volume1/docker/projects/apps/hop/uploads/originals입니다. 업로드한 사람 기준으로 로그인 계정은 user-계정ID, 비로그인 기기는 guest-기기세션ID 폴더를 사용합니다. 같은 로그인 계정은 기기가 달라도 같은 폴더를 사용합니다. 받은 사람의 보관함은 원본을 참조하므로 여러 명이 받아도 복제하지 않습니다. 계정 ID와 이메일은 DB의 users에 있으며 파일명·접근권도 DB에서 연결합니다. 폴더명이 다운로드 권한을 부여하지는 않습니다.
파일명은 무작위 식별자.jpg처럼 안전한 원래 확장자를 붙여 저장하고 다운로드 시 원래 전체 파일명을 제공합니다. 확장자가 없거나 안전한 확장자로 판별할 수 없는 파일에는 임의 확장자를 붙이지 않습니다. 업로드 폴더는 Git과 Docker 이미지에서 제외합니다. NAS 공유 폴더를 컨테이너에 연결하는 방식은 Docker 공식 설명과 UGREEN 공식 안내를 참고했습니다.
로그인한 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 공식 설정 문서를 참고하세요. CF 좌표·우편번호는 무시합니다. VPN·통신사망 등으로 실제 거주지와 다를 수 있습니다. 악용 대응용 관리자 화면·국가/IP 차단은 별도로 관리하며 실제 규칙은 운영자가 지정합니다.
파일 관리자로 원본을 수동 삭제하면 다음 보관함·용량 조회, 다운로드·재전송 시 확인하거나 약 1분 간격의 정리 작업에서 감지합니다. 같은 원본의 모든 계정·기기 항목은 만료 처리해 목록과 전송 선택에서 제외하고 사용량을 회수합니다. 열린 화면에는 갱신 이벤트를 보내며 이미 열린 다운로드·전송 화면에서는 삭제 안내를 표시합니다. 권한 오류는 503 안내를 반환하고 DB 기록을 삭제하지 않습니다. 원본을 수동으로 넣거나 이름을 바꿔도 새 보관함 항목을 자동 생성하지 않습니다. temporary·chunks는 진행 중 업로드에 사용하는 폴더입니다.
- 다운로드 실패·수신자 이탈: 이미 저장된 원본은 바로 삭제되지 않습니다. 받은 항목에 저장된 파일은 로그인 후 만료 전에 다시 다운로드할 수 있습니다. 개인 용량 초과로 받은 항목에 저장되지 않았거나 비로그인으로 받은 경우, 사이트를 떠난 뒤 이전 다운로드를 다시 찾는 UI는 제공하지 않습니다. 이때는 다시 전송하는 방법을 사용합니다. 다운로드 시작 표시는 기기에 저장이 끝났다는 확인을 의미하지 않습니다.
- 업로드 도중 이탈: 완료되지 않은 업로드는 받은 항목에 생성되지 않습니다. 취소된 조각은 제거하며, 남은 미완료·고아 업로드는 기본 24시간 후 정리합니다. 브라우저를 떠난 뒤 자동 전송·자동 다운로드 재개는 보장하지 않습니다.
- 만료·삭제: 기본 보관 기간은 항목별 7일이며, 정리 작업은 약 1분 간격입니다. 같은 원본을 사용하는 유효 접근 권한이 하나라도 남아 있으면 유지합니다. 모두 만료·삭제되면 삭제 실행 취소 시간 이후 원본을 제거합니다. 재배포·재시작만으로 파일이 삭제되지는 않습니다.
- 관리자 접근: NAS·Docker 볼륨과 DB를 읽을 권한이 있는 관리자는 파일과 연결 정보를 확인할 수 있습니다. 종단 간 암호화나 파일 자체 암호화는 적용하지 않습니다. 앱의 서비스 관리 화면에서는 인증된
hello@noname.company만 파일 메타데이터와 파일별 접속 지역/IP를 조회·원본 삭제할 수 있습니다. 파일 내용을 내려받는 관리자 API는 제공하지 않으며 일반 계정은 허용된 본인 항목만 조회할 수 있습니다.
개인 한도가 차도 수신·다운로드는 계속합니다. 새로 받은 파일은 받은 항목에 저장하지 않고 짧은 안내를 표시합니다. 직접 저장은 용량 부족 시 거절합니다. 링크·메모는 파일 용량 합계에 포함하지 않습니다. 기존 10GiB 기본값은 설정 스크립트가 2GiB로 바꾸며, 다른 값으로 조정한 설정과 기존 자료는 유지합니다.
대용량 파일: hop.은 파일을 8MiB씩 분할해 업로드합니다. Cloudflare의 Free/Pro 100MB, Business 200MB 제한은 HTTP 요청 한 번의 크기이므로 전체 파일을 그 크기로 제한하지 않습니다. 앱의 파일당 최대 2GiB와 계정·익명 저장소 제한은 유지합니다. 중간 요청 오류는 서버가 받은 위치부터 최대 3회 자동 재시도합니다. 재시도가 끝나면 현재 전송 화면의 ‘다시 전송’을 누릅니다. 브라우저를 재시작하거나 로그아웃한 뒤의 자동 복구는 보장하지 않습니다. 마지막 파일 내용 검증과 저장이 끝나야 수신 자료가 생성됩니다. Cloudflare 업로드 제한.
7. 다음 버전으로 업데이트
소스 코드와 운영 Compose 설정을 Git에서 함께 받습니다. 같은 파일로 빌드·재배포하며 기존 DB·업로드는 유지합니다.
- hop 폴더로 이동:
cd /volume1/docker/projects/apps/hop
- 최신 코드와 설정 받기:
git pull --ff-only origin main
- 빌드·재배포. 완료될 때까지 기다립니다:
sudo docker compose -f docker-compose.yaml up -d --build
- 상태 확인:
sudo docker compose -f docker-compose.yaml ps
- 브라우저 새로고침. PWA는 앱을 닫고 다시 열기.
일반 업데이트에는 설정 스크립트 실행·YAML 재생성·수동 복사가 필요 없습니다. UGREEN 앱도 같은 docker-compose.yaml을 사용하며 프로젝트 관리 기능은 계속 사용할 수 있습니다. NPM·Cloudflare Tunnel 설정은 그대로 유지합니다.
Git pull이 로컬 Compose 변경 때문에 중단되면 git diff -- docker-compose.yaml로 차이를 먼저 확인합니다. 앱에서 필요한 운영 설정을 바꾼 경우 그 변경을 Git 설정과 함께 정리하며, 자료나 설정을 강제로 초기화하지 않습니다.
7-0. 기존 파일을 단일 운영 파일로 전환
이번에 한 번만 진행합니다. 기존 컨테이너·네트워크·DB 볼륨·업로드·계정을 삭제하지 않습니다. .env도 그대로 사용합니다.
- hop 폴더로 이동:
cd /volume1/docker/projects/apps/hop
- 현재 앱이 만든 파일이 Git 미추적 파일인 경우에만
/tmp에 옮겨 둡니다. 현재 실행 중인 컨테이너의 데이터는 변경하지 않습니다:
if [ -f docker-compose.yaml ] && ! git ls-files --error-unmatch docker-compose.yaml >/dev/null 2>&1; then
sudo mv docker-compose.yaml /tmp/hop-compose.before-unify.yaml
fi
- Git에서 단일 운영 파일 받기:
git pull --ff-only origin main
- 기존
.env값이 현재 운영 설정과 맞는지 확인합니다. 아래 명령은 비밀값을 출력하지 않습니다:
grep -E '^(APP_URL|HOP_BIND_IP|HOP_HOST_PORT|HOP_CONTAINER_PORT|HOP_RUN_UID|HOP_RUN_GID)=' .env
현재 NAS에서는 APP_URL=https://hop.noname.company, HOP_BIND_IP=192.168.50.146, 두 포트는 50189, UID/GID는 1000과 10을 사용합니다. 값이 비어 있거나 다르면 기존 운영 값에 맞춰 .env를 정리한 뒤 진행합니다. 이 전환에서는 configure-nas.sh나 저장소 초기화를 다시 실행하지 않습니다.
- Compose 문법 검사. 정상일 때는 출력 없이 끝납니다:
sudo docker compose -f docker-compose.yaml config --quiet
- 같은 파일로 빌드·재배포:
sudo docker compose -f docker-compose.yaml up -d --build
- UGREEN 앱 상단의 구성 파일 경로가
/volume1/docker/projects/apps/hop/docker-compose.yaml인지 확인합니다. 이후에는 파일 내용을 다른 Compose 파일로 옮기지 않습니다. - 더 이상 사용하지 않는 NAS 생성 파일만 정리합니다. 기존
compose.yaml은 Git 업데이트에서 제거됩니다:
rm -f compose.nas.yaml
이후에는 위 7장의 일반 업데이트 명령만 사용합니다. /tmp에 옮긴 이전 설정은 전환 확인용이며, 운영에는 Git의 docker-compose.yaml만 사용합니다.
7-1. 저장 데이터 유지
DB는 기존 hop_hop_database 볼륨을 /data/database에 연결하며 nocopy: true를 유지합니다. 업로드는 같은 hop 폴더의 uploads를 /data/uploads에 연결합니다. 컨테이너 교체 시 같은 저장소를 사용합니다. .env, uploads, DB, 백업 파일은 Git에서 제외하고, docker-compose.yaml은 Git에서 관리합니다.
실제 업로드 연결 경로는 다음 명령으로 확인할 수 있습니다:
sudo docker inspect --format '{{range .Mounts}}{{if eq .Destination "/data/uploads"}}{{.Type}} → {{.Source}}{{end}}{{end}}' hop
현재 NAS에서는 bind → /volume1/docker/projects/apps/hop/uploads가 나와야 합니다. 이전 업로드 볼륨을 아직 사용 중인 다른 설치는 파일 보존 방법부터 확인한 뒤 전환합니다. 이번 Compose 정리는 기존 자료 초기화 작업이 아닙니다.
추가 작업이 필요한 업데이트
| 변경 내용 | 필요한 작업 |
|---|---|
| 코드·화면·CSS 변경 | Git pull → 이미지 재빌드 → 재배포 |
| Docker 구성·포트·볼륨 경로 변경 | Git pull → 같은 docker-compose.yaml로 빌드·재배포. 저장 경로 변경은 기존 자료 연결부터 확인 |
| 환경변수 변경 | NAS의 .env 값 수정 → 같은 docker-compose.yaml로 재배포 |
| DB 구조·기존 데이터 변경 | 해당 버전의 변경 안내 확인 → 필요 시 아래 데이터 백업 → Git pull → 재빌드 → 재배포 |
YAML이나 DB 변경이 필요한 버전은 업데이트 안내에서 별도로 표시합니다. 코드·CSS 수정 때마다 데이터 백업이나 YAML 재생성을 반복할 필요는 없습니다.
필요한 경우에만 데이터 백업
백업 대상은 Git에 있는 코드가 아니라 **로그인 계정·설정이 들어 있는 DB, 임시 보관 파일, NAS의 .env**입니다. DB 변경 전 상태로 돌아갈 수 있게 준비하거나 현재 데이터를 별도로 보관하고 싶을 때 실행합니다.
- Docker 앱 → 프로젝트 → hop → 중지.
- SSH에서 순서대로 실행:
cd /volume1/docker/projects/apps/hop
mkdir -p backups
chmod 700 backups
sudo docker volume inspect hop_hop_database >/dev/null
sudo docker run --rm -v hop_hop_database:/source/database:ro -v "$PWD/uploads:/source/uploads:ro" -v "$PWD/backups:/backup" alpine:3.22 sh -c 'tar -czf "/backup/hop-$(date +%Y%m%d-%H%M%S).tar.gz" -C /source database uploads'
cp -p .env "backups/env-$(date +%Y%m%d-%H%M%S)"
- 백업 후 위의 업데이트 순서로 진행하거나, 백업만 했다면 Docker 앱에서 시작.
DB 구조가 바뀐 뒤 이전 상태로 복구하려면 같은 시점의 코드·DB·파일·환경설정을 함께 사용해야 합니다. 데이터 유지가 필요한 경우 프로젝트의 볼륨을 삭제하거나 docker compose down -v를 실행하지 않습니다.
8. 시작 / 중지 / 로그
| 할 일 | 누를 위치 |
|---|---|
| 시작 | Docker → 프로젝트 → hop → 시작 |
| 중지 | Docker → 프로젝트 → hop → 중지 |
| 설정 변경 적용 | .env 또는 Git의 docker-compose.yaml 수정 → 같은 파일로 재배포 |
| 오류 확인 | Docker → 컨테이너 → hop 컨테이너 → 로그 |
SSH에서 실제 설정 중 비밀값 없이 주소·포트만 확인하려면:
grep -E '^(APP_URL|HOP_BIND_IP|HOP_HOST_PORT|HOP_CONTAINER_PORT)=' .env
NAS에서 앱의 상태만 확인하려면:
HOP_NAS_IP=$(sed -n 's/^HOP_BIND_IP=//p' .env)
curl -fsS "http://${HOP_NAS_IP}:50189/api/health"
HTTP 주소는 상태 확인에만 씁니다. 실제 접속·QR·로그인은 https://hop.noname.company를 사용하세요.
docker-compose.yaml에는 .env의 경로와 환경변수 참조만 기록합니다. 비밀값을 YAML에 펼쳐 저장하지 않습니다. 설정 스크립트는 Docker Compose 공식 문서의 --no-env-resolution --quiet로 문법만 검사합니다. .env, uploads, backups는 Git과 Docker 빌드에서 제외됩니다.
이미지 다운로드 오류가 나올 때
pull access denied for hop.noname.company는 소스 빌드용 로컬 이미지를 외부 레지스트리에서 찾았다는 뜻입니다. 앱의 재배포 → 최신 이미지는 외부 다운로드 기능입니다. Running은 실행 상태만 나타내며 최신 Git 코드가 반영됐다는 뜻은 아닙니다.
Docker 앱에서 hop 프로젝트 → Compose 구성을 열어 다음 설정인지 확인하세요:
pull_policy: build
이전 never 설정이면 build로 변경하고 재배포합니다. build 경로는 /volume1/docker/projects/apps/hop, Dockerfile은 Dockerfile을 그대로 사용합니다. 기존 DB·업로드 저장 경로도 그대로 유지합니다. 새 코드가 NAS 폴더에 반영돼 있어야 합니다.
다시 실행해도 Running 0.0s와 다운로드 오류만 나오면 실제 소스 빌드가 확인되지 않은 상태입니다. 7장의 sudo docker compose -f docker-compose.yaml up -d --build는 같은 운영 파일로 소스를 빌드·배포하는 명령입니다. 먼저 배포 화면과 전체 로그를 확인하세요. docker login이나 볼륨 삭제는 필요 없습니다.
초대 코드와 모바일 화면
새 초대 코드는 기본 4자리입니다. 기존 .env를 수정하지 않아도 새 이미지에서 적용됩니다. 업데이트 전에 발급한 8자리 코드와 QR은 원래 만료 시간까지 유효합니다.
나중에 초대 코드를 6자리로 바꾸려면 .env에 SPACE_INVITE_CODE_LENGTH=6을 넣고 같은 docker-compose.yaml로 재배포합니다. DB나 업로드 볼륨은 그대로 사용합니다.
모바일 기기 설정은 하단 시트로 열리고 입력창을 자동으로 선택하지 않습니다. 사용자가 입력창을 누르면 키보드가 열린 가시 영역에 맞춰 시트 높이를 조정합니다. 로그인·기기명·URL 등 입력 글자는 16px 이상으로 적용하며 화면 확대 기능 자체를 차단하지 않습니다. 파일 전송 중에는 선택한 파일명 아래에 진행률을 표시합니다.
헤더에는 로그인/보관함과 더보기만 표시합니다. 더보기 → 앱 설치 / 테마 / 서비스 정보에서 보조 기능을 사용하세요.
클립보드 전송 업데이트
일반 텍스트·단일 링크 자동 판별과 텍스트 수신 미리보기·복사를 추가했습니다. 이번 변경은 기존 텍스트 컬럼을 재사용하므로 DB 마이그레이션·YAML 변경 없이 Git pull → 이미지 재빌드 → 재배포로 적용합니다. 기존 .env에 새 설정이 없어도 텍스트 한도는 256KiB입니다. 한도를 별도로 조정할 때만 MAX_TEXT_PAYLOAD_SIZE_KB를 변경하고 환경설정을 재배포하세요.
브라우저마다 클립보드 권한 방식이 다릅니다. 첫 읽기가 제한되어도 직접 입력하거나 ‘다시 가져오기’를 사용할 수 있습니다. ‘다시 가져오기’에서 브라우저의 붙여넣기 확인이 뜨는 것은 권한 확인 절차입니다. 기기 카드 자체의 텍스트 선택은 차단하고 입력창·받은 텍스트의 직접 선택과 붙여넣기는 유지합니다.
화면 안내 정리
보관함 탭은 받은 항목 → 임시 저장 순서입니다. 항목의 날짜는 보관 만료 · 날짜/시간까지로 표시합니다. 계정 설정에서 이메일은 표시만 하고 변경 버튼은 숨깁니다. 비밀번호 변경은 유지합니다. 오류 토스트는 텍스트와 닫기 아이콘의 양쪽 여백을 맞추며 기본 5초 뒤 닫힙니다. 네트워크 연결 실패는 한국어로 안내하고 닫기 버튼으로 즉시 닫을 수도 있습니다.
이메일 인증 자동 로그인 업데이트
가입 비밀번호는 최소 8자입니다. 인증 메일의 링크를 열고 이메일 인증 완료하기를 누르면 해당 계정으로 자동 로그인하며 보관함의 받은 항목을 표시합니다. 다른 계정으로 로그인한 브라우저에서는 이전 세션을 종료하고 인증한 계정으로 전환합니다. 다른 기기의 로그인은 유지합니다. 만료·재사용된 링크로는 현재 로그인을 변경하지 않습니다.
이번 인증 변경은 Git pull → 이미지 재빌드 → 재배포로 적용합니다. 기존 계정·파일은 유지하고 YAML·환경변수·DB 구조는 변경하지 않습니다. 기존 DB·업로드를 초기화하지 않습니다.
전송 요청 제한과 차단 해제
새 기본 한도는 같은 상대 10회/분, 기기 세션 전체 30회/분, 같은 IP 전체 120회/분입니다. 로그인 중 기기 전송에도 같은 기준을 적용합니다. 파일 개수가 아닌 전송 요청 횟수를 계산하며, 거절·취소한 전송 요청도 이미 사용한 횟수에 포함됩니다. 제한된 재시도나 진행 중 전송 때문에 거절된 요청은 다른 한도를 추가로 소모하지 않습니다.
제한 창은 첫 허용 요청부터 60초이며 반복 재시도로 종료 시간이 연장되지 않습니다. 상대가 거절하면 별도로 기본 300초 동안 그 상대에게 재요청할 수 없습니다. 서버는 Retry-After와 남은 시간을 반환하고, 전송 버튼에 카운트를 표시합니다. 대기 중에도 입력·파일 선택은 유지하며 시간이 끝나면 전송 버튼이 다시 활성화됩니다.
기존 NAS .env에 이전 기본값이 있으면 한 번 아래 명령으로 그 값만 변경합니다. 직접 조정한 다른 값과 비밀값은 유지합니다. hop 폴더에서 실행하세요:
sed -i -e 's/^GUEST_REQUESTS_PER_MINUTE=5$/GUEST_REQUESTS_PER_MINUTE=30/' -e 's/^IP_REQUESTS_PER_MINUTE=20$/IP_REQUESTS_PER_MINUTE=120/' -e 's/^PAIR_REQUESTS_PER_MINUTE=2$/PAIR_REQUESTS_PER_MINUTE=10/' .env
이후 Git pull과 같은 docker-compose.yaml로 빌드·재배포합니다. YAML이나 DB 구조 변경은 없습니다. 명시적인 .env 값이 새 기본값보다 우선합니다.
수신 요청은 파일·링크·텍스트 아이콘과 제목으로 구분하고 수락/거절 버튼은 2:1입니다. 차단은 오른쪽 하단의 작은 텍스트를 누른 뒤 확인해야 실행됩니다. 푸터의 내 기기 설정 → 차단한 기기 관리 → 차단 해제로 해제합니다. 차단은 현재 브라우저의 기기 세션에 저장되며 로그인 여부와 별개입니다. 해제해도 이전 요청을 다시 수락하는 방식으로 복구하지 않으므로 발신자가 새로 전송해야 합니다. 상대가 나를 별도로 차단했다면 내 차단 해제만으로 상대의 차단을 풀 수 없습니다.
수신 파일 용량은 0.1MB 이상부터 MB, 1GB 이상부터 GB로 표시합니다. 그보다 작은 파일은 KB 또는 ‘1 KB 미만’이며, 파일 한 개의 용량은 파일명 옆에 한 번만 표시합니다. 단위는 MB=1,000,000바이트, GB=1,000,000,000바이트입니다. 서버의 MiB/GiB 기준 저장·파일 한도는 유지합니다.
관리자 화면과 업로드 보호
이메일 인증을 완료한 hello@noname.company 계정으로 로그인한 뒤 더보기 → 관리자를 선택합니다. 별도의 관리자 비밀번호나 환경변수 없이 이 정확한 활성·인증 계정만 권한을 갖습니다. 서버에서도 권한을 검사하며 다른 일반 계정에는 메뉴와 API를 제공하지 않습니다.
저장 파일의 업로드 주체·파일별 접속 지역/IP·만료일, 전체 사용량·예약량·디스크 여유와 현재 업로드를 확인할 수 있습니다. 원본 삭제는 확인창을 거치며 모든 사용자의 연결된 항목도 무효화합니다. 이미 응답 중인 다운로드까지 회수하는 기능은 아닙니다. 관리 화면의 지역은 각 파일의 최초 전송 요청 당시 전달된 IP 기반 추정 정보입니다. 이후 다른 파일을 올린 지역으로 덮어쓰지 않습니다. 같은 요청의 여러 파일에는 같은 정보를 사용합니다.
용량 설정에서 동시 업로드 116개와 전체 파일 저장 한도를 변경합니다. 화면은 GB(1,000 단위)를 사용하며, 서버 설정 범위인 11024GiB는 화면에서 약 1.1~1099.5GB에 해당합니다. 기본은 4개·20GiB이고, 업로드 요청은 같은 계정/기기 주체당 동시에 최대 2개만 처리합니다. 기존 개인·기기 용량 제한, 파일당 크기 제한, 최소 5GiB 디스크 여유와 전송 요청 제한도 유지합니다. 업로드 본문을 읽기 전에 서버 전체 동시 요청 제한을 적용하고, 들어오는 HTTP 요청에 5분 타임아웃을 적용합니다. 큰 파일은 기존 8MiB 조각 전송을 사용합니다.
신규 업로드 중지는 새 파일 전송·임시 저장을 거절합니다. 중지 전에 수락한 파일, 이미 저장된 원본의 재전송·다운로드, 링크·텍스트 전송은 계속됩니다. 한도를 낮춰도 기존 자료를 자동 삭제하지 않습니다.
네트워크 → 블랙리스트/화이트리스트 → 추가에서 국가 코드 또는 IPv4/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 초기값보다 우선하고 이후에도 관리자 화면에서 수정합니다. 화면은 수동 새로고침만 사용하며 상시 목록 조회를 하지 않습니다.
이번 변경은 관리 테이블만 추가하며 기존 계정·자료를 초기화하지 않습니다. docker-compose.yaml과 볼륨·포트·네트워크·pull_policy: build는 변경하지 않습니다. 새 환경변수를 추가하지 않아도 기본값으로 작동합니다. 평소와 같이 다음 명령으로 적용합니다.
git pull --ff-only origin main
sudo docker compose -f docker-compose.yaml up -d --build
앱의 업로드 제한과 차단은 저장 공간·동시 작업을 보호하지만 앱에 도달하기 전의 대규모 트래픽 공격까지 해결하지는 않습니다. 필요하면 Cloudflare 등 앞단의 접근·요청 제한도 함께 적용합니다.
파일별 접속 정보와 공통 표시 업데이트
file_upload_contexts 테이블을 자동 추가하여 각 원본 파일의 최초 전송 요청 정보를 보관합니다. 새 요청을 다른 IP/지역에서 보내거나 재시도해도 기존 파일의 정보는 바뀌지 않습니다. 기존 파일은 소유자 정보의 동일 요청 ID 기록이 있을 때만 복구하며 다른 요청의 최신 지역은 사용하지 않습니다. 기록은 기본 최대 7일이고 파일 실패·삭제 시에도 약 1분 주기 정리에서 제거합니다. 관리자 외의 전송·보관함 API에는 노출하지 않습니다.
관리자 이름은 대괄호로 감싸고, 만료일은 관리자·보관함 모두 [보관 만료] 2026년 10월 22일 11:02 까지 형식의 한국 시간으로 표시합니다. 푸터의 발견 허용 문구는 숨깁니다. 용량은 모든 화면에서 KB/MB/GB(1,000 단위)로 통일하며 실제 파일 한도·개인별 한도·서버 전체 한도는 그대로입니다. .env 용량 값의 MiB 기준은 유지하고 관리자 화면의 전체 용량 입력은 GB로 환산합니다. 반올림된 GB를 수정하지 않았다면 다른 설정을 저장해도 원래 한도를 보존합니다.
Compose 변경이나 DB 초기화 없이 평소처럼 Git pull 후 sudo docker compose -f docker-compose.yaml up -d --build로 적용합니다.
관리자 한도 설명에는 실제 서버의 계정별 보관함·기기 세션별 업로드·기기 세션 전체 업로드·파일당 크기·NAS 최소 여유 공간을 각각 표시합니다. 전체 파일 한도는 모두가 공유하며 개인에게 같은 양을 배정하지 않습니다. 기본값 2GiB는 약 2.1GB, 최소 여유 5GiB는 약 5.4GB, 전체 20GiB는 약 21.5GB로 표시됩니다. 이 값은 각 NAS의 기존 .env와 저장된 관리 설정을 따르며 이번 표시 변경으로 값을 재설정하지 않습니다.
네트워크·사용자·관리 기록 업데이트
0014_network_protection은 automatic_blocks, user_activity 테이블만 추가합니다. 기존 차단 규칙·관리 설정·계정·자료와 Compose는 유지합니다. 새 설정이 없는 기존 DB에는 화이트리스트 빈 목록과 자동 차단 꺼짐 기본값을 적용합니다. 별도 .env 변경은 없습니다.
네트워크 탭의 세 영역에서 관리합니다. 수동 블랙리스트는 국가/IP/CIDR 차단, 화이트리스트는 등록 국가 또는 IP/CIDR만 허용하는 목록입니다. 비어 있으면 공개하고 블랙리스트·자동 차단이 화이트리스트보다 우선합니다. 국가 미제공 시 허용 IP와 일치하지 않으면 비공개입니다. 자동 차단을 켜면 원본 공인 IP별 기본 10분 창에서 로그인 실패 10회·요청 제한 위반 20회·파일 요청 시도 30회 초과·요청 용량 10GB 초과를 감지해 기본 30분 차단합니다. 기준과 차단 시간을 화면에서 조정합니다. 동일 IP의 로그인 보관함/기기 전송·여러 기기 세션을 함께 계산하며 같은 요청의 재시도는 제외합니다. 수락 전의 유효한 파일 요청 본문을 기준으로 세며 실제 업로드 성공 용량 통계는 아닙니다. 정상 조각 API·UPLOAD_BUSY·거절 대기는 위반 횟수에서 제외합니다. 공유 공인 IP/NAT의 다른 사람도 영향을 받을 수 있습니다.
자동 IP 차단은 DB에 보존하고 만료 후 약 1분 주기로 정리합니다. 감지 중인 횟수·용량 창과 중복 요청 키는 최대 각 10,000개 메모리 항목으로 제한하며 재시작 시 초기화됩니다. 자동 차단 목록은 최대 10,000개의 활성 IP까지 보관하고, 그 이후에도 기존 요청 제한·전체 용량·동시 작업 제한은 유지합니다. 자동 차단을 끄면 기록을 유지한 채 적용/감지를 중지하며, 기간이 끝나기 전에 다시 켜면 남은 차단도 적용됩니다. 표에서 선택 해제하면 해당 IP의 감지 누적도 초기화합니다. 알려지지 않은 프록시 주소는 자동 차단하지 않습니다. 기존 인증된 관리자 로그인은 자동 차단 상태를 복구할 수 있으며 수동 규칙은 우회하지 않습니다.
관리 기록은 전체 설정 반복 대신 실제 변경 필드의 전/후와 목록 추가/제거를 보관합니다. 변경 없는 저장은 기록하지 않으며 과거 전체 설정 기록은 이전 값이 있는 경우만 비교합니다. 기록은 기본 30일, 읽음 표시는 없고 개별/선택/전체 삭제가 가능하며 삭제해도 실제 설정과 자료는 유지됩니다.
사용자 탭은 로그인 계정의 이메일·상태와 마지막 로그인/인증/로그인 API 활동의 시간·국가·지역·IP를 관리자에게만 제공합니다. 같은 정보는 1분 안에 반복 기록하지 않으며 위치/IP는 AUDIT_RETENTION_DAYS(기본 30일) 후 지우고 마지막 시각만 남깁니다. 좌표·비밀번호·쿠키·토큰은 표시하거나 접속 메타데이터로 기록하지 않습니다. 기존 사용자의 과거 위치를 추정하지 않습니다.
사용자 더보기에서 비밀번호 재설정 메일을 보내거나 계정을 삭제합니다. 메일은 기존 Resend 설정과 발송 제한·일회용 토큰을 사용하며 메일 발송만으로 비밀번호를 바꾸지 않습니다. 수신자가 재설정을 완료하면 기존 로그인 세션을 폐기합니다. 계정 삭제는 대상 이메일 입력 확인 후 계정 개인정보·비밀번호를 비우고 세션/토큰·보관함 접근권을 무효화하며 진행 중인 계정 전송도 취소합니다. 공유받은 다른 사람의 파일은 마지막 유효 접근권까지 유지합니다. 계정은 참조 무결성을 위해 익명화한 삭제 상태로 남기고 사용자 목록에서 제외합니다. 관리자 자신과 hello@noname.company는 이 메뉴로 삭제하지 못합니다. 계정 지원 작업도 관리 기록에 남습니다.
평소처럼 Git pull 후 sudo docker compose -f docker-compose.yaml up -d --build로 적용합니다. 운영 NAS와 Cloudflare 설정은 자동 변경하지 않습니다.
국가 화이트리스트를 적용하더라도 컨테이너 내부 루프백에서 전달 헤더 없이 호출하는 /api/health는 상태 검사를 위해 허용합니다. 다른 API·관리 기능에는 이 예외를 적용하지 않고 외부 프록시에서 온 health 요청도 네트워크 규칙을 따릅니다.