# 파일 공유 (FileShare) — `dev.tictechtoeai.com/share` 정적 Astro 홈페이지에 **비밀번호로 보호되는 파일 업로드/다운로드 드롭**을 추가한 기능입니다. 링크와 공유 비밀번호를 아는 사람끼리 파일을 주고받을 수 있습니다. ## 접속 두 개의 페이지 / 두 개의 접근 등급으로 나뉩니다. | 페이지 | 주소 | 접근 | 기능 | | --- | --- | --- | --- | | **업로드(관리)** | https://dev.tictechtoeai.com/share | **비밀번호 필요 (나만)** | 업로드·목록·다운로드·삭제·링크 복사 | | **다운로드(공개)** | https://dev.tictechtoeai.com/download | **비밀번호 없음 (누구나)** | 파일 목록 표시 → 클릭 시 다운로드 | **정책: 업로드는 나만, 목록·다운로드는 누구나.** 공개 페이지 `/download` 에 올려진 파일이 전부 목록으로 보이고, 행을 클릭하면 바로 내려받습니다. 올리기·삭제만 비밀번호가 필요합니다. | 항목 | 값 | | --- | --- | | API (프록시) | `https://dev.tictechtoeai.com/share/api/*` → `127.0.0.1:8090` | | 헤더 네비 | `Files` 링크(→ `/download`) 노출. 업로드 페이지 `/share` 는 네비 미노출(관리자 북마크용) | | 접근 모델 | **쓰기(업로드·삭제)는 토큰 필요**, **읽기(목록·다운로드)는 공개** | ### 파일을 남에게 주는 방법 - 그냥 **`/download` 주소**를 알려주면 됩니다 — 상대가 목록에서 골라 받습니다. - 특정 파일 하나만 콕 집어 주려면, `/share` 에서 그 파일의 **`링크 복사`** 버튼으로 개별 링크(`/download?f=<파일명>`)를 떠서 전달할 수도 있습니다. > `/share` 는 처음 들어가면 비밀번호 입력창이 뜹니다. 한 번 입력하면 브라우저 > `localStorage` 에 저장되어 다음에 자동 잠금 해제됩니다. `잠금` 버튼으로 지울 수 있습니다. ## 구성 요소 ``` brower ──HTTPS──> Caddy (dev.tictechtoeai.com) ├─ /share/api/* ──reverse_proxy──> Node FileShare :8090 ─┐ └─ /share (그 외) ──file_server──> homepage/dist │ fileshare/storage/ (실제 파일) ``` - **프런트**: `homepage/src/pages/share.astro` — 정적 페이지 + 인라인 바닐라 JS(드래그앤드롭 업로드, 진행률, 목록, 다운로드, 삭제). 사이트 디자인 토큰 재사용. - **백엔드**: `fileshare/server.mjs` — 의존성 0개 Node HTTP 서버. `127.0.0.1:8090` 만 바인딩(외부 직접 노출 안 함, Caddy 통해서만). - **엣지**: 루트 `Caddyfile` 의 `dev.tictechtoeai.com` 블록에 `handle /share/api/*` 리버스 프록시 추가. - **서비스**: `LivekitDevFileShare` (NSSM, 자동 시작 + 장애 복구). ## 백엔드 API 모든 경로 접두사는 `/share/api`. 인증은 다음 중 하나: - 헤더 `x-share-token: <토큰>` - 헤더 `Authorization: Bearer <토큰>` - 쿼리 `?token=<토큰>` (다운로드 전용 — `` 링크로 스트리밍 받기 위함) | 메서드 | 경로 | 인증 | 설명 | | --- | --- | --- | --- | | GET | `/share/api/health` | 불필요 | `{"ok":true}` (헬스체크) | | GET | `/share/api/list` | **필요** | `{files:[{name,size,mtime}], maxMb}` (최신순) — 관리 페이지용 | | POST | `/share/api/upload?name=<파일명>` | **필요** | 요청 본문 = 파일 원본 바이트. 스트리밍 저장 | | GET/HEAD | `/share/api/download?name=<파일명>` | **필요** | 파일 스트리밍 (`attachment`; `&inline=1` 이면 미리보기) | | POST/DELETE | `/share/api/delete?name=<파일명>` | **필요** | 파일 삭제 | | GET | `/share/api/public/list` | 불필요 | 공개 목록 — `/download` 페이지가 사용 | | GET/HEAD | `/share/api/public/download?name=<파일명>` | 불필요 | 공개 다운로드 (비밀번호 없이 스트리밍). HEAD = 존재·크기 확인 | > 공개 등급에는 **읽기 전용** 엔드포인트(목록·다운로드)만 있습니다. 업로드/삭제 공개 경로는 > 없으므로(`/share/api/public/upload` 는 404), 남들은 파일을 올리거나 지울 수 없습니다. ### 안전장치 - **파일명 정규화**: 경로 구분자·제어문자·Windows 금지문자 제거, 디렉터리 탈출(`..`) 차단, basename 만 사용, 180자 제한. - **충돌 회피**: 같은 이름 업로드 시 `이름 (1).ext`, `이름 (2).ext` … 로 자동 분기 (덮어쓰지 않음). - **부분 파일**: `.part-xxxx` 임시 파일로 받은 뒤 완료 시 `rename` (중단된 업로드가 목록에 남지 않음). - **크기 제한**: 기본 512 MB/파일. `Content-Length` 사전 검사 + 스트림 초과 시 중단(413). - **상수시간 토큰 비교**: 타이밍 공격 완화(`crypto.timingSafeEqual`). ## 설정 — `fileshare/config.json` ```json { "token": "N0Il2HIWJYBywt1ncE4c", "port": 8090, "dir": "storage", "maxMb": 512 } ``` - 이 파일은 `.gitignore` 대상입니다(토큰·저장 파일 비공개). 예시는 `config.example.json`. - 환경변수 `SHARE_TOKEN` / `SHARE_PORT` / `SHARE_DIR` / `SHARE_MAX_MB` 로 덮어쓸 수 있습니다. - 토큰이 어디에도 없으면 첫 실행 시 랜덤 토큰을 만들어 `config.json` 에 기록합니다. ### 비밀번호(토큰) 바꾸기 ```powershell # 1) 새 토큰 생성 예시 node -e "console.log(require('crypto').randomBytes(15).toString('base64url'))" # 2) fileshare/config.json 의 "token" 값을 교체 # 3) 서비스 재시작 Restart-Service LivekitDevFileShare ``` ## 운영 ```powershell # 상태 Get-Service LivekitDevFileShare curl.exe http://127.0.0.1:8090/share/api/health # 재시작 / 로그 Restart-Service LivekitDevFileShare Get-Content C:\Developments\LivekitDev\logs\LivekitDevFileShare-stdout.log -Tail 20 # 저장된 파일 위치 # C:\Developments\LivekitDev\fileshare\storage\ ``` - 서비스는 `ensure_stack.ps1`(5분 주기 + 부팅 시 헬스 태스크)에 포함되어 죽으면 자동 복구됩니다. - 최초 서비스 등록(관리자 PowerShell 1회): `scripts\install_fileshare_service.ps1`. ## 접근 제어를 더 바꾸고 싶다면 현재 정책 = **업로드·삭제는 토큰(나만), 목록·다운로드는 공개**. 조정 방향: - **다운로드도 비밀번호로 막기**: `/download` 페이지와 `/share/api/public/*` 라우트를 제거하고 `/share` 만 쓰면 전면 보호 모델로 돌아갑니다. - **목록은 숨기고 링크로만 받게**: `server.mjs` 에서 `public/list` 라우트를 빼고 `/download` 를 `?f=<파일명>` 단일 파일 방식으로 바꾸면 됩니다. - **IP 제한**: Caddy `handle /share/api/*` 블록에 `@allowed remote_ip ...` 매처 추가. ## 변경된/추가된 파일 ``` fileshare/server.mjs # 백엔드 (신규) fileshare/package.json # (신규) fileshare/config.json # 토큰/포트 (신규, gitignore) fileshare/config.example.json # (신규) fileshare/.gitignore # (신규) fileshare/storage/ # 업로드 저장소 (신규) homepage/src/pages/share.astro # 업로드(관리) 페이지 — 비밀번호 (신규) homepage/src/pages/download.astro # 다운로드(공개) 페이지 — 목록 표시 + 클릭 다운로드 (신규) homepage/src/config/site.ts # 네비에 Files(다운로드) 링크 (수정) Caddyfile # /share/api/* 프록시 (수정) scripts/install_fileshare_service.ps1 # 서비스 등록 (신규) scripts/ensure_stack.ps1 # 헬스 대상에 추가 (수정) ```