Debian13 通过 Docker 部署 Jellyfin

本文最后更新于 2025年12月14日 上午

环境

Debian 13
内核版本 6.12.43+deb13-amd64

过程

1. 安装 Docker

2. 创建 Jellyfin 配置目录

1
2
sudo mkdir -p /srv/jellyfin/{config,cache}
sudo chown -R 3001:3000 /srv/jellyfin

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
user: "3001:3000" # 用与 SMB 挂载一致的 UID/GID
network_mode: "host" # 走宿主网络,端口按 Jellyfin 默认(8096/8920)
volumes:
- /srv/jellyfin/config:/config # 建议把配置放本地磁盘
- /srv/jellyfin/cache:/cache
- type: bind
source: /mnt/downloads/media # 你的 SMB 已挂载点
target: /media
# read_only: true # 媒体只读更安全;需要写入再去掉
# 可选:系统字体给字幕烧录用(只读)
- type: bind
source: /usr/share/fonts
target: /usr/local/share/fonts
read_only: true
devices:
- /dev/dri:/dev/dri # iGPU 硬件转码(VAAPI)
# group_add:
# - "render"
# - "video"
environment:
- TZ=Asia/Shanghai
# 可选:对外发布地址(反向代理/外网时才需要)
# - JELLYFIN_PublishedServerUrl=http://your-domain-or-ip
restart: unless-stopped
# extra_hosts:
# - 'host.docker.internal:host-gateway'

TMDB 连不上的问题

image.tmdb.org 依旧无法访问

TMDB DNS 可能被污染,导致无法获取元数据,可以通过修改 /etc/hosts 解决:

https://dnschecker.org/ 上查询

  • themoviedb.org
  • www.themoviedb.org
  • api.themoviedb.org
  • image.tmdb.org

的 IP 地址

由于这些域名都对应了多个 IP 地址,而在 /etc/hosts 中只能会生效第一个,所以可以用以下脚本设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env bash
set -euo pipefail

# ================================================
# TMDb 多域名 hosts 自动修复脚本
# 功能:
# - 为几个 TMDb 域名挑选可用 IPv4 地址
# - 更新 /etc/hosts
# - 自动备份 /etc/hosts
# 参数:
# -d dryrun,只打印将要写入的映射,不修改文件
# -q quiet,静默模式(不打印过程日志)
# ================================================

HOSTS_FILE="/etc/hosts"
STAMP="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"

DRYRUN=0 # 默认不启用 dryrun
VERBOSE=1 # 默认打印日志

# 参数解析
while getopts "dq" opt; do
case $opt in
d) DRYRUN=1 ;;
q) VERBOSE=0 ;;
*) echo "用法: $0 [-d] [-q]"; exit 1 ;;
esac
done

# 需要检测的几个域名
DOMAINS=(
"themoviedb.org"
"www.themoviedb.org"
"api.themoviedb.org"
"image.tmdb.org"
)

# 每个域名的兜底 IP
declare -A FALLBACKS=(
["themoviedb.org"]="3.168.73.5 3.168.73.14 3.168.73.124 3.168.73.40"
["www.themoviedb.org"]="3.168.73.5 3.168.73.14 3.168.73.124 3.168.73.40"
["api.themoviedb.org"]="18.173.219.85 18.173.219.5 18.173.219.124 18.173.219.98"
["image.tmdb.org"]="185.93.1.244"
)

# 工具函数
have(){ command -v "$1" >/dev/null 2>&1; }
log(){ [ "$VERBOSE" = "1" ] && echo "$@" >&2 || true; }

# 解析 A 记录(支持 CNAME)
resolve_a_records(){
local name="$1" ; local out=()
if have dig; then
mapfile -t out < <(dig +short A "$name" | grep -E '^[0-9]+\.' || true)
if [ "${#out[@]}" -eq 0 ]; then
cname=$(dig +short CNAME "$name" | tail -n1 || true)
if [ -n "$cname" ]; then
mapfile -t out < <(dig +short A "$cname" | grep -E '^[0-9]+\.' || true)
fi
fi
elif have getent; then
mapfile -t out < <(getent ahostsv4 "$name" | awk '{print $1}' | sort -u)
fi
printf '%s\n' "${out[@]}" | sort -u
}

# 收集候选 IP(顺序很重要!)
# 1) 先放入你提供的 FALLBACK IP(优先尝试)
# 2) 再补充 DNS 解析出来的 IP(去重后追加)
gather_candidates(){
local name="$1"; local ips=()

# 1) 先你的 IP
for ip in ${FALLBACKS[$name]:-}; do
# 避免奇怪空格导致空元素
[ -n "$ip" ] && ips+=("$ip")
done

# 2) 再 DNS 的(避免与前面的重复)
local dns_ips=()
mapfile -t dns_ips < <(resolve_a_records "$name")
for ip in "${dns_ips[@]}"; do
grep -qx "$ip" <(printf '%s\n' "${ips[@]}") || ips+=("$ip")
done

printf '%s\n' "${ips[@]}"
}

# 探测某个 IP 是否可用
probe(){
local name="$1" ip="$2"
local code
code=$(curl -sS -I "https://${name}/" \
--resolve "${name}:443:${ip}" \
--connect-timeout 3 --max-time 5 \
-o /dev/null -w '%{http_code}' || true)
[[ "$code" =~ ^[0-9]{3}$ ]]
}

# 删除旧映射
strip_domain(){
local name="$1" tmp
tmp="$(mktemp)"
sed -E "/[[:space:]]${name//./\\.}([[:space:]]|\$)/d" "$HOSTS_FILE" > "$tmp"
cat "$tmp" > "$HOSTS_FILE"
rm -f "$tmp"
}

# 追加新映射
append_map(){
local name="$1" ip="$2"
printf '%s\t%s\t# managed %s\n' "$ip" "$name" "$STAMP" | tee -a "$HOSTS_FILE" >/dev/null
}

# 刷新缓存
flush(){
have systemd-resolve && systemd-resolve --flush-caches || true
have resolvectl && resolvectl flush-caches || true
have nscd && nscd -i hosts || true
have nmcli && nmcli general reload || true
}

# 主逻辑
main(){
declare -A best=()
for d in "${DOMAINS[@]}"; do
log "→ 收集 $d 候选 IP ..."
mapfile -t cands < <(gather_candidates "$d")
[ "${#cands[@]}" -eq 0 ] && { echo "没有 $d 候选 IP" >&2; continue; }
log "候选:${cands[*]}"
for ip in "${cands[@]}"; do
log " 测试 $d @ $ip ..."
if probe "$d" "$ip"; then
log " ✅ 可用:$ip"
best["$d"]="$ip"
break
else
log " ❌ 不通:$ip"
fi
done
done

if [ "$DRYRUN" = "1" ]; then
echo "[DRYRUN] 将写入映射:"
for d in "${!best[@]}"; do echo " ${best[$d]} $d"; done
exit 0
fi

# ==========================
# 在写入前,统一做一次完整备份
# ==========================
cp -a "$HOSTS_FILE" "${HOSTS_FILE}.bak_${STAMP}"
log "已备份:${HOSTS_FILE}.bak_${STAMP}"

for d in "${!best[@]}"; do
strip_domain "$d"
append_map "$d" "${best[$d]}"
done

flush
log "完成更新 ✅"
}

main

参考


Debian13 通过 Docker 部署 Jellyfin
https://term-inator.github.io/2025/09/14/debian-docker-jellyfin/
作者
Sicong Chen
发布于
2025年9月14日
更新于
2025年12月14日
许可协议