시놀로지 NAS(DSM)의 Docker에 Stash를 설치하고, 전용 Cloudflare WARP 프록시와 R18.dev PostgreSQL DB를 붙여 R18dev_SQL 스크레이퍼로 메타데이터를 가져오는 구성이다.
Claude를 통해 설치 절차를 도움받았고, 중간중간 트러블슈팅 과정도 함께 작성하였다.
* 본 문서의 Docker는 NAS(Synology) 환경에서 구동되고 있으며 Docker 디렉토리가 Volume2에 설치되어 있다.
1. 구성 개요
stash-net (bridge)
┌─────────────────────────────────────────────┐
│ │
│ stash ──HTTP(S)──▶ stash-socks ──▶ 인터넷 │
│ │ (WARP) │
│ └──psql──▶ r18-postgres │
│ ▲ │
│ r18-tools ────────┘ (덤프 갱신/스크레이퍼 설치, 1회성) │
└─────────────────────────────────────────────┘
| 서비스 | 이미지 | 역할 |
| stash-socks | ghcr.io/mon-ius/docker-warp-socks:latest | Stash 전용 WARP 프록시 (9091, HTTP/SOCKS5 겸용). 다른 용도의 warp-socks 컨테이너와 구분하기 위해 이름을 따로 씀 |
| r18-postgres | postgres:17-alpine | R18.dev 덤프를 적재한 DB (r18) |
| stash | stash-r18:latest (로컬 빌드) | stashapp/stash:latest + py3-psycopg2, py3-pysocks |
| r18-tools | postgres:17-alpine | profiles: [tools] — up 으로는 안 뜨고 run 으로만 실행 |
파일 구조
/volume2/docker/stash/
├── docker-compose.yml
├── r18-update-cron.sh # 작업 스케줄러용 정기 갱신 스크립트
├── stash/
│ └── Dockerfile # Stash 이미지 확장
├── r18-tools/
│ └── r18.sh # update / install-scraper
│
│ ── 아래는 실행 중 생성/사용되는 데이터 ──
├── config/ # Stash 설정 (/root/.stash)
│ └── scrapers/R18dev_SQL/ # 스크레이퍼 설치 위치
├── metadata/ cache/ blobs/ generated/
├── r18-db/ # PostgreSQL 데이터
├── r18-dumps/ # 받은 덤프 (r18-latest.sql.gz)
└── logs/ # r18-update.log
미디어(e.g. aaa.mp4)는 /volume4/media → 컨테이너 /data (읽기 전용).
2. 사전 준비
2-1. 권한
DSM에서는 일반 계정이 /var/run/docker.sock 에 접근할 수 없다. 모든 docker 명령은 sudo 로 실행한다.
permission denied while trying to connect to the Docker daemon socket
→ sudo 를 빼먹은 것.
2-2. 파일 배치
stash-r18.zip 을 /volume2/docker/stash/ 에 올리고 압축 해제. zip 안에 stash-r18/ 상위 폴더가 있으므로 내용물만 꺼낸다. (DSM에 unzip 이 없을 수 있어 7z 사용)
(7z사용이 어렵다면 winSCP와 같은 툴로 접근해서 ftp처럼 경로에 올리면 된다.)
cd /volume2/docker/stash
sudo 7z x stash-r18.zip -o/tmp/stash-r18 -y
sudo cp -r /tmp/stash-r18/stash-r18/. .
sudo rm -rf /tmp/stash-r18
docker-compose.yml, stash/Dockerfile, r18-tools/r18.sh, r18-update-cron.sh 가 모두 있는지 확인.
unable to prepare context: path "./stash" not found
→ stash/ 폴더(Dockerfile)를 빼먹은 것.
2-3. bind mount 폴더 생성
시놀로지 Docker는 없는 bind mount 경로를 자동 생성하지 않는다.
cd /volume2/docker/stash
sudo mkdir -p config/scrapers metadata cache blobs generated r18-db r18-dumps logs
Bind mount failed: '/volume2/docker/stash/r18-db' does not exist
→ 이 단계를 빼먹은 것.
2-4. 기존 stash 컨테이너가 있을 때
이름 충돌을 피하려면 기존 컨테이너가 어떻게 만들어졌는지 먼저 확인한다.
sudo docker inspect stash --format '{{ index .Config.Labels "com.docker.compose.project" }} / {{ index .Config.Labels "com.docker.compose.project.working_dir" }}'
| 결과 | 조치 |
| stash / /volume2/docker/stash | 같은 프로젝트. 그대로 up 하면 교체됨 |
| 비어 있음 / 다른 프로젝트 | sudo docker stop stash && sudo docker rm stash 후 진행 (설정·DB는 bind mount라 유지됨). Container Manager의 다른 프로젝트였다면 그쪽도 정리 |
3. 설치
3-1. 기동
cd /volume2/docker/stash
sudo docker compose up -d --build --remove-orphans
sudo docker compose ps
stash-socks, r18-postgres 가 healthy 가 된 뒤 stash 가 뜬다. Stash UI: http://<NAS IP>:9999
WARP 동작 확인 (warp=on 이 보이면 정상):
sudo docker exec stash-socks curl -sx socks5h://127.0.0.1:9091 https://www.cloudflare.com/cdn-cgi/trace | grep warp
3-2. R18.dev DB 적재
덤프가 수 GB라 오래 걸린다. SSH가 끊겨도 계속되도록 스크립트로 실행하는 것을 권장.
cd /volume2/docker/stash
sudo -v # 비밀번호 먼저 입력 (백그라운드에서 프롬프트에 막히지 않게)
sudo nohup bash r18-update-cron.sh >/dev/null 2>&1 &
tail -f logs/r18-update.log # Ctrl+C 로 보기만 종료, 작업은 계속됨
정상 로그:
[r18] 덤프 확인: https://r18.dev/dumps/latest
[r18] 새 덤프 다운로드 완료 (x.xG)
[r18] 임시 DB(r18_new)에 적재 중…
[r18] r18 DB 교체
[r18] 완료 — derived_video N건
확인:
sudo docker exec r18-postgres psql -U postgres -d r18 -c "SELECT count(*) FROM derived_video;"
(2026-10 기준 약 191만 건)
3-3. 스크레이퍼 설치
sudo docker compose run --rm r18-tools install-scraper
install-scraper 가 하는 일:
- javstash/R18dev_SQL 저장소의 scrapers/R18dev_SQL/ 을 config/scrapers/R18dev_SQL/ 에 복사
- R18dev_SQL.py 수정
- host="localhost" → host="r18-postgres"
- password → R18_DB_PASSWORD (기본 postgres)
- LANG='EN' (일본어: -e SCRAPER_LANG=JA 로 실행)
- 안전 실행 래퍼 R18dev_SQL_wrap.py 생성 + yml 이 래퍼를 호출하도록 수정
- 원본은 결과가 없으면 아무것도 출력하지 않고 죽어서 Stash 가 could not unmarshal json from script output: EOF 를 냄
- 래퍼는 이 경우 null / [] 을 출력해 '결과 없음' 으로 처리하고, 원인을 Stash 로그에 남김
설치 후 Stash: 설정 › Metadata Providers › Scrapers › Reload scrapers
스크레이퍼를 Stash UI(Available Scrapers)에서 다시 받으면 위 수정이 사라진다. 그럴 땐 install-scraper 를 다시 실행.
3-4. 스크레이퍼 동작 확인
sudo docker exec -i stash sh -c 'cd /root/.stash/scrapers/R18dev_SQL && echo "{\"code\":\"SSIS-001\"}" | python3 R18dev_SQL_wrap.py sceneByFragment' 2>/dev/null
"title": "After Abstaining ...", "performers": [{"name": "Donald Trump", ... 처럼 영문 JSON 이 나오면 정상.
4. 정기 덤프 갱신 (DSM 작업 스케줄러)
r18-update-cron.sh 동작:
- 서버 덤프가 바뀌지 않았으면 다운로드·적재 없이 수 초 만에 종료 → 매일 실행 권장
- r18_new 에 적재 → derived_video 검증 → 통과 시에만 r18 교체 (실패해도 기존 DB 유지)
- flock 으로 중복 실행 방지, PATH 보정, docker compose run -T (TTY 없음)
- 로그: logs/r18-update.log (최근 2000줄 유지)
등록
제어판 › 작업 스케줄러 › 생성 › 예약된 작업 › 사용자 정의 스크립트
| 탭 | 설정 |
| 일반 | 작업: R18 덤프 갱신 / 사용자: root |
| 스케줄 | 매일, 04:00 등 한가한 시간 |
| 작업 설정 | bash /volume2/docker/stash/r18-update-cron.sh |
| 작업 설정 (선택) | 실행 세부 정보 메일 → "비정상 종료 시에만" |
등록 후 목록에서 선택 → 실행 으로 테스트.
강제 재적재:
cd /volume2/docker/stash
sudo FORCE=1 docker compose run --rm r18-tools update
교체 순간에는 덤프 + r18 +r18_new 가 동시에 존재하므로 /volume2 에 DB 크기 2배 이상 여유 공간이 필요하다.
5. Stash 에서 사용
5-1. 한 장면씩
장면 편집 › Scrape with… › JAVStash-R18dev_SQL. 파일명에 품번(ABCD-001 등)만 있으면 된다. 제목이 비어 있으면 Stash 가 파일명을 제목으로 넘긴다.
5-2. 일괄 수집·저장 (Identify)
Identify 는 위치가 두 군데이고 둘 다 숨겨져 있다.
| 위치 | 보이는 조건 |
| 설정 › Tasks › Library › Identify… | 설정 화면 상단 Advanced Mode 를 켜야 보임. 전체 또는 경로 지정 |
| Scenes 목록 › 우측 상단 ⋯ › Identify… | 장면을 1개 이상 선택해야 보임 |
권장 옵션:
| 항목 | 설정 |
| Sources | JAVStash-R18dev_SQL 만 남김 |
| Title / Details / Date / Code / Director | Overwrite (기존 값 보존하려면 Ignore) |
| Performers / Tags | Merge + Create missing |
| Studio | Overwrite + Create missing |
| Set cover images | 켬 |
| Skip matches that have more than one result | 켬 |
| Set organised flag | 필요 시 |
처음엔 장면 10개 정도로 시험 후 전체 실행.
결과를 눈으로 보며 저장하려면 Scenes 보기 방식을 Tagger 로 바꿔 소스를 R18dev_SQL 로 지정.
6. 문제 해결
| 증상 | 원인 / 조치 |
| permission denied ... docker.sock | sudo 누락 |
| path "./stash" not found | stash/Dockerfile 누락 → 2-2 |
| Bind mount failed ... does not exist | 폴더 미생성 → 2-3 |
| could not unmarshal json from script output: EOF | ① r18 DB 없음 → 3-2 ② 래퍼 미설치 → 3-3 다시 실행 |
| database "r18" does not exist | 덤프 적재 전이거나 실패 → logs/r18-update.log 확인 후 3-2 |
| 스크랩은 되는데 '결과 없음' | Stash 로그(Debug)에서 확인: DB 에서 해당 작품을 찾지 못함 = R18.dev 에 없음 / 품번을 추출하지 못함 = 파일명·제목에 품번 없음 |
| 배우 이름이 일본어로 표시 | 아래 6-1 |
6-1. 배우 이름이 일본어로 나올 때
- 스크레이퍼 출력 확인 (3-4). 출력이 일본어면 LANG 이 JA → 수정:
sudo sed -i "s/^LANG=.*/LANG='EN' # JA or EN/" config/scrapers/R18dev_SQL/R18dev_SQL.py
- 출력은 영문인데 Stash 에 일본어로 보이면 기존 배우와 매칭된 것. 예전에 LANG='JA' 로 스크랩하면 배우가 name=ドナルド・トランプ, aliases=Donald Trump로 생성되므로, 영문 이름이 별칭으로 매칭되어 일본어 이름이 표시된다. → Performers 에서 Name 을 영문으로, 일본어 이름을 Aliases 로 옮긴다.
- DB 에 영문 이름 자체가 없는지 확인:
sudo docker exec r18-postgres psql -U postgres -d r18 -c "
SELECT A.id, A.name_kanji, A.name_romaji, MT.target_en
FROM derived_video_actress VA
LEFT JOIN derived_actress A ON VA.actress_id = A.id
LEFT JOIN machine_translation MT ON A.name_kanji = MT.source_ja
WHERE VA.content_id = 'ssis00001';"
name_romaji, target_en 둘 다 비어 있으면 R18.dev 데이터에 영문명이 없는 것 (스크레이퍼는 일본어로 대체).
7. 유지보수
| 작업 | 명령 |
| Stash 업데이트 | sudo docker compose build --pull stash && sudo docker compose up -d (커스텀 이미지라 pull 만으로는 안 됨) |
| 프록시·DB 이미지 업데이트 | sudo docker compose pull stash-socks r18-postgres && sudo docker compose up -d |
| 스크레이퍼 재설치 | sudo docker compose run --rm r18-tools install-scraper |
| 덤프 갱신 로그 | tail -n 50 /volume2/docker/stash/logs/r18-update.log |
| 전체 상태 | sudo docker compose ps |
| 컨테이너 로그 | sudo docker compose logs -f stash |
설정 메모
- 프록시는 http://stash-socks:9091 로 지정 (warp-socks v8 은 같은 포트에서 HTTP 도 받음). socks5h:// 로 쓰면 파이썬 requests 에 PySocks 가 필요 — 이미지에 py3-pysocks 를 넣어 두었으므로 바꿔도 동작은 함.
- NO_PROXY 에 내부 컨테이너와 사설 대역이 들어 있어 DB·LAN 통신은 프록시를 타지 않음.
- 다른 기기에서 이 프록시를 쓰려면 stash-socks 의 ports 주석을 해제 (9092:9091, 기존 warp-socks 의 9091 과 겹치지 않게).
- DB 비밀번호를 바꾸려면 docker-compose.yml 옆에 .env 를 만들고 R18_DB_PASSWORD=... 지정. DB 최초 생성 전에 정해야 하며, 바꾼 뒤 install-scraper 재실행.
참고
- 스크레이퍼: https://github.com/javstash/R18dev_SQL
- 덤프: https://r18.dev/dumps
- 프록시: https://github.com/mon-ius/docker-warp-socks
docker-compose.yml:
# Stash + Cloudflare WARP 프록시 + R18.dev PostgreSQL
# 배치 위치 예: /volume2/docker/stash/ (이 파일, stash/, r18-tools/ 를 같은 폴더에)
#
# 최초 기동 : docker compose up -d --build
# DB 덤프 : docker compose run --rm r18-tools update
# 스크레이퍼: docker compose run --rm r18-tools install-scraper
x-proxy-env: &proxy-env
# warp-socks v8 은 9091 한 포트에서 HTTP/SOCKS5 를 같이 제공.
# http:// 로 지정해야 스크레이퍼(파이썬 requests)도 별도 의존성 없이 프록시를 탄다.
HTTP_PROXY: http://stash-socks:9091
HTTPS_PROXY: http://stash-socks:9091
http_proxy: http://stash-socks:9091
https_proxy: http://stash-socks:9091
NO_PROXY: &no-proxy localhost,127.0.0.1,stash-socks,r18-postgres,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12
no_proxy: *no-proxy
services:
stash-socks:
image: ghcr.io/mon-ius/docker-warp-socks:latest
container_name: stash-socks
restart: unless-stopped
# 다른 기기에서도 이 프록시를 쓰려면 아래 주석 해제 (호스트 포트가 다른 warp-socks 컨테이너와 겹치지 않게 조정)
# ports:
# - "9092:9091" # 기존 warp-socks(9091)와 겹치지 않게
healthcheck:
test: ["CMD", "curl", "-x", "socks5h://127.0.0.1:9091", "-fsSL", "https://www.cloudflare.com/cdn-cgi/trace"]
interval: 30s
timeout: 10s
retries: 5
start_period: 15s
networks:
- stash-net
r18-postgres:
image: postgres:17-alpine
container_name: r18-postgres
restart: unless-stopped
shm_size: 256mb # 덤프 import 중 인덱스 생성 시 shared memory 부족 방지
environment:
TZ: Asia/Seoul
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${R18_DB_PASSWORD:-postgres}
POSTGRES_DB: postgres
volumes:
- /volume2/docker/stash/r18-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 10
networks:
- stash-net
stash:
build: ./stash # stashapp/stash:latest + psycopg2/pysocks
image: stash-r18:latest
container_name: stash
restart: unless-stopped
ports:
- "9999:9999"
environment:
<<: *proxy-env
TZ: Asia/Seoul
STASH_STASH: /data
STASH_GENERATED: /generated
STASH_METADATA: /metadata
STASH_CACHE: /cache
STASH_BLOBS: /blobs
volumes:
- /volume2/docker/stash/config:/root/.stash
- /volume2/docker/stash/metadata:/metadata
- /volume2/docker/stash/cache:/cache
- /volume2/docker/stash/blobs:/blobs
- /volume2/docker/stash/generated:/generated
- /volume2/MEDIA:/data:ro
depends_on:
stash-socks:
condition: service_healthy
r18-postgres:
condition: service_healthy
networks:
- stash-net
# 1회성 작업용 (up 으로는 뜨지 않음): docker compose run --rm r18-tools <update|install-scraper>
r18-tools:
image: postgres:17-alpine
profiles: ["tools"]
entrypoint: ["sh", "/r18.sh"]
command: ["update"]
environment:
<<: *proxy-env
PGHOST: r18-postgres
PGUSER: postgres
PGPASSWORD: ${R18_DB_PASSWORD:-postgres}
FORCE: ${FORCE:-0}
volumes:
- ./r18-tools/r18.sh:/r18.sh:ro
- /volume2/docker/stash/r18-dumps:/dumps
- /volume2/docker/stash/config/scrapers:/scrapers
depends_on:
stash-socks:
condition: service_healthy
r18-postgres:
condition: service_healthy
networks:
- stash-net
networks:
stash-net:
driver: bridge
'Network' 카테고리의 다른 글
| [GigE] GigE Vision 랜카드(NIC) 고급설정을 통한 최적화 (0) | 2019.03.19 |
|---|---|
| 토렌트 다운로드 기록 볼 수 있는 사이트 (0) | 2019.03.02 |
| ASUS 공유기 MAC주소 변경 (0) | 2019.03.02 |
| GoodbyeDPI 사용 HTTPS 차단 우회하기 (0) | 2019.02.24 |
| Olleh Skylife 셋탑박스 공유기 하위에 설치 (0) | 2018.08.11 |
