Files
hop.noname.company/deploy.md

281 lines
16 KiB
Markdown

# hop. 유그린 NAS 배포 순서
**SSH에서는 소스와 설정만 준비합니다. 시작·중지·재빌드는 NAS의 Docker 앱에서 합니다.**
접속 경로: `https://hop.noname.company → 기존 Cloudflare Tunnel → NPM(기존 8080) → NAS 내부 IP:50189 → hop.`
아래 코드 블록을 하나씩 복사하고 Enter를 누르세요. 오류가 나오면 다음 단계로 넘어가지 마세요.
이미 배포했다면 **7. 업데이트**부터 진행하세요. 이번 업데이트는 **8MiB 분할 업로드·주변 자동 발견·PWA 설치·개인 보관함 2GiB**를 추가하며 기존 DB·업로드 볼륨을 유지합니다. 새 도메인이나 외부 포트는 추가하지 않습니다.
**‘허용되지 않은 접속 경로’가 나오면:** 주소창을 `https://hop.noname.company`로 바꾸세요. `http://`는 운영 `APP_URL`과 달라 차단됩니다. 5-1단계에서 HTTP를 HTTPS로 자동 이동시킵니다.
## 1. SSH에서 소스 받기
NAS에서 프로젝트를 둘 상위 폴더까지 이동한 상태에서 실행합니다.
```sh
git --version
```
```sh
docker compose version
```
```sh
openssl version
```
명령이 없으면 해당 단계의 오류를 알려주세요. NAS에는 Node.js나 npm을 설치할 필요가 없습니다.
```sh
git clone https://git.sori.studio/noname.company/hop.noname.company.git hop
```
Git 로그인을 물으면 Git 서버의 로그인 아이디와 비밀번호 또는 액세스 토큰을 입력합니다. 비밀번호 입력 시 화면에 글자가 안 나오는 것은 정상입니다.
```sh
cd hop
```
```sh
git branch --show-current
```
`main`이 나오면 다음으로 넘어갑니다.
## 2. NAS 설정 만들기
```sh
sh scripts/configure-nas.sh
```
질문이 나오면 순서대로 입력합니다.
1. **NAS 내부 IPv4 주소**: NAS 설정에 표시된 실제 내부 IP 입력 → Enter. NPM도 이 IP로 연결합니다.
2. **Resend API 키**: 기존 `hello@noname.company` 발신에 사용하는 키 붙여넣기 → Enter. 입력 문자는 숨겨집니다. 메일 기능 없이 시작하려면 Enter만 누릅니다.
3. **회원가입 활성화**: 가입까지 테스트하려면 `y` → Enter. 비활성화하려면 Enter만 누릅니다. 키를 생략하면 이 질문 없이 가입이 비활성화됩니다.
`SESSION_SECRET`과 `DISCOVERY_SECRET`은 자동 생성됩니다. NAS 포트와 컨테이너 포트는 모두 **50189**를 사용합니다.
마지막에 표시된 `compose.nas.yaml` 경로를 복사해 둡니다. `.env`는 NAS에만 남고 Git에 올라가지 않습니다. 기존 `.env`가 있으면 덮어쓰지 않습니다.
## 3. Docker 앱에서 배포
1. NAS의 **Docker 앱** 열기.
2. **프로젝트 → 생성** 누르기.
3. 프로젝트 이름에 **hop** 입력.
4. 프로젝트 저장 경로는 방금 `git clone`한 **hop 폴더** 선택.
5. YAML 가져오기에서 **compose.nas.yaml** 선택. 파일 가져오기가 없으면 해당 파일 내용을 Compose 편집기에 붙여넣습니다.
6. **배포 / 빌드 후 시작** 누르기. 처음에는 이미지 빌드가 끝날 때까지 기다립니다.
7. 프로젝트의 hop 컨테이너가 **실행 중**, 상태가 **healthy**인지 확인.
버전별로 버튼 이름이 다를 수 있습니다. 생성한 YAML은 소스와 `.env`의 절대 경로를 사용합니다. 별도의 빈 폴더로 소스를 옮기지 마세요. 프로젝트 배포 방식은 [UGREEN 공식 안내](https://ai.ugreen.com/blogs/knowledge/docker-docker-compose-ugreen-nas)를 참고했습니다.
## 4. Nginx Proxy Manager 설정
기존 NPM 관리 화면에서 진행합니다.
1. **Hosts → Proxy Hosts → Add Proxy Host** 누르기. 이미 hop. 항목이 있으면 **Edit**.
2. 다음 값 입력.
| 항목 | 입력값 |
| --------------------- | -------------------------------- |
| Domain Names | `hop.noname.company` |
| Scheme | `http` |
| Forward Hostname / IP | 2단계에서 입력한 **NAS 내부 IP** |
| Forward Port | `50189` |
| Cache Assets | 끄기 |
| Websockets Support | 켜기 |
3. SSH에서 다음 명령을 실행합니다. 사용자 NAS의 실제 `cloudflared-tunnel` 네트워크 주소로 hop. 전용 설정을 만듭니다.
```sh
sh scripts/print-hop-proxy-config.sh > hop.proxy.conf
```
```sh
cat hop.proxy.conf
```
4. 나온 내용을 **hop.noname.company의 Advanced**에 붙여넣습니다. 기존의 같은 세 항목은 출력된 내용으로 교체합니다. 다른 도메인의 설정은 바꾸지 않습니다.
5. **Save** 누르기.
이 설정은 Tunnel이 연결하는 Docker 주소를 허용하고 외부에서 원본 IP 헤더를 흉내 내어 들어오는 경로를 차단합니다. 실제 전달 헤더는 [Cloudflare의 CF-Connecting-IP](https://developers.cloudflare.com/fundamentals/reference/http-headers/)를 사용합니다. `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은 [공식 문서](https://nginxproxymanager.com/guide/)의 도메인별 전달 기능을 사용합니다.
## 5. Cloudflare Tunnel에 주소 연결
1. 기존 터널의 **Published application routes / Public Hostnames** 열기.
2. `hop.noname.company`가 기존 NPM으로 전달되는지 확인.
3. 주소가 없다면 `hop.noname.company` 추가.
4. Service는 **다른 서비스에서 이미 사용 중인 NPM의 8080 주소** 그대로 입력.
5. 저장.
8080을 50189로 바꾸지 않습니다. Tunnel은 NPM으로, NPM의 hop. 항목은 50189로 전달합니다.
## 5-1. HTTP를 HTTPS로 자동 이동
1. Cloudflare → `noname.company` → **Rules → Redirect Rules → Create rule**.
2. 이름: `hop HTTPS`.
3. **Custom filter expression**에 입력:
```text
(http.host eq "hop.noname.company" and http.request.scheme eq "http")
```
4. URL redirect 타입: **Dynamic**.
5. Target URL expression:
```text
concat("https://hop.noname.company", http.request.uri.path)
```
6. 상태 코드 **301**, **Preserve query string** 켜기 → **Deploy**.
7. `http://hop.noname.company`를 열어 주소창이 `https://hop.noname.company`로 바뀌는지 확인.
hop.에만 적용합니다. Tunnel → NPM 구간을 HTTP로 사용하는 기존 설정은 유지합니다. [Cloudflare 공식 리디렉션 설정](https://developers.cloudflare.com/rules/url-forwarding/single-redirects/create-dashboard/).
## 5-2. 주변 자동 발견 활성화
새 버전의 컨테이너가 실행 중이고 4단계 NPM 설정을 저장한 뒤, NAS의 hop 폴더에서 실행합니다.
```sh
sh scripts/enable-nas-discovery.sh
```
이 명령은 **현재 운영 주소를 통한 실제 요청**으로 인접 프록시와 Cloudflare 원본 IP 전달을 확인하고, 그 프록시 IP만 `.env`에 지정합니다. 운영 진단 토큰은 설정 스크립트가 생성하며 화면이나 Git에 공개하지 않습니다.
1. 명령이 성공하면 Docker 앱 → **프로젝트 → hop → 편집**.
2. **compose.nas.yaml** 다시 불러오기 → 저장.
3. **재배포**하여 컨테이너를 새 환경설정으로 다시 생성. 단순 재시작은 환경값을 다시 읽지 않습니다.
4. PC와 휴대폰을 같은 Wi-Fi에 연결하고 `https://hop.noname.company`를 열기.
5. QR 없이 상대 기기가 표시되는지 확인.
6. 휴대폰 Wi-Fi를 끄고 모바일 데이터로 전환 → 다른 네트워크 기기가 목록에서 사라지는지 확인.
확인 중 오류가 나면 자동 발견 설정을 임의로 넓히지 마세요. 오류와 Docker 로그를 알려주세요. 주변 목록은 같은 **공인 IP** 기준이며 Bluetooth 탐색은 아닙니다. IPv6 주소가 서로 다르거나 VPN을 사용하면 같은 Wi-Fi에서도 자동 목록이 다를 수 있습니다.
NPM 또는 Tunnel 컨테이너를 재생성해 Docker IP가 바뀌었다면 4단계 설정을 다시 출력하여 저장한 뒤 5-2단계를 다시 실행합니다.
## 6. 동작 확인
브라우저에서 다음 주소를 엽니다.
```text
https://hop.noname.company
```
1. PC와 휴대폰을 같은 Wi-Fi에서 열어 주변 기기가 자동으로 보이는지 확인.
2. 다른 네트워크의 기기는 **QR로 연결**을 눌러 연결 공간 만들기.
3. 해당 기기에서 QR을 스캔하거나 연결 코드 입력.
4. 참여 후 QR 모달이 닫히고 상대 기기가 보이는지 확인.
5. 상대 기기 선택 → 링크 전송 → 상대에서 수락 → 링크가 열리는지 확인.
6. 작은 파일 전송 → 상대에서 수락 → 다운로드 확인.
7. 가입을 활성화했다면 **로그인 → 회원가입**에서 실제 이메일로 가입 → Resend 인증 메일 확인 → 로그인.
8. 로그인 상태로 받은 자료가 **보관함 → 받은 항목**에 표시되는지 확인.
주변 자동 발견은 **5-2단계를 실행해 활성화**합니다. 임의로 `TRUSTED_PROXY_CIDRS`에 모든 IP를 넣지 마세요.
운영 비밀번호는 **12자 이상**입니다. 개발 DB와 `1234` 테스트 계정은 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 |
| `MIN_FREE_DISK_MB=5120` | NAS에 최소 5GiB 여유 공간 확보 |
| `DEFAULT_FILE_RETENTION_DAYS=7` | 정상 저장한 파일 최대 7일 후 삭제 |
| `ORPHAN_UPLOAD_RETENTION_HOURS=24` | 중단·방치한 업로드 조각 정리 기준 24시간 |
비로그인은 내역 화면이 없습니다. 파일은 서버로 업로드한 뒤 상대가 다운로드하므로, 전송과 정리를 위한 임시 서버 파일은 필요합니다. 로그인한 개인 보관함과 익명 전체 용량은 별도입니다. 직접 보관함 파일을 삭제하거나 만료되면 개인 사용량에서 빠지며, 다른 사람에게 공유된 원본은 마지막 유효 접근권이 끝날 때 물리적으로 삭제됩니다.
개인 한도가 차도 **수신·다운로드는 계속**합니다. 새로 받은 파일은 받은 항목에 저장하지 않고 짧은 안내를 표시합니다. 직접 저장은 용량 부족 시 거절합니다. 링크·메모는 파일 용량 합계에 포함하지 않습니다. 기존 10GiB 기본값은 설정 스크립트가 2GiB로 바꾸며, 다른 값으로 조정한 설정과 기존 자료는 유지합니다.
**대용량 파일:** hop.은 파일을 **8MiB씩 분할**해 업로드합니다. Cloudflare의 Free/Pro **100MB**, Business **200MB** 제한은 **HTTP 요청 한 번의 크기**이므로 전체 파일을 그 크기로 제한하지 않습니다. 앱의 파일당 최대 **2GiB**와 계정·익명 저장소 제한은 유지합니다. 중간 요청 오류는 서버가 받은 위치부터 최대 3회 자동 재시도합니다. 재시도가 끝나면 현재 전송 화면의 ‘다시 전송’을 누릅니다. 브라우저를 재시작하거나 로그아웃한 뒤의 자동 복구는 보장하지 않습니다. 마지막 파일 내용 검증과 저장이 끝나야 수신 자료가 생성됩니다. [Cloudflare 업로드 제한](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/4xx-client-error/error-413/).
## 7. 다음 버전으로 업데이트
사용자가 확인한 NAS 경로에서 진행합니다.
```sh
cd /volume1/docker/projects/apps/hop
```
1. Docker 앱 → **프로젝트 → hop → 중지**.
2. SSH에서 데이터 백업:
```sh
mkdir -p backups
```
```sh
chmod 700 backups
```
```sh
sudo docker volume inspect hop_hop_database hop_hop_uploads >/dev/null
```
```sh
sudo docker run --rm -v hop_hop_database:/source/database:ro -v hop_hop_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'
```
```sh
cp -p .env "backups/env-$(date +%Y%m%d-%H%M%S)"
```
3. 최신 코드 받기:
```sh
git pull --ff-only origin main
```
4. 배포 YAML 다시 만들기. 기존 `.env`와 비밀값은 유지됩니다.
```sh
sh scripts/configure-nas.sh
```
5. Docker 앱 → **프로젝트 → hop → 편집**.
6. 최신 **compose.nas.yaml**을 다시 불러오거나 편집기에 붙여넣기 → 저장.
7. **재빌드 / 이미지 다시 빌드** 선택 → **시작 / 재배포**. 단순 재시작만 하면 바뀐 코드가 이미지에 반영되지 않습니다.
8. `healthy` 확인.
9. 이번 업데이트에서는 **4단계**의 hop. 전용 NPM 설정, **5-1단계** HTTPS 자동 이동, **5-2단계** 자동 발견 활성화를 순서대로 진행.
10. 브라우저 새로고침 → 6단계의 링크·100MB보다 큰 파일 전송 확인.
프로젝트를 삭제하거나 `docker compose down -v`를 실행하지 마세요. DB·파일은 `hop_hop_database`, `hop_hop_uploads` 볼륨에 남습니다. 업데이트 실패 시 중지하고 오류를 확인하세요. DB 마이그레이션이 적용됐을 수 있으므로 코드만 되돌리지 말고 같은 시점의 DB·업로드·환경파일 백업으로 함께 복구해야 합니다.
## 8. 시작 / 중지 / 로그
| 할 일 | 누를 위치 |
| -------------- | ----------------------------------------------------------------------- |
| 시작 | Docker → 프로젝트 → hop → 시작 |
| 중지 | Docker → 프로젝트 → hop → 중지 |
| 설정 변경 적용 | `.env` 수정 → YAML 재생성 → 프로젝트 편집 → YAML 다시 불러오기 → 재배포 |
| 오류 확인 | Docker → 컨테이너 → hop 컨테이너 → 로그 |
SSH에서 실제 설정 중 비밀값 없이 주소·포트만 확인하려면:
```sh
grep -E '^(APP_URL|HOP_BIND_IP|HOP_HOST_PORT|HOP_CONTAINER_PORT)=' .env
```
NAS에서 앱의 상태만 확인하려면:
```sh
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`를 사용하세요.
환경파일을 YAML에 포함하지 않는 생성 방식은 [Docker Compose 공식 문서](https://docs.docker.com/reference/cli/docker/compose/config/)의 `--no-env-resolution`을 사용합니다. `.env`, 생성된 `compose.nas.yaml`, `backups`는 Git과 Docker 빌드에서 제외됩니다.