mirror of
https://github.com/okxlin/appstore.git
synced 2026-10-07 00:00:31 +00:00
Add librarr app
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
PANEL_APP_PORT_HTTP=5050
|
||||
APP_DATA_DIR=./data/librarr
|
||||
APP_EBOOK_DIR=./data/ebooks
|
||||
APP_AUDIOBOOK_DIR=./data/audiobooks
|
||||
APP_MANGA_DIR=./data/manga
|
||||
CONTAINER_NAME=librarr-compose-check
|
||||
@@ -0,0 +1,83 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 5050
|
||||
edit: true
|
||||
envKey: PANEL_APP_PORT_HTTP
|
||||
labelEn: HTTP Port
|
||||
labelZh: HTTP 端口
|
||||
label:
|
||||
en: HTTP Port
|
||||
zh: HTTP 端口
|
||||
zh-Hant: HTTP 埠
|
||||
ja: HTTP ポート
|
||||
ko: HTTP 포트
|
||||
ru: HTTP-порт
|
||||
ms: Port HTTP
|
||||
pt-br: Porta HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
- default: ./data/librarr
|
||||
edit: true
|
||||
envKey: APP_DATA_DIR
|
||||
labelEn: Application data directory
|
||||
labelZh: 应用数据目录
|
||||
label:
|
||||
en: Application data directory
|
||||
zh: 应用数据目录
|
||||
zh-Hant: 應用資料目錄
|
||||
ja: アプリケーションデータディレクトリ
|
||||
ko: 애플리케이션 데이터 디렉터리
|
||||
ru: Каталог данных приложения
|
||||
ms: Direktori data aplikasi
|
||||
pt-br: Diretório de dados do aplicativo
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/ebooks
|
||||
edit: true
|
||||
envKey: APP_EBOOK_DIR
|
||||
labelEn: Ebook directory
|
||||
labelZh: 电子书目录
|
||||
label:
|
||||
en: Ebook directory
|
||||
zh: 电子书目录
|
||||
zh-Hant: 電子書目錄
|
||||
ja: 電子書籍ディレクトリ
|
||||
ko: 전자책 디렉터리
|
||||
ru: Каталог электронных книг
|
||||
ms: Direktori e-buku
|
||||
pt-br: Diretório de ebooks
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/audiobooks
|
||||
edit: true
|
||||
envKey: APP_AUDIOBOOK_DIR
|
||||
labelEn: Audiobook directory
|
||||
labelZh: 有声书目录
|
||||
label:
|
||||
en: Audiobook directory
|
||||
zh: 有声书目录
|
||||
zh-Hant: 有聲書目錄
|
||||
ja: オーディオブックディレクトリ
|
||||
ko: 오디오북 디렉터리
|
||||
ru: Каталог аудиокниг
|
||||
ms: Direktori buku audio
|
||||
pt-br: Diretório de audiolivros
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/manga
|
||||
edit: true
|
||||
envKey: APP_MANGA_DIR
|
||||
labelEn: Manga directory
|
||||
labelZh: 漫画目录
|
||||
label:
|
||||
en: Manga directory
|
||||
zh: 漫画目录
|
||||
zh-Hant: 漫畫目錄
|
||||
ja: 漫画ディレクトリ
|
||||
ko: 만화 디렉터리
|
||||
ru: Каталог манги
|
||||
ms: Direktori manga
|
||||
pt-br: Diretório de mangás
|
||||
required: true
|
||||
type: text
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
librarr:
|
||||
image: "ghcr.io/jeremiahm37/librarr:v1.1.3"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
restart: always
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=512M
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- 1panel-network
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP}:5050"
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}:/data"
|
||||
- "${APP_EBOOK_DIR}:/books/ebooks"
|
||||
- "${APP_AUDIOBOOK_DIR}:/books/audiobooks"
|
||||
- "${APP_MANGA_DIR}:/books/manga"
|
||||
environment:
|
||||
- LIBRARR_PORT=5050
|
||||
- LIBRARR_DB_PATH=/data/librarr.db
|
||||
- EBOOK_DIR=/books/ebooks
|
||||
- AUDIOBOOK_DIR=/books/audiobooks
|
||||
- MANGA_DIR=/books/manga
|
||||
- WISHLIST_CLEANUP_ENABLED=false
|
||||
- OIDC_PROXY_HEADERS_ENABLED=false
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:5050/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 9
|
||||
start_period: 15s
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
|
||||
LIBRARR_UID=1000
|
||||
LIBRARR_GID=1000
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local value=""
|
||||
value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
|
||||
case "$value" in
|
||||
\"*\") value="${value#\"}"; value="${value%\"}" ;;
|
||||
\'*\') value="${value#\'}"; value="${value%\'}" ;;
|
||||
esac
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
reject_symlink_components() {
|
||||
local path="$1"
|
||||
local current=""
|
||||
local component
|
||||
IFS='/' read -r -a components <<< "${path#/}"
|
||||
for component in "${components[@]}"; do
|
||||
[[ -n "$component" ]] || continue
|
||||
current="${current}/${component}"
|
||||
[[ ! -L "$current" ]] || fail "$path must not contain symbolic-link components"
|
||||
done
|
||||
}
|
||||
|
||||
prepare_media_dir() {
|
||||
local key="$1"
|
||||
local raw=""
|
||||
local candidate=""
|
||||
local resolved=""
|
||||
local scope=""
|
||||
local existed=false
|
||||
|
||||
raw="$(read_env_value "$key")"
|
||||
[[ -n "$raw" ]] || fail "$key must not be empty"
|
||||
[[ "$raw" =~ ^[A-Za-z0-9._/\ -]+$ ]] || fail "$key contains unsupported characters"
|
||||
|
||||
if [[ "$raw" == /* ]]; then
|
||||
candidate="$raw"
|
||||
scope="absolute"
|
||||
else
|
||||
candidate="${ROOT_DIR}/${raw#./}"
|
||||
scope="application"
|
||||
fi
|
||||
|
||||
reject_symlink_components "$candidate"
|
||||
resolved="$(realpath -m -- "$candidate")" || fail "Unable to resolve $key"
|
||||
[[ "$resolved" != "/" ]] || fail "$key must not be the filesystem root"
|
||||
if [[ "$scope" == "application" ]]; then
|
||||
case "$resolved" in
|
||||
"${ROOT_DIR}"/*) ;;
|
||||
*) fail "Relative $key must stay inside the application version directory" ;;
|
||||
esac
|
||||
fi
|
||||
[[ ! -e "$resolved" || -d "$resolved" ]] || fail "$key must be a directory"
|
||||
[[ -d "$resolved" ]] && existed=true
|
||||
|
||||
if [[ "$scope" == "absolute" && "$existed" == "true" ]]; then
|
||||
local incompatible_owner=""
|
||||
incompatible_owner="$(find "$resolved" -xdev \( ! -uid "$LIBRARR_UID" -o ! -gid "$LIBRARR_GID" \) -print -quit)" || \
|
||||
fail "Unable to inspect $key ownership"
|
||||
[[ -z "$incompatible_owner" ]] || fail "Existing absolute $key tree must be owned by ${LIBRARR_UID}:${LIBRARR_GID}"
|
||||
fi
|
||||
|
||||
install -d -m 0750 -o "$LIBRARR_UID" -g "$LIBRARR_GID" -- "$resolved" || \
|
||||
fail "Unable to prepare $key"
|
||||
local verified=""
|
||||
verified="$(realpath -e -- "$resolved")" || fail "Unable to verify $key"
|
||||
[[ "$verified" == "$resolved" ]] || fail "$key changed while it was being prepared"
|
||||
if [[ "$scope" == "application" ]]; then
|
||||
case "$verified" in
|
||||
"${ROOT_DIR}"/*) ;;
|
||||
*) fail "$key resolves outside the application version directory" ;;
|
||||
esac
|
||||
chown -R --no-dereference "$LIBRARR_UID:$LIBRARR_GID" "$verified" || \
|
||||
fail "Unable to set $key ownership"
|
||||
elif [[ "$existed" == "false" ]]; then
|
||||
chown "$LIBRARR_UID:$LIBRARR_GID" "$verified" || fail "Unable to set $key ownership"
|
||||
fi
|
||||
chmod 0750 "$verified" || fail "Unable to set $key permissions"
|
||||
printf '%s\n' "$verified"
|
||||
}
|
||||
|
||||
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
|
||||
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
|
||||
[[ "$(id -u)" -eq 0 ]] || fail "Librarr init must run as root to prepare media-directory ownership"
|
||||
|
||||
data_dir="$(prepare_media_dir APP_DATA_DIR)"
|
||||
ebook_dir="$(prepare_media_dir APP_EBOOK_DIR)"
|
||||
audiobook_dir="$(prepare_media_dir APP_AUDIOBOOK_DIR)"
|
||||
manga_dir="$(prepare_media_dir APP_MANGA_DIR)"
|
||||
|
||||
[[ "$data_dir" != "$ebook_dir" && "$data_dir" != "$audiobook_dir" && "$data_dir" != "$manga_dir" && \
|
||||
"$ebook_dir" != "$audiobook_dir" && "$ebook_dir" != "$manga_dir" && "$audiobook_dir" != "$manga_dir" ]] || \
|
||||
fail "Librarr data and media directories must be distinct"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Media directories contain user-managed files and are never deleted here.
|
||||
exit 0
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Librarr applies SQLite migrations in the upstream binary during startup.
|
||||
exit 0
|
||||
@@ -0,0 +1,48 @@
|
||||
## 产品介绍
|
||||
|
||||
**Librarr** 是一个面向电子书、有声书和漫画的自托管媒体管理器。它提供统一的 Web 界面,用于管理本地媒体库、愿望单、搜索和下载流程。
|
||||
|
||||
## 主要功能
|
||||
|
||||
- 分别管理电子书、有声书和漫画目录。
|
||||
- 维护愿望单、阅读记录、标签和作者监控。
|
||||
- 支持上传本地媒体文件并整理到对应媒体目录。
|
||||
- 可按需连接 qBittorrent、Audiobookshelf、Kavita、Komga 等外部服务。
|
||||
|
||||
## 访问说明
|
||||
|
||||
- 安装完成后打开 Web 端口。
|
||||
- 首次访问时注册管理员账号;首个用户会自动成为管理员。
|
||||
- 媒体目录默认为应用版本目录下的 `data/ebooks`、`data/audiobooks` 和 `data/manga`,也可在安装表单中改为其他宿主机路径。
|
||||
|
||||
## 数据与升级
|
||||
|
||||
- 用户、愿望单、设置和活动记录保存在应用数据目录中的 SQLite 数据库里。
|
||||
- 三类媒体文件保存在安装表单所选的宿主机目录中。
|
||||
- 应用限制为单实例,以避免多个实例争用同一数据目录。
|
||||
- 卸载脚本不会删除媒体目录。卸载或升级前请备份数据库卷和媒体目录。
|
||||
- `latest` 为浮动镜像,适合通过容器更新工具跟踪上游发布;固定版本用于可重复部署。
|
||||
|
||||
## 默认工作流
|
||||
|
||||
首次注册管理员后,可以添加和删除愿望单条目,也可以上传一个有效的电子书文件并在上传记录中查看。该流程可用于确认身份验证、SQLite 持久化、媒体目录写入和重启恢复均正常。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 项目仓库:<https://github.com/JeremiahM37/librarr>
|
||||
- 容器镜像:<https://github.com/JeremiahM37/librarr/pkgs/container/librarr>
|
||||
|
||||
## Introduction
|
||||
|
||||
Librarr is a self-hosted media manager for ebooks, audiobooks, and manga. It provides a unified Web interface for local libraries, wishlists, search, and download workflows.
|
||||
|
||||
## Features
|
||||
|
||||
- Manage separate ebook, audiobook, and manga directories.
|
||||
- Maintain wishlists, reading history, tags, and monitored authors.
|
||||
- Upload local media files and organize them into the selected directories.
|
||||
- Optionally integrate qBittorrent, Audiobookshelf, Kavita, Komga, and other services.
|
||||
|
||||
## First use
|
||||
|
||||
Open the Web port and register the first account, which becomes the administrator. The application data path and three media paths can be selected during installation.
|
||||
@@ -0,0 +1,33 @@
|
||||
## Introduction
|
||||
|
||||
**Librarr** is a self-hosted media manager for ebooks, audiobooks, and manga. It provides a unified Web interface for managing local libraries, wishlists, search, and download workflows.
|
||||
|
||||
## Features
|
||||
|
||||
- Manage separate ebook, audiobook, and manga directories.
|
||||
- Maintain wishlists, reading history, tags, and monitored authors.
|
||||
- Upload local media files and organize them into the selected directories.
|
||||
- Optionally integrate qBittorrent, Audiobookshelf, Kavita, Komga, and other services.
|
||||
|
||||
## First use
|
||||
|
||||
- Open the Web port after installation.
|
||||
- Register the administrator account on first use. The first user automatically becomes an administrator.
|
||||
- Media directories default to `data/ebooks`, `data/audiobooks`, and `data/manga` under the application version directory. You can select other host paths in the installation form.
|
||||
|
||||
## Data and upgrades
|
||||
|
||||
- Users, wishlists, settings, and activity are stored in a SQLite database in the selected application data directory.
|
||||
- Media files are stored in the three host directories selected during installation.
|
||||
- This package is limited to one instance to prevent multiple installations from competing for the same data directory.
|
||||
- The uninstall script does not remove media directories. Back up both the database volume and media directories before uninstalling or upgrading.
|
||||
- `latest` is intentionally movable for users who track releases with container update tools; the numbered version provides a reproducible deployment.
|
||||
|
||||
## Default workflow
|
||||
|
||||
After registering the first administrator, add and remove a wishlist item, then upload a valid ebook and view its upload record. This exercises authentication, SQLite persistence, media-directory writes, and recovery after a restart.
|
||||
|
||||
## Links
|
||||
|
||||
- Project: <https://github.com/JeremiahM37/librarr>
|
||||
- Container image: <https://github.com/JeremiahM37/librarr/pkgs/container/librarr>
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Librarr
|
||||
tags:
|
||||
- Media
|
||||
title: 自托管的电子书、有声书和漫画管理器
|
||||
description: 自托管的电子书、有声书和漫画管理器
|
||||
additionalProperties:
|
||||
key: librarr
|
||||
name: Librarr
|
||||
description:
|
||||
en: A self-hosted manager for ebooks, audiobooks, and manga
|
||||
zh: 自托管的电子书、有声书和漫画管理器
|
||||
zh-Hant: 自託管的電子書、有聲書和漫畫管理器
|
||||
ja: 電子書籍、オーディオブック、漫画向けのセルフホスト型管理ツール
|
||||
ko: 전자책, 오디오북 및 만화를 위한 자체 호스팅 관리자
|
||||
ru: Самостоятельно размещаемый менеджер электронных и аудиокниг и манги
|
||||
ms: Pengurus hos sendiri untuk e-buku, buku audio dan manga
|
||||
pt-br: Gerenciador auto-hospedado de ebooks, audiolivros e mangás
|
||||
tags:
|
||||
- Media
|
||||
shortDescZh: 自托管的电子书、有声书和漫画管理器
|
||||
shortDescEn: A self-hosted manager for ebooks, audiobooks, and manga
|
||||
type: tool
|
||||
crossVersionUpdate: true
|
||||
limit: 1
|
||||
recommend: 0
|
||||
website: https://github.com/JeremiahM37/librarr
|
||||
github: https://github.com/JeremiahM37/librarr
|
||||
document: https://github.com/JeremiahM37/librarr#readme
|
||||
architectures:
|
||||
- amd64
|
||||
@@ -0,0 +1,6 @@
|
||||
PANEL_APP_PORT_HTTP=5050
|
||||
APP_DATA_DIR=./data/librarr
|
||||
APP_EBOOK_DIR=./data/ebooks
|
||||
APP_AUDIOBOOK_DIR=./data/audiobooks
|
||||
APP_MANGA_DIR=./data/manga
|
||||
CONTAINER_NAME=librarr-compose-check
|
||||
@@ -0,0 +1,83 @@
|
||||
additionalProperties:
|
||||
formFields:
|
||||
- default: 5050
|
||||
edit: true
|
||||
envKey: PANEL_APP_PORT_HTTP
|
||||
labelEn: HTTP Port
|
||||
labelZh: HTTP 端口
|
||||
label:
|
||||
en: HTTP Port
|
||||
zh: HTTP 端口
|
||||
zh-Hant: HTTP 埠
|
||||
ja: HTTP ポート
|
||||
ko: HTTP 포트
|
||||
ru: HTTP-порт
|
||||
ms: Port HTTP
|
||||
pt-br: Porta HTTP
|
||||
required: true
|
||||
rule: paramPort
|
||||
type: number
|
||||
- default: ./data/librarr
|
||||
edit: true
|
||||
envKey: APP_DATA_DIR
|
||||
labelEn: Application data directory
|
||||
labelZh: 应用数据目录
|
||||
label:
|
||||
en: Application data directory
|
||||
zh: 应用数据目录
|
||||
zh-Hant: 應用資料目錄
|
||||
ja: アプリケーションデータディレクトリ
|
||||
ko: 애플리케이션 데이터 디렉터리
|
||||
ru: Каталог данных приложения
|
||||
ms: Direktori data aplikasi
|
||||
pt-br: Diretório de dados do aplicativo
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/ebooks
|
||||
edit: true
|
||||
envKey: APP_EBOOK_DIR
|
||||
labelEn: Ebook directory
|
||||
labelZh: 电子书目录
|
||||
label:
|
||||
en: Ebook directory
|
||||
zh: 电子书目录
|
||||
zh-Hant: 電子書目錄
|
||||
ja: 電子書籍ディレクトリ
|
||||
ko: 전자책 디렉터리
|
||||
ru: Каталог электронных книг
|
||||
ms: Direktori e-buku
|
||||
pt-br: Diretório de ebooks
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/audiobooks
|
||||
edit: true
|
||||
envKey: APP_AUDIOBOOK_DIR
|
||||
labelEn: Audiobook directory
|
||||
labelZh: 有声书目录
|
||||
label:
|
||||
en: Audiobook directory
|
||||
zh: 有声书目录
|
||||
zh-Hant: 有聲書目錄
|
||||
ja: オーディオブックディレクトリ
|
||||
ko: 오디오북 디렉터리
|
||||
ru: Каталог аудиокниг
|
||||
ms: Direktori buku audio
|
||||
pt-br: Diretório de audiolivros
|
||||
required: true
|
||||
type: text
|
||||
- default: ./data/manga
|
||||
edit: true
|
||||
envKey: APP_MANGA_DIR
|
||||
labelEn: Manga directory
|
||||
labelZh: 漫画目录
|
||||
label:
|
||||
en: Manga directory
|
||||
zh: 漫画目录
|
||||
zh-Hant: 漫畫目錄
|
||||
ja: 漫画ディレクトリ
|
||||
ko: 만화 디렉터리
|
||||
ru: Каталог манги
|
||||
ms: Direktori manga
|
||||
pt-br: Diretório de mangás
|
||||
required: true
|
||||
type: text
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
librarr:
|
||||
image: "ghcr.io/jeremiahm37/librarr:latest"
|
||||
container_name: ${CONTAINER_NAME}
|
||||
restart: always
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=512M
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- 1panel-network
|
||||
ports:
|
||||
- "${PANEL_APP_PORT_HTTP}:5050"
|
||||
volumes:
|
||||
- "${APP_DATA_DIR}:/data"
|
||||
- "${APP_EBOOK_DIR}:/books/ebooks"
|
||||
- "${APP_AUDIOBOOK_DIR}:/books/audiobooks"
|
||||
- "${APP_MANGA_DIR}:/books/manga"
|
||||
environment:
|
||||
- LIBRARR_PORT=5050
|
||||
- LIBRARR_DB_PATH=/data/librarr.db
|
||||
- EBOOK_DIR=/books/ebooks
|
||||
- AUDIOBOOK_DIR=/books/audiobooks
|
||||
- MANGA_DIR=/books/manga
|
||||
- WISHLIST_CLEANUP_ENABLED=false
|
||||
- OIDC_PROXY_HEADERS_ENABLED=false
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:5050/api/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 9
|
||||
start_period: 15s
|
||||
labels:
|
||||
createdBy: "Apps"
|
||||
|
||||
networks:
|
||||
1panel-network:
|
||||
external: true
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
||||
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
|
||||
LIBRARR_UID=1000
|
||||
LIBRARR_GID=1000
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
read_env_value() {
|
||||
local key="$1"
|
||||
local value=""
|
||||
value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
|
||||
case "$value" in
|
||||
\"*\") value="${value#\"}"; value="${value%\"}" ;;
|
||||
\'*\') value="${value#\'}"; value="${value%\'}" ;;
|
||||
esac
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
reject_symlink_components() {
|
||||
local path="$1"
|
||||
local current=""
|
||||
local component
|
||||
IFS='/' read -r -a components <<< "${path#/}"
|
||||
for component in "${components[@]}"; do
|
||||
[[ -n "$component" ]] || continue
|
||||
current="${current}/${component}"
|
||||
[[ ! -L "$current" ]] || fail "$path must not contain symbolic-link components"
|
||||
done
|
||||
}
|
||||
|
||||
prepare_media_dir() {
|
||||
local key="$1"
|
||||
local raw=""
|
||||
local candidate=""
|
||||
local resolved=""
|
||||
local scope=""
|
||||
local existed=false
|
||||
|
||||
raw="$(read_env_value "$key")"
|
||||
[[ -n "$raw" ]] || fail "$key must not be empty"
|
||||
[[ "$raw" =~ ^[A-Za-z0-9._/\ -]+$ ]] || fail "$key contains unsupported characters"
|
||||
|
||||
if [[ "$raw" == /* ]]; then
|
||||
candidate="$raw"
|
||||
scope="absolute"
|
||||
else
|
||||
candidate="${ROOT_DIR}/${raw#./}"
|
||||
scope="application"
|
||||
fi
|
||||
|
||||
reject_symlink_components "$candidate"
|
||||
resolved="$(realpath -m -- "$candidate")" || fail "Unable to resolve $key"
|
||||
[[ "$resolved" != "/" ]] || fail "$key must not be the filesystem root"
|
||||
if [[ "$scope" == "application" ]]; then
|
||||
case "$resolved" in
|
||||
"${ROOT_DIR}"/*) ;;
|
||||
*) fail "Relative $key must stay inside the application version directory" ;;
|
||||
esac
|
||||
fi
|
||||
[[ ! -e "$resolved" || -d "$resolved" ]] || fail "$key must be a directory"
|
||||
[[ -d "$resolved" ]] && existed=true
|
||||
|
||||
if [[ "$scope" == "absolute" && "$existed" == "true" ]]; then
|
||||
local incompatible_owner=""
|
||||
incompatible_owner="$(find "$resolved" -xdev \( ! -uid "$LIBRARR_UID" -o ! -gid "$LIBRARR_GID" \) -print -quit)" || \
|
||||
fail "Unable to inspect $key ownership"
|
||||
[[ -z "$incompatible_owner" ]] || fail "Existing absolute $key tree must be owned by ${LIBRARR_UID}:${LIBRARR_GID}"
|
||||
fi
|
||||
|
||||
install -d -m 0750 -o "$LIBRARR_UID" -g "$LIBRARR_GID" -- "$resolved" || \
|
||||
fail "Unable to prepare $key"
|
||||
local verified=""
|
||||
verified="$(realpath -e -- "$resolved")" || fail "Unable to verify $key"
|
||||
[[ "$verified" == "$resolved" ]] || fail "$key changed while it was being prepared"
|
||||
if [[ "$scope" == "application" ]]; then
|
||||
case "$verified" in
|
||||
"${ROOT_DIR}"/*) ;;
|
||||
*) fail "$key resolves outside the application version directory" ;;
|
||||
esac
|
||||
chown -R --no-dereference "$LIBRARR_UID:$LIBRARR_GID" "$verified" || \
|
||||
fail "Unable to set $key ownership"
|
||||
elif [[ "$existed" == "false" ]]; then
|
||||
chown "$LIBRARR_UID:$LIBRARR_GID" "$verified" || fail "Unable to set $key ownership"
|
||||
fi
|
||||
chmod 0750 "$verified" || fail "Unable to set $key permissions"
|
||||
printf '%s\n' "$verified"
|
||||
}
|
||||
|
||||
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
|
||||
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
|
||||
[[ "$(id -u)" -eq 0 ]] || fail "Librarr init must run as root to prepare media-directory ownership"
|
||||
|
||||
data_dir="$(prepare_media_dir APP_DATA_DIR)"
|
||||
ebook_dir="$(prepare_media_dir APP_EBOOK_DIR)"
|
||||
audiobook_dir="$(prepare_media_dir APP_AUDIOBOOK_DIR)"
|
||||
manga_dir="$(prepare_media_dir APP_MANGA_DIR)"
|
||||
|
||||
[[ "$data_dir" != "$ebook_dir" && "$data_dir" != "$audiobook_dir" && "$data_dir" != "$manga_dir" && \
|
||||
"$ebook_dir" != "$audiobook_dir" && "$ebook_dir" != "$manga_dir" && "$audiobook_dir" != "$manga_dir" ]] || \
|
||||
fail "Librarr data and media directories must be distinct"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Media directories contain user-managed files and are never deleted here.
|
||||
exit 0
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Librarr applies SQLite migrations in the upstream binary during startup.
|
||||
exit 0
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Reference in New Issue
Block a user