fix: release QA hardening across processing, media, security, and CI gates (#649)

A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
This commit is contained in:
SnapOtter
2026-07-27 15:37:30 +08:00
committed by GitHub
parent bc32f86a07
commit d10d0f544f
855 changed files with 54564 additions and 13092 deletions
+40 -17
View File
@@ -1,8 +1,9 @@
---
description: "모든 SnapOtter 환경 변수와 기본값. 인증, 스토리지, AI 모델, 분석 등을 구성합니다."
i18n_source_hash: 8e9e9ca2840c
i18n_source_hash: 25970c776f7c
i18n_provenance: human
i18n_output_hash: 6a94a1e1efe3
i18n_output_hash: 51e09a535cd4
i18n_hash_version: 2
---
# 구성 {#configuration}
@@ -19,28 +20,51 @@ i18n_output_hash: 6a94a1e1efe3
| `RATE_LIMIT_PER_MIN` | `1000` | IP당 분당 최대 요청 수입니다. 속도 제한을 비활성화하려면 0으로 설정합니다. |
| `CORS_ORIGIN` | (비어 있음) | CORS에 대해 허용된 오리진의 쉼표 구분 목록이며, 동일 오리진만 허용하려면 비워 둡니다. |
| `LOG_LEVEL` | `info` | 로그 상세 수준입니다. 다음 중 하나: `fatal`, `error`, `warn`, `info`, `debug`, `trace`. |
| `TRUST_PROXY` | `true` | 리버스 프록시의 `X-Forwarded-For` 헤더 신뢰합니다. 프록시 뒤에 있지 않으면 `false`로 설정합니다. |
| `TRUST_PROXY` | `loopback,linklocal,uniquelocal` | `X-Forwarded-For`를 통해 클라이언트 IP를 설정할 수 있는 피어를 지정합니다. 기본값은 사설 네트워크의 피어만 믿으므로 Docker 네트워크나 LAN의 리버스 프록시는 신뢰되고, 공개 클라이언트가 위조한 헤더 신뢰되지 않습니다. 공개 주소에서 직접 관리하는 프록시를 앞에 둔 경우에만 `true`로 설정하세요. |
### 인증 {#authentication}
아래 두 불리언 값은 `true``false`만 허용합니다. `1`, `yes`, `on` 같은 그 밖의 값은 검증에 실패하며, 서버는 수신 대기를 시작하기 전에 종료됩니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `AUTH_ENABLED` | `false` | 로그인을 요구하려면 `true`로 설정합니다. Docker 이미지의 기본값은 `true`입니다. |
| `AUTH_ENABLED` | `true` | 로그인을 요구합니다. 계정을 전혀 두지 않고 실행하려면 `false`로 설정합니다. 이 경우 모든 요청에 관리자 권한이 부여되므로 신뢰할 수 있는 네트워크에서만 사용하세요. |
| `DEFAULT_USERNAME` | `admin` | 초기 관리자 계정의 사용자 이름입니다. 첫 실행 시에만 사용됩니다. |
| `DEFAULT_PASSWORD` | `admin` | 초기 관리자 계정의 비밀번호입니다. 첫 로그인 후 변경하세요. |
| `MAX_USERS` | `0` (무제한) | 등록된 사용자 계정의 최대 수입니다. 무제한으로 하려면 0으로 설정합니다. |
| `SESSION_DURATION_HOURS` | `168` | 로그인 세션 수명(시간 단위, 기본값은 7일)입니다. |
| `SKIP_MUST_CHANGE_PASSWORD` | - | 첫 로그인 시 강제 비밀번호 변경 프롬프트를 우회하려면 비어 있지 않은 값으로 설정합니다 |
| `SKIP_MUST_CHANGE_PASSWORD` | `false` | 첫 로그인 시 강제 비밀번호 변경 프롬프트를 건너뛰려면 `true`로 설정합니다. |
### 스토리지 {#storage}
| 변수 | 기본값 | 설명 |
|---|---|---|
| `STORAGE_MODE` | `local` | `local` 또는 `s3`. S3/MinIO에는 s3_storage 기능이 포함된 라이선스가 필요합니다. |
| `DATABASE_URL` | `postgres://snapotter:snapotter@postgres:5432/snapotter` | PostgreSQL 연결 문자열입니다. |
| `REDIS_URL` | `redis://redis:6379` | Redis 연결 문자열(BullMQ 작업 큐에 사용)입니다. |
| `WORKSPACE_PATH` | `./tmp/workspace` | 처리 중 임시 파일을 위한 디렉터리입니다. 자동으로 정리됩니다. |
| `FILES_STORAGE_PATH` | `./data/files` | 영구 사용자 파일(업로드된 이미지, 저장된 결과)을 위한 디렉터리입니다. |
| `STORAGE_MODE` | `local` | `local` 또는 `s3`. S3MinIO에는 s3_storage 기능이 포함된 라이선스와 아래의 `S3_*` 변수가 필요합니다. |
| `DATABASE_URL` | `postgres://snapotter:snapotter@localhost:5432/snapotter` | PostgreSQL 연결 문자열입니다. Compose 스택은 이 값을 자체 `postgres` 서비스로 지정합니다. (`REDIS_URL`과 함께) 설정하지 않은 채로 두면 임베디드 모드가 됩니다. |
| `REDIS_URL` | `redis://localhost:6379` | Redis 연결 문자열(BullMQ 작업 큐에 사용)입니다. Compose는 이 값을 자체 `redis` 서비스로 지정합니다. |
| `WORKSPACE_PATH` | `./tmp/workspace` | 처리 중 임시 파일을 위한 디렉터리입니다. 자동으로 정리됩니다. 이미지는 `/tmp/workspace`로 설정합니다. |
| `FILES_STORAGE_PATH` | `./data/files` | 영구 사용자 파일(업로드된 이미지, 저장된 결과)을 위한 디렉터리입니다. 이미지는 `/data/files`로 설정합니다. |
### S3 오브젝트 스토리지 {#s3-object-storage}
`STORAGE_MODE=s3`일 때만 읽습니다. 필수인 세 가지 중 하나라도 빠지면 시작이 실패하며, 빠뜨린 변수 이름을 알려줍니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
| `S3_BUCKET` | (비어 있음) | 업로드와 출력을 담는 버킷입니다. 필수입니다. |
| `S3_ACCESS_KEY_ID` | (비어 있음) | 액세스 키입니다. 필수입니다. 컨테이너에서는 `S3_ACCESS_KEY_ID_FILE`을 통해 대신 마운트할 수 있습니다. |
| `S3_SECRET_ACCESS_KEY` | (비어 있음) | 시크릿 키입니다. 필수입니다. 같은 파일 규칙을 씁니다: `S3_SECRET_ACCESS_KEY_FILE`. |
| `S3_REGION` | `us-east-1` | 버킷 리전입니다. |
| `S3_ENDPOINT` | (비어 있음) | MinIO, R2, Backblaze를 비롯한 S3 호환 스토리지를 위한 사용자 지정 엔드포인트입니다. 비워 두면 AWS를 뜻합니다. |
| `S3_FORCE_PATH_STYLE` | `false` | MinIO처럼 가상 호스트 주소 지정 대신 `endpoint/bucket/key`를 요구하는 경우 `true`로 설정합니다. |
| `S3_PREFIX` | (비어 있음) | 키 접두사입니다. 버킷 하나에 여러 인스턴스를 담을 수 있습니다. |
### 저장 데이터 암호화 {#encryption-at-rest}
| 변수 | 기본값 | 설명 |
|---|---|---|
| `DATA_ENCRYPTION_KEY` | (비어 있음) | 16진수 64자(32바이트)입니다. 데이터베이스에 저장된 민감한 설정을 암호화합니다. 16진수 64자가 아니면 시작 시 거부됩니다. |
| `DATA_ENCRYPTION_KEY_PREVIOUS` | (비어 있음) | 교체해서 물러나는 이전 키이며 형식은 같습니다. 교체 중에는 둘 다 설정해 기존 행이 계속 복호화되도록 한 뒤, 이 값을 제거하세요. |
### 임베디드 모드 {#embedded-mode}
@@ -59,16 +83,15 @@ i18n_output_hash: 6a94a1e1efe3
| 변수 | 기본값 | 설명 |
|---|---|---|
| `MAX_UPLOAD_SIZE_MB` | `100` | 업로드당 최대 파일 크기(메가바이트)입니다. 무제한으로 하려면 0으로 설정합니다. |
| `MAX_BATCH_SIZE` | `100` | 단일 배치 요청의 최대 파일 수입니다. 무제한으로 하려면 0으로 설정합니다. |
| `MAX_UPLOAD_SIZE_MB` | `0` (무제한) | 업로드당 최대 파일 크기(메가바이트)입니다. 무제한으로 하려면 0으로 설정합니다. 게시된 이미지는 `0`으로 제공되며, 소스 빌드는 100에서 시작합니다. |
| `MAX_BATCH_SIZE` | `0` (무제한) | 단일 배치 요청의 최대 파일 수입니다. 무제한으로 하려면 0으로 설정합니다. 게시된 이미지는 `0`으로 제공되며, 소스 빌드는 100에서 시작합니다. |
| `CONCURRENT_JOBS` | `0` (자동) | 병렬로 실행되는 배치 작업 수입니다. 사용 가능한 CPU 코어를 기준으로 자동 감지하려면 0으로 설정합니다. |
| `MAX_MEGAPIXELS` | `0` (무제한) | 허용되는 최대 이미지 해상도(메가픽셀)입니다. 무제한으로 하려면 0으로 설정합니다. |
| `MAX_WORKER_THREADS` | `0` (자동) | 이미지 처리를 위한 최대 워커 스레드 수입니다. 사용 가능한 CPU 코어를 기준으로 자동 감지하려면 0으로 설정합니다. |
| `PROCESSING_TIMEOUT_S` | `0` (제한 없음) | 요청당 최대 처리 시간(초)입니다. 시간 초과 없음으로 하려면 0으로 설정합니다. |
| `MAX_PIPELINE_STEPS` | `20` | 파이프라인의 최대 단계 수입니다. 제한 없음으로 하려면 0으로 설정합니다. |
| `MAX_CANVAS_PIXELS` | `0` (제한 없음) | 출력 이미지의 최대 캔버스 크기(픽셀)입니다. 제한 없음으로 하려면 0으로 설정합니다. |
| `MAX_SVG_SIZE_MB` | `0` (무제한) | 최대 SVG 파일 크기(메가바이트)입니다. 무제한으로 하려면 0으로 설정합니다. |
| `MAX_SPLIT_GRID` | `100` | 이미지 분할 도구의 최대 그리드 차원입니다. |
| `MAX_SVG_SIZE_MB` | `50` | 새니타이즈하기 전에 허용하는 최대 SVG 크기(메가바이트)입니다. 여기서 `0`은 주변 행과 다르게 동작합니다. 상한을 높이는 것이 아니라 파싱 전 크기 제한을 아예 없애므로, 이 값은 설정된 상태로 두세요. |
| `MAX_PDF_PAGES` | `0` (무제한) | PDF-to-image 변환의 최대 PDF 페이지 수입니다. 무제한으로 하려면 0으로 설정합니다. |
### 정리 {#cleanup}
@@ -82,7 +105,7 @@ i18n_output_hash: 6a94a1e1efe3
| 변수 | 기본값 | 설명 |
|---|---|---|
| `DEFAULT_THEME` | `light` | 새 세션의 기본 테마입니다. `light` 또는 `dark`. |
| `DEFAULT_THEME` | `light` | 새 세션의 기본 테마입니다. `light`, `dark` 또는 `system`. |
| `DEFAULT_LOCALE` | `en` | 기본 인터페이스 언어입니다. |
| `DEFAULT_TOOL_VIEW` | `sidebar` | 기본 도구 레이아웃입니다. `sidebar` 또는 `fullscreen`. |
@@ -124,13 +147,13 @@ services:
image: postgres:17-alpine
environment:
POSTGRES_USER: snapotter
POSTGRES_PASSWORD: snapotter
POSTGRES_PASSWORD: snapotter # 로컬이 아닌 배포의 경우 이를 변경합니다.
POSTGRES_DB: snapotter
volumes:
- SnapOtter-pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U snapotter"]
test: ["CMD-SHELL", "pg_isready -U snapotter -d snapotter"]
interval: 10s
timeout: 5s
retries: 12