- 이 글에서 다루는 HWP/HWPX 지원 구성은 subinsong 님의 글과 Gemini 도움을 바탕으로 TrueNAS 환경에 맞게 재구성한 것입니다. – Paperless-ngx에서 한글파일 지원하도록 설정하기
1. 인트로
2025년을 마무리하면서 사진과 문서를 정리하고 있습니다.
사진은 photoprism으로 이전 중이고, 문서는 원노트와 paperless-ngx 조합으로 정리하고 있습니다.
대부분의 문서류는 pdf와 doc 형식이지만, 아이들 관련 문서처럼 간혹 hwp 파일이 섞여 있어서 hwp까지 함께 관리할 방법을 찾던 중에 subinsong 님의 블로그 글을 발견했습니다.
저는 TrueNAS에서 서비스를 운영하고 있고, 이미 더 최신 버전의 paperless-ngx를 사용 중이라 원문 그대로 따라 하기보다는 TrueNAS 환경에 맞게 구성을 새로 만드는 쪽을 선택했습니다.
2. vibe coding?
AI 없이 혼자서도 어떻게든 구성할 수는 있겠지만, 효율성 측면에서는 AI와 협업하는 편이 훨씬 낫다고 느끼고 있습니다. photoprism 이전 과정도 다음 글에서 정리하겠지만, 거기에서도 AI가 중요한 역할을 해 주었고, 이번 paperless-ngx의 TrueNAS 지원 작업 역시 Gemini와 함께 진행했습니다.
ChatGPT는 TrueNAS 환경에서 Dockerfile을 직접 생성할 수 없다는 제약을 제대로 반영하지 못하고, 계속 subinsong 블로그 예제를 고집하는 바람에 몇 번 시도하다가 결국 Gemini로 옮겼습니다. Gemini는 요구사항과 제약을 반영해서 코드를 재구성하는 능력이 좋아서, 특히 코딩 영역에서는 확실한 강점이 느껴졌습니다.
3. Gemini 생성 코드
개인적으로 heredoc 방식을 좋아합니다. 스크립트 하나만 공유하면 필요한 Dockerfile과 Django 앱 코드까지 한 번에 만들어 낼 수 있어서 관리와 재사용이 편하기 때문입니다. Gemini에게 TrueNAS 환경과 paperless-ngx 버전을 설명하고, subin-song님의 blog 주소를 알려 주고 heredoc 방식으로 만들어 달라고 요청했습니다.
아래 스크립트를 실행한 뒤 Docker Hub와 연결하면, Tika·Gotenberg·paperless-ngx 이미지를 HWP/HWPX 지원 버전으로 빌드하고 업로드할 수 있습니다.
#!/bin/bash
# 1. 설정 (Docker Hub ID 및 버전 명시)
DOCKERHUB_ID="flywithu"
TIKA_VER="3.2.3.0"
GOTENBERG_VER="8.22"
PAPERLESS_VER="2.20" # 현재 안정화된 최신 버전 기준
# 2. 작업 디렉토리 생성 및 이동
mkdir -p paperless-hwp-build/hwp_tika
cd paperless-hwp-build
# 3. tika.dockerfile 생성
cat <<EOF > tika.dockerfile
FROM apache/tika:${TIKA_VER}
USER root
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl
RUN mkdir -p /opt/tika-extra && \\
curl -L -o /opt/tika-extra/tika-parser-hwp-${TIKA_VER}.jar \\
https://repo1.maven.org/maven2/org/apache/tika/tika-parser-hwp/${TIKA_VER}/tika-parser-hwp-${TIKA_VER}.jar
ENV TIKA_CLASSPATH="/opt/tika-extra/*"
EOF
# 4. gotenberg.dockerfile 생성
cat <<EOF > gotenberg.dockerfile
FROM gotenberg/gotenberg:${GOTENBERG_VER}
USER root
RUN apt-get update -qq && \
apt-get install -y --no-install-recommends \
openjdk-21-jre-headless \
libreoffice-java-common \
libreoffice-h2orestart
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
USER gotenberg
EOF
# 5. paperless.dockerfile 생성 (Django 앱 포함)
cat <<EOF > paperless.dockerfile
FROM paperlessngx/paperless-ngx:${PAPERLESS_VER}
# 커스텀 앱 코드를 컨테이너 내부 소스 경로로 복사
USER root
RUN apt-get update && \\
apt-get install -y --no-install-recommends \\
tesseract-ocr-kor \\
tzdata \\
mariadb-client && \\
apt-get clean && \\
rm -rf /var/lib/apt/lists/*i
USER paperless
COPY hwp_tika/ /usr/src/paperless/src/hwp_tika/
EOF
# 6. 커스텀 Django 앱 파일 생성 (hwp_tika)
cat <<EOF > hwp_tika/apps.py
from django.apps import AppConfig
class HwpTikaConfig(AppConfig):
name = "hwp_tika"
def ready(self):
from documents.signals import document_consumer_declaration
from .signals import hwp_consumer_declaration
document_consumer_declaration.connect(hwp_consumer_declaration)
EOF
cat <<EOF > hwp_tika/__init__.py
default_app_config = "hwp_tika.apps.HwpTikaConfig"
EOF
cat <<EOF > hwp_tika/signals.py
from django.dispatch import receiver
from documents.signals import document_consumer_declaration
from paperless_tika.parsers import TikaDocumentParser
import logging
import os
from pathlib import Path
logger = logging.getLogger("paperless.hwp_tika")
class HwpTikaParser(TikaDocumentParser):
def convert_to_pdf(self, document_path, file_name):
# document_path가 Path 객체일 수 있으므로 문자열로 확실히 변환합니다.
path_str = str(document_path)
# 1. 파일이 .hwpx인 경우 물리적 파일명 변경 로직 수행
if path_str.lower().endswith(".hwpx"):
# .hwpx -> .hwp
temp_hwp_path = path_str[:-1]
# file_name(Gotenberg API에 전달될 이름)도 .hwp로 변경
safe_file_name = file_name
if file_name and file_name.lower().endswith(".hwpx"):
safe_file_name = file_name[:-1]
logger.info(f"[Fix] Renaming: {os.path.basename(path_str)} -> {os.path.basename(temp_hwp_path)}")
# 물리적 파일명 변경
os.rename(path_str, temp_hwp_path)
try:
# 변경된 경로와 안전한 파일 이름으로 Gotenberg에 전송
# super() 호출 시 document_path 타입을 맞춰주기 위해 Path 객체로 다시 감쌉니다.
return super().convert_to_pdf(Path(temp_hwp_path), safe_file_name)
finally:
# 시스템 정합성을 위해 원래 이름(.hwpx)으로 복구
if os.path.exists(temp_hwp_path):
os.rename(temp_hwp_path, path_str)
# .hwp 파일이거나 다른 경우는 기본 로직 수행
return super().convert_to_pdf(document_path, file_name)
def get_parser(*args, **kwargs):
return HwpTikaParser(*args, **kwargs)
@receiver(document_consumer_declaration)
def hwp_consumer_declaration(sender, **kwargs):
return {
"parser": get_parser,
"weight": 100,
"mime_types": {
"application/x-hwp": ".hwp",
"application/hwp": ".hwp",
"application/x-hwpx": ".hwpx",
"application/x-hwp+zip": ".hwpx",
},
}
EOF
# 7. 빌드 및 푸시 실행
echo "--- Docker Hub 로그인 ---"
docker login
echo "--- Tika HWP 빌드 (${TIKA_VER}-hwp) ---"
docker build -t ${DOCKERHUB_ID}/tika-hwp:${TIKA_VER}-hwp -f tika.dockerfile .
docker push ${DOCKERHUB_ID}/tika-hwp:${TIKA_VER}-hwp
echo "--- Gotenberg HWP 빌드 (${GOTENBERG_VER}-hwp) ---"
docker build -t ${DOCKERHUB_ID}/gotenberg-hwp:${GOTENBERG_VER}-hwp -f gotenberg.dockerfile .
docker push ${DOCKERHUB_ID}/gotenberg-hwp:${GOTENBERG_VER}-hwp
echo "--- Paperless-ngx HWP 빌드 (${PAPERLESS_VER}-hwp) ---"
docker build -t ${DOCKERHUB_ID}/paperless-hwp:${PAPERLESS_VER}-hwp -f paperless.dockerfile .
docker push ${DOCKERHUB_ID}/paperless-hwp:${PAPERLESS_VER}-hwp
echo "--- 모든 작업 완료! ---"
4. truenas compose 파일
Docker Hub에 미리 빌드해 둔 이미지를 내려받아 사용하는 방식이라, 대부분의 경우 이 compose 설정만으로 HWP/HWPX를 지원하는 paperless-ngx 환경을 구성할 수 있습니다.
디버깅용으로 넣어 둔 alpine 컨테이너는 필요 없으면 제거해도 무방합니다.
아래 예시에서는 Tika·Gotenberg·paperless-ngx·paperless-ai 컨테이너를 한 네트워크에 올렸고, HWP/HWPX 처리를 위해 PAPERLESS_APPS에 hwp_tika.apps.HwpTikaConfig를 등록하고, PAPERLESS_CONSUMER_EXTENSION_USER_ALLOWLIST에 .hwp와 .hwpx를 추가했습니다.
데이터베이스 관련 환경 변수(PAPERLESS_DBHOST, PAPERLESS_DBNAME, PAPERLESS_DBPASS, PAPERLESS_DBPORT)는 각자의 TrueNAS 및 DB 환경에 맞게 채워 넣으면 됩니다.
https://hub.docker.com/repositories/flywithu 에 이미지가 있습니다.
networks:
default:
name: paperless_default
services:
debug-alpine:
command: sh -c "sleep infinity"
container_name: debug-alpine
image: alpine:latest
restart: unless-stopped
stdin_open: True
tty: True
gotenberg:
command:
- gotenberg
- '--chromium-disable-javascript=true'
- '--chromium-allow-list=file:///tmp/.*'
- '--api-timeout=1200s'
- '--libreoffice-start-timeout=60s'
container_name: gotenberg
image: flywithu/gotenberg-hwp:8.22-hwp
restart: unless-stopped
paperless-ai:
container_name: paperless-ai
environment:
AI_MAX_RETRIES: 3
AI_OCR_ENGINE: paddle
image: clusterzx/paperless-ai:3.0.9
ports:
- '43001:3000'
restart: unless-stopped
volumes:
- /mnt/hdd0/mount/paperless/ai_data:/app/data
paperless-ngx:
container_name: paperless-ngx
depends_on:
- paperless-ai
- tika
- gotenberg
environment:
PAPERLESS_AI_API: http://paperless-ai:3000
PAPERLESS_APPS: hwp_tika.apps.HwpTikaConfig
PAPERLESS_CONSUMER_EXTENSION_USER_ALLOWLIST: .hwpx,.hwp
PAPERLESS_CONSUMER_RECURSIVE: True
PAPERLESS_DBENGINE: mariadb
PAPERLESS_DBHOST:
PAPERLESS_DBNAME:
PAPERLESS_DBPASS:
PAPERLESS_DBPORT:
PAPERLESS_DBSSLMODE: DISABLED
PAPERLESS_DBUSER: truenas
PAPERLESS_OCR_LANGUAGE: kor+eng
PAPERLESS_OCR_LANGUAGES: kor eng
PAPERLESS_REDIS: redis://192.168.10.100:40059/10
PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_URL: http://localhost:48001
image: flywithu/paperless-hwp:2.20-hwp
ports:
- '48001:8000'
pull_policy: always
restart: unless-stopped
volumes:
- /mnt/hdd0/mount/paperless/data:/usr/src/paperless/data
- /mnt/hdd0/mount/paperless/media:/usr/src/paperless/media
- /mnt/hdd0/mount/paperless/export:/usr/src/paperless/export
- /mnt/hdd0/scan/paperless:/usr/src/paperless/consume
tika:
container_name: tika
image: flywithu/tika-hwp:3.2.3.0-hwp
restart: unless-stopped
volumes:
consume: Null
data: Null
export: Null
media: Null
paperless-ai_data: Null
redisdata: Null
5. 문제점 및 실행화면
처음에는 subinsong 님의 가이드를 거의 그대로 따라 구성했는데, 이 상태에서는 hwpx 파일이 제대로 import되지 않는 문제가 있었습니다.
Gotenberg 쪽에서 hwpx를 hwp와 동일하게 처리하지 못하는 부분이 있어, hwpx 파일을 일시적으로 .hwp 확장자로 변경한 뒤 변환을 진행하고, 변환이 끝나면 다시 원래 이름으로 되돌리는 래퍼 파서를 추가하는 방식으로 보완했습니다.
이 수정 이후에는 hwp와 hwpx 파일 모두 아래 스크린샷처럼 정상적으로 import되는 것을 확인했습니다. 다만 모든 형식의 hwp/hwpx 파일을 다 테스트해 보지는 못했기 때문에, 사용 중에 문제가 발생하는 파일이 있다면 댓글이나 메일로 공유해 주시면 확인해 보겠습니다.

HWP 파일

HWPX 파일
6. 마무리
TrueNAS에서 paperless-ngx를 사용하면서 HWP/HWPX까지 함께 관리하고 싶은 분께 도움이 되었으면 합니다. 구성 자체는 Docker 이미지 교체와 환경 변수 설정만으로 끝나지만, 중간에 hwpx 처리와 같이 오류날수 있는 지점이 있어서 기록 차원에서 정리해 두었습니다. 더 나은 설정이나 개선 아이디어가 있다면 편하게 알려 주세요.