English Version (README_EN.md)
๋ถ ์ค์์์ค๋ ZIP/CBZ ์์ถ ํ์ผ ํํ์ ๋์ ๋ฐ ๋งํ์ฑ ์ ์น ํ๊ฒฝ์์ ์ง์ฐ ์์ด ๊ฐ์ํ ์ ์๋๋ก ์ค๊ณ๋ ์ด๊ฒฝ๋, ๊ณ ์ฑ๋ฅ ๊ฐ์ธ ๋ฏธ๋์ด ์๋ฒ์ ๋๋ค.
์๋ํํฐ ์ข ์์ฑ์ ๊ทน๋๋ก ์ต์ํํ๊ณ ํ์ด์ฌ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ฌ๋ ฅ์ ํ์ฉํ์ฌ ๊ฐ๋ณ๊ณ ๋น ๋ฅธ ๊ตฌ๋ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
-
ํ์ผ ๋ฌดํด์ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ
- ๋์ฉ๋ ์์ถ ํ์ผ(ZIP/CBZ)์ ์๋ฒ ๋๋ ํฐ๋ฆฌ์ ๋ฏธ๋ฆฌ ์์ถ ํด์ ํ์ง ์์ต๋๋ค.
- ๋ด์ฅ zipfile ๋ชจ๋์ ์ ์ดํ์ฌ ํ์ํ ํ์ด์ง์ ๋ฌผ๋ฆฌ์ ๋ฐ์ดํธ ์์ ์คํ์ ๋ง ์ฝ์ด ์คํธ๋ฆฌ๋ฐ ์ ์กํ๋ฏ๋ก ๋์คํฌ I/O์ CPU ์๋ชจ๋ฅผ ํ์ ์ ์ผ๋ก ์ ๊ฐํฉ๋๋ค.
-
๋ฏธ๋๋ฉํ ์ข ์์ฑ
- Flask ํ๋ ์์ํฌ ์ธ์ ๋ฌด๊ฑฐ์ด ORM์ด๋ ์๋ํํฐ ๋ชจ๋์ ๋ฐฐ์ ํ์ต๋๋ค.
- ๋ด์ฅ SQLite ๋ชจ๋์ ๊ณ ์ฑ๋ฅ ์ปค๋ฅ์ ํ๋ง๊ณผ ์ ๋ฐ ์ฟผ๋ฆฌ๋ฅผ ์ ํ ์ฑ๋ฅ ์ ํ ์๋ ์ด๊ฒฝ๋ ์ํคํ ์ฒ๋ฅผ ์คํํ์ต๋๋ค.
-
์ต์ ํ๋ ํ๋ก ํธ์๋ ํ์ดํ๋ผ์ธ
- ๋ทฐํฌํธ ๋ด ์นด๋๋ง ๋ก๋ํ๋ ์ง์ฐ ๋ก๋ฉ(Lazy Loading) ๋ฐ ๋ค์ ํ์ด์ง ํ๋ฆฌ๋ก๋ฉ(Preloading)์ ์ง์ํ์ฌ ๋๊น ์๋ ๋ ์ ๊ฒฝํ์ ๋ณด์ฅํฉ๋๋ค.
-
์ ์ฐํ ๋ฉํ๋ฐ์ดํฐ ํ๋ฌ๊ทธ์ธ
- ํ๋ฌ๊ทธ์ธ ์ํคํ ์ฒ๋ฅผ ๋ด์ฅํ์ฌ ๊ตญ๋ด ํ๊ฒฝ์ ํนํ๋ ์๋ผ๋ Open API ๋ฑ์ ์ธ๋ถ ์ ๋ณด ์๊ธ ์ฑ๋์ ํ๋ฌ๊ทธ์ธ ํํ๋ก ์ ์ดํฉ๋๋ค.
- ์ฑ ์๊ฐ๊ธ, ์๊ฐ ์ ๋ณด, ๊ณ ํ์ง ์ปค๋ฒ๋ฅผ ์ํด๋ฆญ์ผ๋ก ํตํฉ ๋ฐ์ํ๋ฉฐ ์๋ ๋ฉํ๋ฐ์ดํฐ ํธ์ง๊ธฐ ๋ฐ ํ์ง ๊ต์ฒด๋ฅผ ์ง์ํฉ๋๋ค.
-
๋ชจ๋ฐ์ผ ๋ทฐ์ด ํธํ
- ์ธ๋ถ ๋ทฐ์ด ์ ํ๋ฆฌ์ผ์ด์ ์ฐ๋์ ์ํ OPDS ๊ท๊ฒฉ์ ์ง์ํ๋ฉฐ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ธ์ฆ์ ๋ณด ๊ธฐ๋ฐ์ Basic Authentication ๋ณด์ ์ฒ๋ฆฌ๋ฅผ ์ ์ฉํ์ต๋๋ค.
-
์ ์ฐํ ์ค์บ ์์ธ ํํฐ๋ง (.bookoasisignore)
- ์๋๋ก์ง NAS ์ธ๋ค์ผ ํด๋(
@eaDir/), ํด์งํต(#recycle/), ์์ ํ์ผ(*.tmp,*.sample.cbz) ๋ฑ ์์ธ ํจํด์ ์ ์ญ ์ค์ ๋๋ ํด๋๋ณ.bookoasisignoreํ์ผ๋ก ๋ฑ๋กํ์ฌ ๋ถํ์ํ ์ค์บ ๋ฐ ๋์คํฌ ํ์ ์๋ชจ๋ฅผ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ฐจ๋จํฉ๋๋ค.
- ์๋๋ก์ง NAS ์ธ๋ค์ผ ํด๋(
BookOasis๊ฐ ๋์ฉ๋(10๋ง ๊ถ ์ด์) ํ๊ฒฝ์์๋ ๋ฒ๋ฒ ์ ์์ด ์ด๊ด์์ผ๋ก ๋์ํ๋ ๋น๋ฐ์ ๋๋ค!
- ์ด๊ณ ์ ์ค์บ๋: ๋์์ ์์ถํ์ผ์ ํด์ ํ์ง ์๊ณ ์ต์ ์คํ์ ๋ง ์ฝ์ด ์์๊ฐ์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํด DB์ ์๋ฉํฉ๋๋ค.
- ์ง์ฐ(Lazy) ์ค์บ๋: ์ฉ๋์ด ํฐ ๋งํ์ฑ ZIP ํ์ผ ๋ฑ์ ๋ฌด๋ฆฌํ๊ฒ ์ฆ์ ํ์ฑํ์ง ์๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋๋ฅธํ๊ฒ ๋น๋๊ธฐ ์ฒ๋ฆฌํ์ฌ ๋ฉ์ธ ๋์์ ๋ ์ ์ฐจ๋จํฉ๋๋ค.
์์ธํ ํ๊ฒฝ ๊ตฌ์ฑ ๋ฐ ์ค์น ๋ฐฉ๋ฒ์ ๊ธฐ์ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
- ์ค์น ์๋ด: ์ค์น ๊ฐ์ด๋ (docs/guide_installation.md)
- ์นด๋นํ ์ด์ฌํ๊ธฐ: ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋ (docs/migration_guide.md)
- ๊ด๋ฆฌ ๋ฐ ๊ด๋ฆฌ์ ์ค์ : ๊ด๋ฆฌ์ ๊ฐ์ด๋ (docs/guide_admin.md)
- ๋ชจ๋ฐ์ผ ๋ทฐ์ด ์ฐ๋: OPDS ์ฐ๋ ๊ฐ์ด๋ (docs/guide_opds.md)
- ์ํคํ ์ฒ ๋ฐ ์์ค ๊ตฌ์กฐ: ์ํคํ ์ฒ ๊ฐ์ด๋ (docs/guide_architecture.md)
- ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ ์ ๋ณด: ํ๋ฌ๊ทธ์ธ ๊ฐ๋ฐ ๊ฐ์ด๋ (docs/guide_plugins.md)
- ํ์ค ์ด๋ฒคํธ ์นํ ๋ช ์ธ: API ์๋ํฌ์ธํธ ๋ช ์ธ (docs/api_endpoints.md)
- ์ํค ํฌํธ: ๊ธฐ์ ์ํค ํ (docs/index.md)
์ด๋ฒคํธ ์นํ (
book.new,book.read,book.finish)์ ํฌ๋งท๋ณ ์งํ๋ฅ ์ ์ฝ์ ๋ฐ์ํฉ๋๋ค. EPUB/TXT๋ ๋ฌผ๋ฆฌ ํ์ด์ง๊ฐ ๊ณ ์ ๋์ง ์์totalPages๊ฐnull์ผ ์ ์์ผ๋ฉฐ, ์์ ์ธก์์๋progress(0~100)๋ฅผ ์ฐ์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
-
์ค์ ํ ํ๋ฆฟ ๋ณต์ฌ ๋ก์ปฌ ํ๊ฒฝ ๊ณ ์ ์ค์ ์ ์ํด ์ ๊ณต๋๋ ์ค๋ฒ๋ผ์ด๋ ํ ํ๋ฆฟ ํ์ผ์ ๋ณต์ฌํฉ๋๋ค.
cp docker-compose.override.example.yml docker-compose.override.yml
-
๋ณผ๋ฅจ ๊ฒฝ๋ก ์์ ์์ฑ๋
docker-compose.override.ymlํ์ผ์ ์ด์ด ๋ณธ์ธ์ ์ค์ ์ฑ /๋งํ์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก๋ก ์์ ํฉ๋๋ค.services: bookoasis: volumes: - /์ค์ /์ฑ /์ ์ฅ/๊ฒฝ๋ก:/data/comics:ro
-
์ปจํ ์ด๋ ์คํ
docker compose up -d
๊ธฐ๋ณธ
docker-compose.yml์ ๋ก์ปฌ ๋น๋ ์์ด GHCR(ghcr.io/leeyj/bookoasis:stable)์ ๋ฏธ๋ฆฌ ๋น๋๋์ด ๋ฐฐํฌ๋ ๊ณต์ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋๋ก ๋ฐ์์ต๋๋ค. Synology/Unraid ๋ฑ ์ ์ฌ์ NAS์์๋ ๋น ๋ฅด๊ฒ ๊ธฐ๋๋ฉ๋๋ค.
Tip:
docker-compose.override.yml์.gitignore์ ๋ฑ๋ก๋์ด ์์ผ๋ฏ๋ก ํฅํ ํ๋ก์ ํธ ์์ค๊ฐ ์ ๋ฐ์ดํธ๋์ดgit pull์ ๋ฐ์๋ ์ฌ์ฉ์์ ๊ฐ์ธ ๊ฒฝ๋ก ์ค์ ํ์ผ์ด ์ถฉ๋์ ์ผ์ผํค๊ฑฐ๋ ์ ์ค๋์ง ์์ต๋๋ค.
์์ค๋ฅผ ์ง์ ์์ ํ๋ ๊ฐ๋ฐ์๋ผ๋ฉด: ๋ก์ปฌ
Dockerfile์ ๋น๋ํด์ ์ฐ๋docker-compose.build.yml์ ๋์ ์ฌ์ฉํ์ธ์.docker compose -f docker-compose.build.yml -f docker-compose.override.yml up -d --build
๋ณด์ ์ ์ฑ ์ ๋ฐ๋ผ ์ด์์์ฉ ๋ฐฐํฌ/์ ๋ฐ์ดํธ ์ ์ฐจ๋ ๋น๊ณต๊ฐ ๋ด๋ถ ๋ฌธ์๋ก ๊ด๋ฆฌํฉ๋๋ค.
# ๊ฐ์ํ๊ฒฝ ํ์ฑํ ๋ฐ ์ข
์์ฑ ์ค์น
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# ์ค์ ํ์ผ ๊ตฌ์ฑ
# .env ๋ด SECRET_KEY ํญ๋ชฉ์ ์์ ๋ง์ ์์์ ๊ธด ๋ฌธ์์ด์ ์
๋ ฅํ์ฌ ๋ณด์ ๊ณ ์ ํค๋ฅผ ์์ฑํฉ๋๋ค.
# ์ด ํค๋ฅผ ๊ณ ์ ํด ์ฃผ์๋ฉด ์๋ฒ ๊ธฐ๋ ํ๋ก์ธ์ค๊ฐ ์ฌ์์๋๋๋ผ๋ ์ฌ์ฉ์ ์ธ์
(๋ก๊ทธ์ธ ์ํ)์ด ํ๋ฆฌ์ง ์๊ณ ์์ ํ๊ฒ ์ ์ง๋ฉ๋๋ค.
# ์๋ฒ ๊ธฐ๋
python core.py- ๊ธฐ๋ณธ ๋์: ๋์ปค ์ธ ํ๊ฒฝ์์๋ ์ค์บ๋ ์์ปค๊ฐ ์๋์ผ๋ก ํจ๊ป ๊ธฐ๋๋์ด ํ ๋๊ธฐ์ด(
pending)์ ์ฒ๋ฆฌํฉ๋๋ค. - ํ์ ์ ๋นํ์ฑํ:
BOOKOASIS_ENABLE_EMBEDDED_WORKER=0 python core.py
์๋์ฐ ํ๊ฒฝ์์๋ ๋ง์ฐ์ค ๋๋ธํด๋ฆญ ํ ๋ฒ์ผ๋ก ๊ตฌ๋ ๋๋ ํ ๋ฆฌ ์์ฑ ๋ฐ ํจํค์ง ์ค์น๋ถํฐ ํ๋ก๋์
์์ค์ ์น ์๋ฒ(waitress) ์คํ๊น์ง ์๋์ผ๋ก ์ํด๋ฆญ ์
์
ํด ์ฃผ๋ ๋ฐฐ์น ํ์ผ์ ์ ๊ณตํฉ๋๋ค.
.env.exampleํ์ผ์ ๋ณต์ฌํ์ฌ.envํ์ผ๋ก ์ด๋ฆ ๋ณ๊ฒฝ ํ ํ์ํ ์ค์ ์ ๋ ฅ.(SECURITY_KEY)- ํ๋ก์ ํธ ๋ฃจํธ์
run_windows.batํ์ผ์ ๋ง์ฐ์ค ๋๋ธํด๋ฆญํ์ฌ ๊ธฐ๋.
-
run_windows.bat๋ ์น ์๋ฒ์ ์ค์บ๋ ์์ปค๋ฅผ ํจ๊ป ๊ธฐ๋ํ๋๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. -
๋ก์ปฌ ํฌํธ:
http://localhost:5930
BookOasis๋ ์ค์๊ฐ ๋ ์ ์งํ๋ ์ ์ฅ ์ SQLite ํ์ผ์ ๋ฐ๋ก ์ ๊ทผํ์ง ์๊ณ ์ธ๋ฉ๋ชจ๋ฆฌ(RAM) ์บ์๋ก ์์ถฉํด ์ฃผ๋ Redis ํ์ด๋ธ๋ฆฌ๋ ์บ์ ๋ชจ๋๋ฅผ ์ง์ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋์คํฌ ์ฐ๊ธฐ ๋ณ๋ชฉ์ด ๊ฒฉ๊ฐํ๊ณ ์ค์บ ์ค์ DB ์์(Malformed) ํ์์ด ์๋ฒฝํ ๋ฐฉ์ง๋ฉ๋๋ค.
docker-compose.ymlํ ํ๋ฆฟ์redis:7-alpine์ด๋ฏธ์ง ์ฐ๋์ด ๋ด์ฅ๋์ด ์์ต๋๋ค..envํ์ผ์ ์์ ํ ํ์ ์์ดdocker compose up -d๋ช ๋ น์ด ํ๋๋ก ์ ์ฉ ๋ ๋์ค๊ฐ ์ผ์ง๊ณ ๊ฒฉ๋ฆฌ ๋คํธ์ํฌ ์์์ ์์ ํ ์๋์ผ๋ก ํตํฉ ์ฐ๋๋ฉ๋๋ค.- ์ปจํ
์ด๋ ์ข
๋ฃ ์ ์บ์๊ฐ ์ ์ค๋์ง ์๋๋ก
stop_grace_period์ด1m(1๋ถ)์ผ๋ก ์ํฅ ์ง์ ๋์ด ์์ต๋๋ค.
- ํธ์คํธ OS์ ๋ ๋์ค ์๋ฒ๋ฅผ ์ค์น ๋ฐ ๊ธฐ๋ํฉ๋๋ค. (Ubuntu ์์:
sudo apt install redis-server -y) .envํ์ผ ํ๋จ์ ์ ์ URL์ ๊ธฐ์ฌํด ์ค๋๋ค.REDIS_URL=redis://127.0.0.1:6379/9
- Tip: ๋ค๋ฅธ ๊ธฐ์กด ์๋น์ค์ ๋ ๋์ค๋ฅผ ๊ณต์ ํ๋ ๊ฒฝ์ฐ, DB ๋ฒํธ๋ฅผ
/9๋ฒ ๋ฑ ๋ค๋ฅธ ์ธ๋ฑ์ค๋ก ์ง์ ํ๋ฉด ์ถฉ๋์ด ์ ํ ์์ต๋๋ค. ๋ชจ๋ ๋ฐ์ดํฐ ํค ์์bookoasis:๋ค์์คํ์ด์ค๊ฐ ๊ฐ์ ์ง์ ๋ฉ๋๋ค.
- Tip: ๋ค๋ฅธ ๊ธฐ์กด ์๋น์ค์ ๋ ๋์ค๋ฅผ ๊ณต์ ํ๋ ๊ฒฝ์ฐ, DB ๋ฒํธ๋ฅผ
- ๋ ๋์ค๊ฐ ์ค๋น๋์ง ์์๊ฑฐ๋ ์ ์์ ์คํจํ๋ฉด ์๋์ผ๋ก ๊ธฐ์กด์ SQLite ์ง์ ์ฐ๊ธฐ ๋ชจ๋๋ก ์ฐํ(Fallback) ํ๋ฏ๋ก ์์ฌํ๊ณ ์ฌ์ฉํ์ ๋ ๋ฉ๋๋ค.
BookOasis๋ ๊ธฐ๋ณธ ํ์ผ ๊ธฐ๋ฐ SQLite ์์ง ์ธ์๋, 10๋ง ๊ถ ์ด์์ ๋๊ท๋ชจ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๋ฉํฐ ์ฌ์ฉ์/๋์ ์ ์ ํ๊ฒฝ์ ์ํ MariaDB ์ํฐํ๋ผ์ด์ฆ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ง์ ๊ณต์ ์ง์ํฉ๋๋ค.
| ๊ธฐ๋ฅ / ํน์ฑ | SQLite (๊ธฐ๋ณธ๊ฐ) | MariaDB (์ ํ/๊ถ์ฅ) |
|---|---|---|
| ์ค์น ๋ฐ ๊ตฌ๋ | ์ค์ ์์ด ์ฆ์ ๊ตฌ๋ (ํ์ผ DB) | Docker Compose ๋๋ ๋ณ๋ MariaDB ์ฐ๋ |
| ์ ํฉํ ๊ท๋ชจ | ๊ฐ์ธ / ์ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (5๋ง ๊ถ ์ดํ) | ๋๊ท๋ชจ / ์ํฐํ๋ผ์ด์ฆ (10๋ง ๊ถ ์ด์, ๋์ฉ๋ ์ค์บ) |
| ๋์์ฑ ๋ฐ I/O | ํ์ผ ๋ฝ ๊ธฐ๋ฐ (Redis ์บ์ ๊ถ์ฅ) | ํ ๋จ์ ๋ฝ(Row-level lock) ๊ณ ์ ๋ถ์ฐ ์ฒ๋ฆฌ |
| ๋ฐ์ดํฐ ์์ ์ฑ | ๋จ์ผ ํ์ผ ๋ณด๊ด | DB ํธ๋์ญ์ , ์๋ ๋ณต๊ตฌ, ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ |
-
Docker ์ฌ์ฉ์ (์๋/์ํด๋ฆญ)
docker-compose.override.yml์DB_ENGINE=mariadb๋ฐ ์ ์ ์ ๋ณด๋ฅผ ์ถ๊ฐํ ๋ค ์ปจํ ์ด๋๋ฅผ ์คํํ๋ฉด ํ์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค(media_general,media_adult,media_audiobook) ๋ฐ ์คํค๋ง๊ฐ ์๋ ๊ตฌ์ถ๋ฉ๋๋ค.- GHCR ๊ณต์ ์ด๋ฏธ์ง๋ก ๋ก์ปฌ ๋น๋ ์์ด ๋ฐ๋ก ๊ธฐ๋ํ๋ ค๋ฉด
docker-compose.mariadb.ghcr.yml์ ์ฌ์ฉํ์ธ์.์์ค๋ฅผ ์ง์ ๋น๋ํ๋ ค๋ฉด ๊ธฐ์กดdocker compose -f docker-compose.mariadb.ghcr.yml -f docker-compose.override.yml up -d
docker-compose.mariadb.yml(๋ก์ปฌDockerfile๋น๋)์ ๊ทธ๋๋ก ์ฌ์ฉํ์๋ฉด ๋ฉ๋๋ค. - ์์ธ ์ด๊ด ์๋ด: Docker ์ฌ์ฉ์์ฉ MariaDB ๋ง์ด๊ทธ๋ ์ด์ ๊ฐ์ด๋ (docs/move_to_mariadb.md)
-
1-Click ๋ฐ์ดํฐ ๋ง์ด๊ทธ๋ ์ด์ (SQLite โ MariaDB)
- ๊ธฐ์กด์ ์ฝ๋ ์ฑ , ์ค๋์ค๋ถ ์งํ๋, ์ฆ๊ฒจ์ฐพ๊ธฐ, ์ฌ์ฉ์ ๊ณ์ ๋ฑ ๊ธฐ์กด SQLite์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์์ค ์์ด ํด๋ฆญ ํ ๋ฒ์ผ๋ก MariaDB๋ก ์ด๊ดํ ์ ์์ต๋๋ค.
python tools/migrator_sqlite_to_mariadb.py
- ์ต์ด ์๋ฒ ๊ตฌ๋ ํ ๋ก๊ทธ์ธ ์ ์๋์ ๊ด๋ฆฌ์ ๊ณ์ ์ ์ฌ์ฉํ์ฌ ์ ์ํ ์ ์์ต๋๋ค.
- ์ฌ์ฉ์ ์ด๋ฆ(ID):
admin - ๋น๋ฐ๋ฒํธ:
admin
- ์ฌ์ฉ์ ์ด๋ฆ(ID):
- ๋ณด์ ๊ถ์ฅ: ์ฒซ ๋ก๊ทธ์ธ ํ ์ฆ์ [์ค์ ] > [๊ณ์ ๊ด๋ฆฌ] ํญ์์ ๊ด๋ฆฌ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ณ๊ฒฝํด ์ฃผ์ธ์.
ํด์ธ ํ๋ฉ ์ ์ ๋ฐ OIDC ์ฐ๋์ ์ํด Proxy Header Auth (๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ ๋ก๊ทธ์ธ) ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.
Authelia, Authentik ๋ฑ ์๋จ์ ๋ฆฌ๋ฒ์ค ํ๋ก์ ์ธ์ฆ ์๋ฒ๊ฐ ๊ฒ์ฆ์ ๋ง์น๊ณ ์ ๋ฌํ๋ HTTP ํค๋(Remote-User ๋๋ X-Forwarded-User) ๊ฐ์ ์ ๋ขฐํ์ฌ ์๋์ผ๋ก BookOasis์ ๋ก๊ทธ์ธ(SSO)์ํฌ ์ ์์ต๋๋ค.
Caution
์ฌ๊ฐํ ๋ณด์ ๊ฒฝ๊ณ
์ด ๊ธฐ๋ฅ์ ๋ฐ๋์ Nginx, Authelia ๋ฑ ๋ฆฌ๋ฒ์ค ํ๋ก์๊ฐ ํค๋๋ฅผ ๋ณ์กฐ ๋ฐ ๋ณดํธํ๋ ํ์๋ง ํ๊ฒฝ์์๋ง ํ์ฑํํด์ผ ํฉ๋๋ค!
ํ๋ก์ ์์ด ์ธํฐ๋ท์ ๊ฐ๋ฐฉ๋ ์ํ์์ ์ด ์ต์
์ ์ผค ๊ฒฝ์ฐ, ์
์์ ์ธ ์ฌ์ฉ์๊ฐ ํค๋ ์กฐ์(์: Remote-User: admin)๋ง์ผ๋ก ๊ด๋ฆฌ์ ๊ถํ์ ํ์ทจํ ์ ์๋ ์ฌ๊ฐํ ์ทจ์ฝ์ ์ด ๋ฉ๋๋ค. ์ํ์ฑ์ ์ถฉ๋ถํ ์ธ์งํ ๋ถ๋ง ์ฌ์ฉํ์ธ์.
์ค์ ๋ฐฉ๋ฒ:
- ์ด๋๋ฏผ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ ํ ์ผ๋ฐ ์ค์ ๋ฉ๋ด์ ์ง์ ํฉ๋๋ค.
- ์คํฌ๋กค์ ๋ด๋ ค Proxy Header Auth (๋ฆฌ๋ฒ์ค ํ๋ก์ ์๋ ๋ก๊ทธ์ธ) ํญ๋ชฉ์ ์ฐพ์ ํ ๊ธํฉ๋๋ค.
- ์ค์ ์ ์ ์ฅํ ๋ค, ์๋จ์ Nginx/ํ๋ก์ ์๋ฒ๊ฐ ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ์๋ช ํค๋๋ฅผ BookOasis ๋ก ๋๊ฒจ์ฃผ๋๋ก ๊ตฌ์ฑํ์ญ์์ค.
์ ํ ๋ณด์ ์ต์ (๊ถ์ฅ):
PROXY_HEADER_TRUSTED_IPS: ํ๋ก์ ์๋ณธ IP/CIDR ํ์ดํธ๋ฆฌ์คํธ (์:127.0.0.1,10.0.0.0/8,192.168.0.0/16)- ์ด ๊ฐ์ด ์ค์ ๋๋ฉด, ํ์ดํธ๋ฆฌ์คํธ์ ์๋ ์์ค IP์์ ์ ๋ฌ๋
Remote-User/X-Forwarded-Userํค๋๋ ๋ฌด์๋ฉ๋๋ค.
- ์ด ๊ฐ์ด ์ค์ ๋๋ฉด, ํ์ดํธ๋ฆฌ์คํธ์ ์๋ ์์ค IP์์ ์ ๋ฌ๋
PROXY_HEADER_DENY_DIRECT:1๋ก ์ค์ ์, ํ๋ก์ ํค๋๊ฐ ์๋ ์ง์ ์ ์์ ๊ฑฐ๋ถ (reverse proxy ๊ฒฝ์ ๊ฐ์ )
์ ์ต์ ์ DB ์ค์ (
settingsํ ์ด๋ธ) ๋๋ ํ๊ฒฝ๋ณ์(.env)๋ก ์ค์ ํ ์ ์์ต๋๋ค. DB ์ค์ ๊ฐ์ด ์ฐ์ ์ ์ฉ๋ฉ๋๋ค.
- ์ ์ญ ์ค์ (nginx.conf) ์ ๋ฐ์ดํธ Cloudflare๋ฅผ ํตํ๋ ์์ฒญ์ ์ผ๋ฐ์ ์ธ ์์ฒญ๋ณด๋ค ํค๋ ํฌ๊ธฐ(์ฟ ํค, ์ธ์ฆ ํ ํฐ, ํ๋ก์ ํค๋ ๋ฑ)๊ฐ ํจ์ฌ ๋น๋ํฉ๋๋ค. Nginx์ ๊ธฐ๋ณธ ํค๋ ๋ฒํผ ํฌ๊ธฐ๊ฐ ์์ผ๋ฉด ์๋ฒ๊ฐ ์์ฒญ์ ๊ฑฐ๋ถํ๊ณ 400 Bad Request ๋๋ 414 Request-URI Too Large ์๋ฌ๋ฅผ ๋ฐํํฉ๋๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด /etc/nginx/nginx.conf ํ์ผ์ http { ... } ๋ธ๋ก ๋ด๋ถ์ ์๋ ์ค์ ์ ๋ฐ๋์ ์ถ๊ฐํ๊ฑฐ๋ ํ์ฅํด ์ฃผ์ธ์.
http {
# ------------------------------------------------------------------
# [Cloudflare ๋ฐ ๋์ฉ๋ ํค๋ ๋์] ๋ฒํผ ํฌ๊ธฐ ๋ํญ ํ์ฅ
# ์ฟ ํค ๋ฐ ์ธ์ฆ ํค๋๊ฐ ๊ธธ์ด์ง ๋ ๋ฐ์ํ๋ 4XX ์๋ฌ๋ฅผ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
# ------------------------------------------------------------------
client_header_buffer_size 16k;
large_client_header_buffers 4 64k;
# ------------------------------------------------------------------
# ํ๋ก์ ํค๋ ํด์ ํฌ๊ธฐ ์ต์ ํ
# ๋ค์์ ๋ณต์กํ X-Forwarded-* ํค๋๊ฐ ์ ์
๋์ด๋ ํด์ ํ
์ด๋ธ ์ค๋ฒํ๋ก์ฐ๊ฐ ๋์ง ์๋๋ก ํฉ๋๋ค.
# ------------------------------------------------------------------
proxy_headers_hash_max_size 1024;
proxy_headers_hash_bucket_size 128;
# (๊ธฐ์กด์ ๋ค๋ฅธ ์ค์ ๋ค...)
}
- BookOasis ๊ฐ์ ํธ์คํธ ์ค์ (sites-available) /etc/nginx/sites-available/default ํ์ผ(๋๋ ๊ฐ์ ํธ์คํธ ์ค์ ๋ธ๋ก)์ ์๋ ๋ด์ฉ์ ์์ฑํ๊ณ , sites-enabled์ ์ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ๊ฑธ์ด ์ ์ฉํฉ๋๋ค.
# ------------------------------------------------------------------
# ๋ฐฑ์๋ ์๊ตฌ ์ปค๋ฅ์
ํ ์ค์ (์์ผ ์์ฑ ์ง์ฐ 0ms)
# ------------------------------------------------------------------
upstream bookoasis_backend {
server 127.0.0.1:5930;
keepalive 64; # 64๊ฐ ๋ฐฑ์๋ ์ปค๋ฅ์
์ ์๊ตฌ ์ ์งํ์ฌ ์ฐ๊ฒฐ ์๋ชจ ๋ฐฉ์ง
}
server {
listen 80;
server_name your-domain.com; # <== ๋ณธ์ธ์ ๋๋ฉ์ธ์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
# HTTP๋ก ๋ค์ด์ค๋ ์์ฒญ์ HTTPS๋ก ๊ฐ์ ๋ฆฌ๋ค์ด๋ ํธ (๋ณด์)
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri/;
}
# ------------------------------------------------------------------
# Gzip ํ
์คํธ ๋ฐ SVG ์์ถ ์ค์ (UI ๋ฐ JSON API ๊ฐ์)
# ------------------------------------------------------------------
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
# ๋ฉํ๋ฐ์ดํฐ ํ๋ฌ๊ทธ์ธ ๋ฐ ๋์ฉ๋ ํ์ผ ์
๋ก๋๋ฅผ ๊ณ ๋ คํ ๋ฐ๋ ํฌ๊ธฐ ์ ํ
client_max_body_size 100M;
# ------------------------------------------------------------------
# ๋ณด์ ํค๋ ์ฃผ์
# ------------------------------------------------------------------
add_header X-Frame-Options "SAMEORIGIN" always; # ํด๋ฆญ์ฌํน ๋ฐฉ์ง
add_header X-Content-Type-Options "nosniff" always; # MIME ์ค๋ํ ๋ฐฉ์ง
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# ------------------------------------------------------------------
# 1. ์ปค๋ฒ ์ด๋ฏธ์ง ๋ผ์ฐํ
์ต์ ํ
# ------------------------------------------------------------------
# 1-1. ๋์ SVG ์ปค๋ฒ ์์ฑ API (๋ฐฑ์๋ ํ์ด์ฌ์ผ๋ก ์ ๋ฌ)
location /covers/fallback {
proxy_pass http://bookoasis_backend;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
# 1-2. ์ปค๋ฒ ์ด๋ฏธ์ง ์ ์ ์๋น ์ต์ ํ (ํ์ด์ฌ ์ฐํ ๋ค์ด๋ ํธ 0ms ์๋น)
location /covers/ {
alias /path/to/media_server/covers/; # <== ๋ณธ์ธ์ ์ค์น ๊ฒฝ๋ก/covers/ ๋ก ๋ณ๊ฒฝํ์ธ์.
expires 1d;
add_header Cache-Control "public, max-age=86400";
sendfile on;
tcp_nopush on;
}
# ------------------------------------------------------------------
# 2. ์ผ๋ฐ API ๋ฐ ๋ฉ์ธ ์น ์๋น์ค (WebSocket ์ง์)
# ------------------------------------------------------------------
location / {
proxy_pass http://bookoasis_backend/;
# [๊ธฐ๋ณธ ํ๋ก์ ํค๋ ์ค์ ]
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
# [WebSocket ๋ฐ HTTP/1.1 ํ๋กํ ์ฝ ์
๊ทธ๋ ์ด๋ ์ง์]
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# [CRITICAL] ๋์ฉ๋ ํ์ผ ์ ์ก ์ต์ ํ (ํ๋ก์ ๋ฒํผ๋ง ๋)
proxy_buffering off;
# [ํ์์์ ํ์ฅ] ๋์ฉ๋ ์ค์บ ํ๋ก์ธ์ค ๋ฐ ์ฅ์๊ฐ ์์
๋์
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
}์ค๋์ค๋ถ ์ค์บ์ด ์ ์(BOOK_PROCESSED, duration_sec > 0)์ด์ด๋, ์๋จ ํ๋ก์/CDN์์ Range ์์ฒญ์ด ๋ณํ๋๊ฑฐ๋ ์บ์๊ฐ ์๋ชป ์ ์ฉ๋๋ฉด ์ฌ์์ด ๋ฉ์ถ๊ฑฐ๋ 0:00์ผ๋ก ๋ณด์ผ ์ ์์ต๋๋ค.
- Cloudflare ์บ์ ์ฐํ ๊ท์น ์ถ๊ฐ (๊ถ์ฅ)
- ๊ฒฝ๋ก ์์:
/api/media/audiobooks/*/tracks/*/stream
- Cache Level: Bypass
- Performance/Optimization ๊ณ์ด์์ ์ค๋์ค ์คํธ๋ฆผ URL์ ๋ํ ๋ณํ(Transform/Minify/Polish ๋ฑ)์ด ์ ์ฉ๋์ง ์๋๋ก ์ ์ธ
- Nginx์์ ์คํธ๋ฆฌ๋ฐ ๊ฒฝ๋ก ๋ฒํผ๋ง ๋นํ์ฑํ (๊ถ์ฅ)
- ์ ์ฒด
location /์proxy_buffering off;๋ฅผ ์ฌ์ฉ ์ค์ด๋ผ๋ฉด ๊ทธ๋๋ก ์ ์ง - ๋ณ๋ location์ ๋ถ๋ฆฌํ๋ ๊ฒฝ์ฐ ์๋์ฒ๋ผ ๋์ผ ์ ์ฑ ์ ์ง
location ~ ^/api/media/audiobooks/.*/tracks/.*/stream$ {
proxy_pass http://bookoasis_backend;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
# ์ค๋์ค ์คํธ๋ฆฌ๋ฐ ์ง์ฐ/๋๊น ๋ฐฉ์ง
proxy_buffering off;
# ์ฅ์๊ฐ ์ฒญ์ทจ ์ธ์
๋์
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}- ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์๋๊ตฌ Network์์ ์คํธ๋ฆผ ์์ฒญ ์ํ ์ฝ๋๊ฐ
206 Partial Content์ธ์ง ํ์ธ - ์คํธ๋ฆผ ์์ฒญ์ด ๋ฐ๋ณต
404๋ผ๋ฉด DB ๊ฒฝ๋ก๊ฐ ์๋๋ผ ์คํ ์ค์ธ ์น ํ๋ก์ธ์ค์ ์ค์ ํ์ผ ์ ๊ทผ ๊ฒฝ๋ก/๋ง์ดํธ ์ํ๋ฅผ ์ฐ์ ์ ๊ฒ - ์ฌ์ ์คํจ ์งํ
current_time=0์ ์ฅ์ด ๋ค์ด๊ฐ ์ ์์ผ๋ฏ๋ก, ์์ธ ํ์ ์ ์๋ ์คํจ ์ฌ์ ๋ฐ๋ณต ํ ์คํธ๋ฅผ ํผํ๊ณ ์คํธ๋ฆผ ์๋ต ์ฝ๋๋ถํฐ ํ์ธ
Nginx ๋์ Caddy๋ฅผ ๋ฆฌ๋ฒ์ค ํ๋ก์๋ก ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์๋์ ๊ฐ์ด Caddyfile์ ๊ตฌ์ฑํ์ฌ ์ต์ ํ ๋ฐ ์ฐ๋์ ์งํํ ์ ์์ต๋๋ค. Caddy๋ ์๋์ผ๋ก SSL ์ธ์ฆ์(HTTPS)๋ฅผ ๋ฐ๊ธ ๋ฐ ๊ด๋ฆฌํด์ฃผ๋ฉฐ, WebSocket๊ณผ ๋ฆฌ๋ฒ์ค ํ๋ก์ ํค๋ ์ฒ๋ฆฌ๋ฅผ ๊ธฐ๋ณธ ๋ด์ฅํ๊ณ ์์ต๋๋ค.
/etc/caddy/Caddyfile ํ์ผ์ ๋ณธ์ธ์ ๋๋ฉ์ธ์ ๋งคํํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ๋ด์ฉ์ ๊ตฌ์ฑํฉ๋๋ค.
your-domain.com { # <== ๋ณธ์ธ์ ๋๋ฉ์ธ์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
# ------------------------------------------------------------------
# Gzip ๋ฐ Zstd ํ
์คํธ ์์ถ ์ค์ (UI ๋ฐ JSON API ๊ฐ์)
# ------------------------------------------------------------------
encode gzip zstd
# ------------------------------------------------------------------
# ์ต๋ ๋ฐ๋ ํฌ๊ธฐ ์ ํ (Nginx client_max_body_size 100M ๋์)
# ์๋ผ๋ ๋ฉํ๋ฐ์ดํฐ ํ๋ฌ๊ทธ์ธ ๋ฑ ์ปค๋ฒ ์ด๋ฏธ์ง ์
๋ก๋๋ฅผ ๊ณ ๋ คํ ํ๋์
๋๋ค.
# ------------------------------------------------------------------
request_body {
max_size 100mb
}
# ------------------------------------------------------------------
# ๋ฉ์ธ ์ดํ๋ฆฌ์ผ์ด์
ํ๋ก์ ๋ผ์ฐํ
# ------------------------------------------------------------------
reverse_proxy 127.0.0.1:5930 {
# [CRITICAL] ๋์ฉ๋ ํ์ผ ์ ์ก ์ต์ ํ (ํ๋ก์ ๋ฒํผ๋ง ๋นํ์ฑํ)
# BookOasis์ ํต์ฌ ๊ธฐ๋ฅ์ธ '์คํ์
๊ธฐ๋ฐ ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ'์ด ํ๋ก์ ๋ฒํผ์
# ๊ฐ๋ก๋งํ์ง ์๊ณ ์ฆ์ ์ ์ก๋๋๋ก ์ค์ ํฉ๋๋ค.
flush_interval -1
}
}๋ณ๊ฒฝ ์๋ฃ ํ sudo systemctl reload caddy ๋ช
๋ น์ ํตํด Caddy ์ค์ ์ ๊ฐฑ์ ํฉ๋๋ค.
- ์คํ์์ค ๋ผ์ด์ ์ค: ๋ณธ ํ๋ก์ ํธ๋ GNU AGPLv3 (Affero General Public License v3.0) ๊ท๊ฒฉ์ ๋ฐ๋ฆ ๋๋ค. ๋๊ตฌ๋ ์์ค ์ฝ๋๋ฅผ ์ด๋, ์์ , ๋ฐฐํฌํ ์ ์์ผ๋ฉฐ, ์ด ์ํํธ์จ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์๋น์ค(๋คํธ์ํฌ๋ฅผ ํตํ ์๋น์ค ํฌํจ)๋ฅผ ์ ๊ณตํ ๊ฒฝ์ฐ ๋ฐ๋์ ์์ ๋ ์์ค ์ฝ๋๋ฅผ ๋์ผํ AGPLv3 ๋ผ์ด์ ์ค๋ก ๊ณต๊ฐํด์ผ ํฉ๋๋ค. ์์ธํ ์ฝ๊ด์ LICENSE ํ์ผ์ ์ฐธ์กฐํ์ญ์์ค.
- ์ํ๊ถ ๋ณดํธ ๊ฐ์ด๋๋ผ์ธ: "BookOasis" ๋ช ์นญ ๋ฐ ๊ด๋ จ ๊ณต์ ๋ก๊ณ ์ด๋ฏธ์ง๋ ์์์์ ๊ณ ์ ์๋ณ ์ํ(Trademark)๋ก ๋ณดํธ๋ฉ๋๋ค. ๋ณธ ํ๋ก๊ทธ๋จ์ ํฌํฌ(Fork) ๋ฐ ๋ณ๊ฒฝํ์ฌ ์ฌ๋ฐฐํฌ ์ ์๋ณธ ์์ค์ฝ๋์ ์ ์๊ถ ํ๊ธฐ๋ ์ฑ์คํ ์ค์ํ๋, ๋์ผํ ๋ช ์นญ("BookOasis")๊ณผ ๊ณต์ ๋ก๊ณ ๋ฅผ ์ ํ๋ช ์ ์ฌ์ฉํ์ฌ ์ฌ๋ฐฐํฌํ ์ ์์ผ๋ฉฐ ๋ฐ๋์ ์ด๋ฆ์ ๋ค๋ฅด๊ฒ ๋ณ๊ฒฝํ์ฌ ์๋ณ๋๋๋ก ํด์ผ ํฉ๋๋ค.
Copyright ยฉ 2026 leeyj (Carls, leeyj78@gmail.com). All rights reserved.

