hop.
기기 사이를 가볍게. 가입 없이 접속한 브라우저 사이에서 수락 후 자료를 전달하는 서비스입니다.
파일·클립보드(링크/텍스트) 전송, 로그인 중 수신 기록·7일 보관함과 PWA를 구현했습니다. 자동 검증 범위는 검증 기록, 클립보드 동작과 256KiB 제한은 클립보드 전송을 참고하세요. Resend 실제 메일 수신과 실기기 운영 경로는 별도로 확인합니다. 전체 설계는 구현 계획, 개발 기준은 SPEC.md와 이후 사용자 수정안을 따릅니다.
로컬 개발
Node.js 22.12 이상과 npm을 사용합니다.
npm ci
npm run build -w @hop/shared
npm run dev
웹과 API 모두 OS가 빈 임시 포트를 배정합니다. 프로젝트 루트 .env를 읽습니다. 개발 DB·파일은 기본 .data/를 사용하며 Docker의 /data/ 설정을 그대로 사용하지 않습니다. 출력된 개발 화면 주소를 열어 주세요. 서버 코드 변경은 자동으로 다시 시작하며 실행 중에는 배정된 포트를 유지합니다. 고정 샘플 포트나 기존 서비스 포트를 사용하지 않습니다. 데이터는 .data/hop.sqlite에 저장합니다. 동일 브라우저의 탭은 하나의 기기이므로 다른 기기를 테스트하려면 다른 브라우저·프로필·시크릿 창을 사용하세요.
검증
npm run typecheck
npm run build
npm test
API 테스트는 격리된 실제 SQLite DB와 임시 로컬 포트의 Socket.IO를 사용합니다. 테스트 환경이 로컬 소켓 연결을 제한하면 권한이 필요할 수 있습니다. 운영 프록시와 실기기 확인을 자동 테스트로 대신했다고 주장하지 않습니다.
Docker 배포 준비
유그린 NAS에서 Docker 앱 버튼으로 배포하는 순서는 deploy.md를 따라 진행하세요. 현재 배포 경로는 Cloudflare Tunnel → 기존 NPM(8080) → NAS의 hop.(50189)입니다.
.env.example을 .env로 복사하고 비밀값을 설정합니다. SESSION_SECRET과 DISCOVERY_SECRET은 서로 다른 32자 이상의 무작위 비밀값이어야 합니다. 예를 들어 openssl rand -hex 32로 각각 생성합니다. Git에 실제 환경 파일·DB·파일을 넣지 않습니다.
이번 NAS 배포는 사용자가 지정한 50189를 호스트·컨테이너 포트로 사용합니다. scripts/configure-nas.sh가 실제 NAS 내부 IP를 물어보고 운영 .env와 배포 YAML을 준비합니다. 다른 환경에서는 충돌 없는 포트·바인딩 IP·프록시 경로를 따로 확인하세요. Compose는 필수 값이 비어 있으면 실행을 거절합니다. .env는 Node 서버 직접 실행 시 자동 로드되지 않으며 Compose 또는 실행 환경이 주입합니다.
docker compose config
docker compose up -d --build
앱 시작 시 버전 관리된 Drizzle 마이그레이션을 적용하며 실패하면 시작하지 않습니다. DB와 업로드 볼륨은 재배포·재시작 후 유지됩니다. docker compose down -v는 데이터를 삭제하므로 사용하지 마세요.
Nginx Proxy Manager에는 실제 컨테이너/호스트 경로의 포트를 지정하고 HTTPS·WebSocket 지원을 켭니다. Secure 쿠키 때문에 운영 환경에서는 HTTPS가 필수입니다. 요청·연결 타임아웃과 업로드 제한은 Phase 2에서 문서화하고 실제 경로에서 검증합니다. 앱의 파일 상한은 중간 프록시·Cloudflare 상한을 보장하지 않습니다.
발견과 QR·코드 연결
같은 네트워크 탐색은 기본 비활성입니다. TRUSTED_PROXY_CIDRS는 실제로 앱에 연결하는 검증된 프록시 IP/CIDR만 허용합니다. 임의 전달 헤더를 신뢰하지 않습니다. 검증된 클라이언트 IP를 얻지 못하면 네트워크 탐색을 하지 않습니다. 공인 IP가 정확히 같은 경우만 후보로 표시하고 IPv6 /64 통합은 하지 않습니다. 같은 공인 IP는 동일 Wi-Fi나 실제 근접의 증명이 아닙니다.
QR·4자리 코드는 기본 10분의 임시 공간 초대입니다. 초대 코드 길이는 설정으로 6자리까지 확장할 수 있습니다. 최대 10대, 선택적 참여 승인, 내보내기와 공간 닫기를 지원합니다. 공간 참여와 자료 전송 수락은 별개입니다. QR 초대 토큰은 URL fragment에 넣어 서버 요청·리퍼러로 전달하지 않으며 참여 후 주소에서 지웁니다.
익명 자료
비로그인 화면에는 전송 내역을 표시하지 않습니다. 수락 후 링크는 새 탭으로 열고 파일은 다운로드합니다. 텍스트는 원문 미리보기와 클립보드 복사 버튼을 표시합니다. 익명 기기 세션은 기본 30일이며 저장한 자료는 최대 7일입니다. 쿠키 삭제·만료·초기화 시 이전 익명 접근권은 복구할 수 없습니다. 로그인 전 수신 자료를 계정으로 가져오지 않습니다. 기기 세션 정리가 필요하면 브라우저의 사이트 데이터를 삭제합니다. 기기 이름은 인증된 신원이 아니며 전송은 서버를 경유합니다. E2EE나 P2P 전송을 제공한다고 안내하지 않습니다.
업데이트와 백업
일반 코드·UI 업데이트는 git pull --ff-only origin main → Docker 이미지 재빌드 → 재배포로 진행합니다. Docker 구성과 환경변수가 바뀌지 않았다면 기존 YAML을 그대로 사용합니다. DB·업로드는 기존 볼륨에 유지되므로 매번 백업할 필요는 없습니다. DB 구조 변경에 대비하거나 데이터를 따로 보관하려면 앱을 정지한 뒤 DB 볼륨(WAL·SHM 포함), 업로드 볼륨, .env를 같은 시점에 백업합니다. 자세한 순서는 deploy.md의 업데이트 절차를 참고하세요.
파일당 기본 2GiB, 익명 주체당 2GiB, 전체 익명 20GiB 제한과 최소 5GiB 여유 공간을 적용합니다. MB/KB 환경변수는 MiB/KiB 기준입니다. 파일은 수락 후 스트리밍 저장하고 중단 시 임시 파일·예약을 회수합니다. 파일은 8MiB 단위로 나누어 전송하고 서버가 받은 위치를 기록합니다. 중간 오류는 최대 3회 자동 재시도하며, 현재 화면에서 같은 파일로 ‘다시 전송’할 수 있습니다. 백그라운드 완료는 보장하지 않습니다.
개발 환경의 QR
기본 개발 URL은 127.0.0.1입니다. 이 URL을 휴대폰에서 열면 휴대폰 자신을 가리키므로 PC 개발 서버에 접속할 수 없습니다. 같은 PC의 다른 브라우저·시크릿 창에서는 코드를 입력해 연결할 수 있습니다. 휴대폰 검증을 시작할 때 사용할 LAN 접속 주소·포트·바인딩 범위를 사용자에게 확인합니다. HTTPS 운영 배포에서는 공개 서비스 주소로 QR을 생성합니다. 개발 HTTP 환경에서는 플랫폼에 따라 클립보드 등 보안 컨텍스트 API가 제한될 수 있습니다.
이메일 발송
2026-10-08 사용자 지침에 따라 명세의 SMTP 대신 기존 Resend 연동을 사용합니다. 발신 주소는 hello@noname.company입니다. Resend HTTPS API 발송 어댑터를 연결했습니다. 루트 .env의 RESEND_API_KEY와 MAIL_FROM을 서버에서 읽으며 API 키는 브라우저로 전달하지 않습니다. 공개 가입은 기본 닫혀 있으며, REGISTRATION_MODE=open으로 시작하려면 사용 가능한 메일 어댑터가 필요합니다. API 키는 Git에 저장하지 않습니다.
계정 메일 토큰은 일회용·해시 저장이며 링크 fragment를 사용합니다. 인증 전 계정 업로드·원격 전송·수락을 금지합니다. 이메일 정규화는 양끝 공백 제거와 도메인 소문자화이며 로컬 파트 대소문자·점·별칭은 보존합니다. ‘내 계정에 저장’은 기존 익명 접근권을 제거하고 계정 보관함으로 이동하며 원래 만료와 상대 접근권을 유지합니다.
최초 관리자 초기화는 SETUP_TOKEN(32자 이상 무작위 비밀값)과 빈 사용자 DB 조건으로만 가능합니다. /api/auth/setup도 이메일 인증을 거치며, 실제 메일 수신 검증 후 실행 절차를 완성합니다.
기술 참고: Fastify 프록시 설정, Drizzle SQLite, Tailwind Vite.
현재 전송 UX
파일·링크 통합 수정안을 우선합니다. 일반 텍스트 전송과 유형 탭은 제거했습니다. 기기를 누르면 클립보드 URL을 제안하며, 권한 거부나 일반 텍스트는 무시하고 파일 선택을 유지합니다. 링크 직접 붙여넣기도 가능합니다. 읽기만으로 전송하지 않으며 수락 후에만 전달합니다. 수신과 완료 카드는 뷰포트 상단에 표시됩니다. 기본 테마는 검정이며 라이트·시스템 전환을 유지합니다.
개발 테스트 계정
node --import tsx scripts/seed-test-accounts.mjs로 개발 DB에 인증된 일반 사용자 test1@hop.test, test2@hop.test를 준비합니다. 비밀번호는 둘 다 1234입니다. 메일을 보내지 않으며 운영 환경에서는 실행을 거절합니다. 같은 이름의 테스트 계정은 비밀번호만 재설정하고 다른 기존 계정은 변경하지 않습니다.
개발·테스트 환경은 1자 이상 비밀번호를 허용합니다. 운영 환경(NODE_ENV=production)은 기존 12자 이상 규칙을 유지하며 가입·재설정 화면도 서버가 제공한 최소 길이를 적용합니다.
현재 전송·보관함 흐름
보관함은 받은 항목으로 먼저 열립니다. 계정 설정에서 이메일·비밀번호별 변경 버튼을 눌러 입력 화면으로 이동하며, 모바일에서는 하단 시트로 표시합니다. 기기 설정에서는 이름만 변경합니다. 사이트에 접속하면 자동 발견에 참여하며, 이전 버전에서 발견을 꺼둔 기기도 다시 접속하면 켜집니다. 파일의 실제 저장 위치·다운로드 실패·만료·관리자 접근은 deploy.md의 파일 저장 안내에 정리했습니다.
최신 동작 기준은 TEMPORARY_VAULT.md입니다. 중앙에서 기기를 고른 뒤 PC 모달·모바일 하단 시트로 링크·파일·임시 저장 자료를 보냅니다. 로그인 중 받은 자료는 보관함의 받은 항목에 날짜별로 남고, 직접 담은 자료는 임시 저장에서 한 줄 메모와 함께 관리합니다. 모든 자료는 최대 7일입니다. 저장 종류는 먼저 링크/파일 중 하나를 선택합니다. 이메일 원격 전송과 영구 즐겨찾기는 제거했습니다.
개발 실행은 같은 PC의 다른 브라우저를 자동 발견하고 반복 검증용 요청 한도를 사용합니다. QR 방에 들어가면 방 안의 기기만 보이며 한 방만 참여할 수 있습니다. 방장 나가기는 방 전체를 닫습니다. 운영 발견은 실제 프록시 경로를 검증한 후 켭니다.
npm run test:e2e:layout은 실제 12개 기기 연결과 4/3/2열, 선택 전 입력 없음, 중앙 모달/하단 시트 및 방 격리를 검증합니다.
NAS 업데이트·PWA·개인 용량
이번 NAS 업데이트는 deploy.md의 7단계부터 진행합니다. 운영 자동 발견은 실제 Tunnel → NPM → hop. 경로를 진단해 인접 프록시만 신뢰하며, Cloudflare의 원본 IP로 같은 네트워크 기기를 구분합니다. 공개 주소는 HTTPS를 사용합니다. 상단 더보기의 앱 설치 메뉴로 PWA 설치 또는 iPhone 홈 화면 추가를 안내합니다. 파일·계정 기록·API 응답은 오프라인 캐시에 넣지 않습니다.
로그인 계정의 임시 저장과 받은 파일을 합쳐 개인별 2GiB로 제한합니다. 같은 원본은 한 계정에서 한 번 계산하고 다른 계정의 한도와 분리합니다. 한도가 차면 파일 수신·다운로드는 계속하고 받은 항목에 저장하지 않았다는 안내를 표시합니다. 익명 업로드는 기기 세션당 2GiB, 서버 전체 20GiB이며 화면에는 내역을 표시하지 않습니다.
근처 위치 검색과 기종 구분은 기기 발견 문서를 참고하세요. 양쪽 기기가 위치를 허용하면 약 100m 내 기기를 표시하며, 대기 화면은 반복 목록 검색을 하지 않습니다.
로고 원본은 apps/web/public/logo.svg, 아이콘 원본은 apps/web/public/icon.svg입니다. 헤더·서비스 정보 로고는 SVG 경로를 사용하므로 기기 기본 폰트에 영향을 받지 않습니다. 흰 글자는 테마에 맞춰 변경하고 청록색 점은 유지합니다. 아이콘 원본을 교체한 뒤 node scripts/generate-icons.mjs를 실행하면 192px·512px·Android maskable PNG가 다시 생성됩니다.
기기 이름은 서버 DB에 저장되며 동일 브라우저의 기기 세션 쿠키가 유효한 동안 유지됩니다. 쿠키를 지우거나 다른 브라우저로 접속하면 새 기기로 인식됩니다.