파일 보관 데스크 (FileStore 운영 표면)
2026-10 변경 — 관리자 메뉴 탭 제거. 콘솔 관리자 메뉴의 파일 보관 탭은 없어졌다. 저장 라우팅·백엔드·자격증명의 원천은 FileStore 자체
/admin이고, 콘솔은 등록된file-store포털 주소를 셸 레일 하단 바로가기로 새 창에서 넘긴다(docs/portal-console-admin-menu.md§관리자 셸의 부가 진입점). 이 문서는 콘솔 서버 경로(server/fileStore*)와 화면 자산(FileStoreDesk) 의 기록으로 남긴다 — 되살릴 때는DirectoryAdmin.tsx의AdminTab·tabs·tabMessageKeys에file-store를 다시 넣으면 된다.
포털별 업무 파일이 어디에 어떻게 보관되는지를 콘솔 관리자 메뉴 파일 보관에서 관리한다. 저장 라우팅의 소유자는 FileStore이고(UiPlatform docs/FILE_STORE_SCOPE.md), 콘솔은 서버 간 호출로 프록시하는 운영 표면이다. 이 문서는 화면 구성·환경변수·진단 항목·위험 작업을 정리한다.
1. 경계 — 누가 무엇을 소유하는가
| 콘솔이 하는 것 | 콘솔이 하지 않는 것 |
|---|---|
| 백엔드 목록·등록·수정·비활성, 백엔드별 자격증명 입력·회전·삭제(값은 표시하지 않음) | 자격증명을 콘솔 DB·브라우저·감사 로그에 저장·표시 ❌ |
| 포털(소비자)별 정책 등록·수정·삭제 | 파일 본문·버킷 소유 ❌ |
저장 I/O 검증(validate)·R2 연결 실측·불일치 진단 | FileStore 계정·권한(admin:manage)·정리(cleanup) ❌ |
- 브라우저는 FileStore에 직접 붙지 않는다 — 주소·토큰은 콘솔 서버만 알고, 화면에는 설정 여부만 내려간다.
- 화면에서 입력한 자격증명은 콘솔 서버를 지나 FileStore로 전달될 뿐 콘솔 DB·응답·로그에 남지 않는다 — FileStore가 백엔드별로 봉투 암호화해 보관한다(멀티 백엔드: 백엔드마다 다른 값이 정상).
- FileStore
/admin화면은 계속 유지한다.requireAdminPermission이 걸린 라우트(계정·cleanup)는 로컬 관리자 세션만 허용하므로 콘솔이 대신할 수 없다.
2. 콘솔 서버 환경변수
| 이름 | 용도 | 없을 때 화면 동작 |
|---|---|---|
FILE_STORE_BASE_URL | 서버 간 호출 주소(https, 로컬만 http) | "미설정" 안내 — 목록·등록·진단 불가 |
FILE_STORE_ADMIN_TOKEN | 백엔드·정책 등록·수정·검증·진단 (x-filestore-admin-token) | 목록 조회 불가 |
FILE_STORE_SERVICE_TOKEN | 포털 어댑터가 실제로 받는 목록 교차 확인(Authorization: Bearer) | 교차 확인만 생략(나머지 진단은 그대로) |
이전 이름(ASSETSTORE_*)은 개명으로 제거되었다(2026-09-29) — 표준 FILE_STORE_*만 쓴다. 나머지 구 이름(DIRECTORY_SERVICE_*·UI_CENTER_*·RAG_CENTER_*)은 server/envAliases.ts가 새 이름으로 매핑한다.
자격증명(Access Key·Secret Key)은 이 목록에 없다 — 화면 입력 → FileStore가 백엔드별 봉투 암호화 저장이 기본이고, secretRef(FileStore 서버 환경변수 접두)는 대체 경로다.
3. 화면 탭
저장 백엔드
| provider | 필요한 값 | 검증 |
|---|---|---|
CLOUDFLARE_R2 | endpoint · bucket · secretRef | validate(secret 해석) + test-connection(HeadBucket 실측) |
AMAZON_S3 | bucket · secretRef · region | validate |
S3_COMPATIBLE | endpoint · bucket · secretRef | validate |
LOCAL_VOLUME | localRoot | validate(root 읽기·쓰기 권한) |
객체 저장소(R2·S3)는 자격증명이 함께 필요하다 — 등록·수정 폼의 Access Key ID·Secret Access Key(비우면 저장된 값 유지, 입력하면 교체 = 회전)와 secret 이름(환경변수 접두, 선택) 중 하나가 반드시 있어야 저장된다. 검증·연결 실측(R2)은 폼에 입력한 값(비어 있으면 저장된 값) 으로 실행되므로 저장 전에 실측할 수 있고, 카드에는 자격증명 설정됨/미설정 배지와 자격증명 삭제가 함께 보인다.
등록 폼에는 백엔드 ID 필드가 없다 — ID 는 FileStore 가 표시 이름에서 채번한다(로마자 slug → provider → 중복이면 -2). 저장은 POST /admin/backends 이고, 수정은 기존 ID 를 경로로 쓰는 PUT 이라 ID 를 손으로 적어 기존 백엔드를 덮어쓰는 사고가 없다.
목록의 연결 테스트 열은 마지막 검증·연결 테스트 결과를 보여 준다 — 통과 · 시각, 실패, 미실행, 재시험 필요(자격증명·경로를 바꾼 뒤). 값은 저장하지 않고 지문만 남기므로 회전하면 자동으로 재시험 필요가 되고, 카드 배지에도 같은 상태가 보인다. 진단 탭은 이 결과를 CREDENTIALS_UNVERIFIED(경고)·CREDENTIALS_TEST_FAILED(오류)로 함께 알린다 — "설정됨"과 "실제로 붙는다"는 다르기 때문이다.
비활성화는 삭제가 아니라 enabled=false로 한다. 삭제는 그 백엔드를 쓰는 정책이 하나도 없을 때만 허용된다(FileStore가 거부하면 사유를 그대로 보여 준다).
포털별 정책
| 필드 | 의미 |
|---|---|
consumerId | FileStore 소비자 ID = 콘솔의 포털 ID |
backendId | 이 포털이 쓸 저장 백엔드 |
pathPrefix | 객체 키 접두 — 실제 키는 {접두}/{포털}/{자산ID}/{파일명} |
maxBytes · allowedContentTypes | 업로드 허용 크기·형식(HTML·JavaScript·SVG는 FileStore가 거부) |
retentionDays | 보존 기한(비우면 무기한) |
encryptionRequired · readEnabled · writeEnabled · enabled | 암호화 요구·읽기·쓰기·정책 사용 |
ragPublishAllowed | 색인 발행 허용 — 공유 경로 규약이 정해지기 전에는 끈 상태로 둔다 |
정책을 만들면 나온 정책 ID(uuid)를 '등록포털 관리'의 첨부파일 연결(매핑 ID)에 등록해야 포털 업로드가 그 정책을 쓴다. 등록하는 곳은 포털 연동 설정(/admin?tab=settings)의 첨부파일 보관 (FileStore) 구간이다 — FileStore 사용을 켜고 소비자 ID(consumerId)·FileStore 주소·매핑 ID(정책 ID)·서비스 토큰을 넣는다. 켜지 않은 포털은 이 데스크에서 보관 미사용으로 표시된다.
진단
콘솔이 저장한 포털 연결과 FileStore의 실제 정책·백엔드를 맞춰 본다.
| 코드 | 심각도 | 뜻 |
|---|---|---|
MISSING_CONSUMER · MISSING_MAPPING | 오류 | 포털 설정에 소비자 ID·매핑 ID가 없다 |
MISSING_POLICY | 오류 | 그 ID의 정책이 FileStore에 없다 |
POLICY_CONSUMER_MISMATCH | 오류 | 매핑 ID가 다른 포털 정책을 가리킨다 |
MISSING_BACKEND | 오류 | 정책이 없는 백엔드를 가리킨다 |
MISSING_CREDENTIALS | 오류 | 객체 저장 백엔드에서 쓸 자격증명이 없다 — 관리자 등록도 없고 환경변수도 해석되지 않음(secretRef 이름만 있는 상태 포함, 메시지에 비어 있는 환경변수 이름을 함께 보여 준다) |
MISSING_OAUTH_REFRESH_TOKEN | 오류 | OAuth 백엔드(구글드라이브·원드라이브)에 refresh token 이 없다 — client 자격증명만으로는 업로드가 안 된다 |
CREDENTIALS_UNVERIFIED | 경고 | 자격증명(또는 마운트 경로)은 해석되는데 검증·연결 테스트 이력이 없거나, 값이 바뀐 뒤 재시험되지 않았다 |
CREDENTIALS_TEST_FAILED | 오류 | 마지막 연결 테스트가 실패했다 — 그 키로는 업로드가 되지 않는다 |
POLICY_DISABLED · BACKEND_DISABLED | 경고 | 정책 또는 백엔드가 비활성 — 저장 I/O 거부 |
MISSING_SERVICE_TOKEN | 경고 | 포털에 전달할 서비스 토큰이 콘솔에 없다 |
BASE_URL_MISMATCH | 경고 | 포털에 등록된 FileStore 주소가 콘솔 서버 설정과 다르다 |
UNUSED_BINDING | 경고 | 보관을 껐는데 포털 설정에 값이 남아 있다 |
어느 포털에도 연결되지 않은 FileStore 정책은 따로 목록으로 보여 준다(삭제하거나 포털 설정에 ID를 등록).
4. 자격증명 규칙 — 화면 입력이 기본, 환경변수는 대체 경로
기본은 화면 입력이다. FileStore 관리자 화면이나 이 데스크의 등록·수정 폼에서 값(객체 저장소는 Access Key ID·Secret Access Key, OAuth 저장소는 Client ID·Client Secret(+OneDrive Tenant ID))을 넣으면 FileStore가 백엔드별로 봉투 암호화(FILE_STORE_ENVELOPE_MASTER_KEY, AES-256-GCM)해 저장하고, 응답·로그·감사 기록에는 값이 남지 않는다. 화면에는 설정됨/미설정만 보이고, 비우고 저장하면 기존 값이 유지되며, 자격증명 삭제로 지울 수 있다.
secret 이름(예: FILE_STORE_R2_PORTAL)은 환경변수 경로다 — FileStore 서버가 다음을 읽는다(provider 별 접미사가 다르다).
# 객체 저장소
FILE_STORE_R2_PORTAL_ACCESS_KEY_ID=<...>
FILE_STORE_R2_PORTAL_SECRET_ACCESS_KEY=<...>
# OAuth 저장소(구글드라이브·원드라이브)
DRIVE_MAIN_CLIENT_ID=<...>
DRIVE_MAIN_CLIENT_SECRET=<...>
DRIVE_MAIN_TENANT_ID=<...> # OneDrive 전용(선택, 비우면 common)
DRIVE_MAIN_REFRESH_TOKEN=<...> # 이 데스크의 '수정' 또는 FileStore /admin 에서 UI로도 등록한다(봉투 암호화 보관)해석 우선순위는 화면 등록 값 → 환경변수 → 오류다. FileStore는 화면에 이 상태를 그대로 알려 준다 — credentialsConfigured(화면 등록 있음), secretRefConfigured(실제로 해석 가능: 화면 등록 또는 환경변수 성공), 해석 불가일 때 missingCredentialNames(비어 있는 환경변수 이름). 그래서 secretRef 이름만 남고 환경변수가 빈 상태(설정된 것처럼 보이지만 첫 업로드가 실패하는 유형)도 진단 탭에서 MISSING_CREDENTIALS로 잡힌다. 백엔드가 여러 개면(포털마다 다른 백엔드) 환경변수는 백엔드 수만큼 필요하고 재기동도 필요하므로 화면 입력을 쓰고, 단일 백엔드 배포는 지금처럼 env만으로도 동작한다. 어느 경로든 자격증명 값은 콘솔 DB·브라우저·로그에 남지 않는다.
5. 위험 작업
- 로컬 볼륨: FileStore 컨테이너에
localRoot가 마운트되어 있지 않으면 재배포·재기동 시 파일이 사라진다(문서 라이브러리에서 실제로 발생 —DOCUMENT_LIBRARY_STORAGE.md). 화면은 해당 백엔드가 있으면 항상 경고를 띄운다. - 백엔드 삭제: 사용 중인 정책이 있으면 FileStore가 거부한다. 화면은 백엔드마다 사용 중인 포털 수를 보여 준다.
- 정책 쓰기 차단/비활성: 이 포털의 업로드가 거부된다.
- 색인 발행 허용: RAG 발행 경로(FileStore → RagService)가 확정되기 전에는 켜지 않는다(
FILE_STORE_SCOPE.md§6).
6. 코드 위치와 테스트
| 파일 | 역할 |
|---|---|
server/fileStore.ts | 접속 설정·관리 API 호출·진단 대조(순수 함수)·연결 실패 원인 문장화(fileStoreNetworkHint) |
FileStore/src/backendCredentialStore.ts | 백엔드별 자격증명 저장(봉투 암호화, Memory/File/Postgres) · 해석은 FileStore/src/storage.ts의 resolveObjectStoreCredentials(화면 등록 → env 순) |
FileStore/src/backendCheckStore.ts | 마지막 검증·연결 테스트 결과(지문만 저장) — 회전 시 stale 로 바뀌어 "재시험 필요"가 된다 |
server/routers.ts fileStore | 프록시 프로시저(status·state·등록·검증·진단) |
client/src/components/FileStoreDesk.tsx | 화면(탭 3개) |
client/src/styles/fileStoreDesk.css | 화면 스타일(라이트·다크) |
server/fileStore.test.ts | 접속 설정·호출(토큰 경로·204·오류 매핑·허용 4xx)·진단 대조 |
pnpm test # vitest (서버 단위 테스트 포함)
pnpm check # tsc --noEmit7. 연결 실패 문구 읽는 법
콘솔은 FileStore에 닿지 못한 이유를 원인 코드로 바꿔 보여 준다(예전에는 fetch failed만 보여 원인을 구분할 수 없었다).
| 화면 문구(요약) | 뜻 | 조치 |
|---|---|---|
TLS 인증서를 신뢰할 수 없습니다 … (원인: DEPTH_ZERO_SELF_SIGNED_CERT) | 자체 서명 인증서이거나 중간 인증서 누락 | 정식 인증서(예: Let's Encrypt)로 교체. 유지해야 하면 콘솔에 그 CA를 마운트하고 NODE_EXTRA_CA_CERTS 지정 |
주소를 찾지 못했습니다(DNS) … (원인: ENOTFOUND) | 콘솔 서버가 호스트를 해석하지 못함 | 도메인·DNS·컨테이너 네트워크 확인 |
연결이 거부되었습니다 … (원인: ECONNREFUSED) | 대상 포트가 닫혀 있음 | 프록시 대상 포트(기본 8080) 확인 |
연결이 시간 초과했습니다 | 네트워크·방화벽 지연 | 보안그룹·프록시 경로 확인 |
FileStore가 오류를 반환했습니다(503). 프록시가 응답했습니다 … | 프록시는 살아 있으나 뒤에 앱이 없음 | FileStore 로그에서 기동 실패 사유 확인(운영에서 FILE_STORE_SERVICE_TOKEN 누락이 대표적) |
FileStore가 오류를 반환했습니다(502·504). 게이트웨이가 … | 게이트웨이가 대상을 못 찾음 | 프록시 대상 주소·포트 확인 |
| FileStore가 사유를 준 경우 | 그 사유를 그대로 표시 | 예: 저장소 secret이 설정되지 않았습니다: R2_PORTAL_ACCESS_KEY_ID |
8. 프로필 사진도 FileStore 소유다
포털 사용자 프로필 사진은 콘솔 자체 오브젝트 스토리지를 두지 않는다. 콘솔은 자기 자신도 FileStore 소비자로 등록해(consumerId: portal-console) 사진 본문을 FileStore 에 두고, 원장(directory_users)은 자산 ID(profile_image_asset_id)만 갖는다.
- 이유: 사진을 원장 행에 넣으면 사용자 수가 늘수록 전량 조회(
server/directory.ts의 진단·동기화)와 프로필 응답이 감당이 안 된다. - 사진 교체 시 이전 자산은 FileStore lifecycle 로 넘긴다(soft delete) — 실제 객체 삭제는 FileStore cleanup 정책이 한다.
- 소비 포털에는 종전과 같이 만료 locator URL(
FILE_STOREread-url)을 내려보내므로 포털 화면 코드는 그대로다.
필요한 콘솔 환경변수(예시는 .env.onprem.example):
| 이름 | 용도 |
|---|---|
FILE_STORE_BASE_URL | 서버 간 호출 주소(파일 보관 데스크와 공유) |
FILE_STORE_SERVICE_TOKEN | 서비스 토큰(파일 보관 데스크와 공유) |
PORTAL_CONSOLE_FILESTORE_CONSUMER_ID | 콘솔 소비자 식별자(기본 portal-console) |
PORTAL_CONSOLE_FILESTORE_MAPPING_ID | FileStore 관리자 화면에 등록한 콘솔 저장 정책 ID |
PORTAL_CONSOLE_FILESTORE_ORGANIZATION_ID | 선택 — 업로드에 실을 조직 식별자 |
PORTAL_CONSOLE_FILESTORE_ENABLED | 선택 — false 면 콘솔 소비자 호출을 끈다 |
FileStore 관리자 화면에서 portal-console 정책(백엔드·prefix·MIME·용량)을 먼저 등록해야 업로드가 동작한다. 이 값들은 배포 환경변수로만 바뀌므로, 콘솔은 파일 보관 데스크 상단에 콘솔 자신의 연결 상태(설정 여부와 빠진 환경변수 이름)를 읽기 전용으로 보여 준다 — 비밀값은 화면·로그에 남기지 않는다.