Continuing from the previous article Tinkering with Aliyun Drive + AList + Rclone + AnimePaste.
On the server, mount Aliyun Drive to AList, then use rclone to mount it to the local disk, and finally expose the anime media library using Jellyfin.
Jellyfin#
First, install Jellyfin.
dnf install -y https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
dnf install -y https://repo.jellyfin.org/releases/server/fedora/stable/server/jellyfin-10.8.10-1.fc36.x86_64.rpm
dnf install -y https://repo.jellyfin.org/releases/server/fedora/stable/web/jellyfin-web-10.8.10-1.fc36.noarch.rpm
# Start Jellyfin
systemctl start jellyfin
# Enable Jellyfin to start on boot
systemctl enable jellyfin
After starting, you can access the Jellyfin media library at http://serverIP:8096
(make sure the server firewall allows port 8096).
AList#
Deploy AList directly using Docker.
#!/usr/bin/bash
docker stop alist 2> /dev/null
docker rm alist 2> /dev/null
docker run -d --name=alist --restart=always \
-v /etc/alist:/opt/alist/data -p 5244:5244 \
-e PUID=0 -e PGID=0 -e UMASK=022 xhofe/alist:latest
After starting, check the container logs to obtain the login password.
docker logs alist
After starting, you can access AList at http://serverIP:5244
(make sure the server firewall allows port 5244). Then, configure the Aliyun Drive mount to /aliyundriver
in its WebUI.
The anime directory is agreed to be
/anime/
on Aliyun Drive, which corresponds to/aliyundriver/anime/
in AList.
qbittorrent#
version: '3.9'
services:
alist:
image: xhofe/alist:latest
container_name: alist
restart: always
environment:
- PUID=0
- PGID=0
- UMASK=022
- TZ=Asia/Shanghai
volumes:
- /etc/alist:/opt/alist/data
networks:
- alist_net
ports:
- 5244:5244
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- WEBUI_PORT=8080
volumes:
- ./appdata/config:/config
- ./downloads:/downloads
networks:
- alist_net
ports:
- 8080:8080
- 6881:6881
- 6881:6881/udp
networks:
alist_net:
Rclone#
[alist]
type = webdav
url = http://127.0.0.1:5244/dav/
user = admin
pass = xxxyyyzzz
The password has been hidden.
I wrote a script mount-anime.sh
for easy mounting of the local directory.
#!/usr/bin/bash
mount_dir="/jellyfin/anime"
cache_dir="/root/.rclone/cache/anime"
log_path="/var/log/rclone-anime.log"
function bootstrap() {
mkdir -p $mount_dir
rm $log_path
rclone mount alist:/aliyundriver/anime/ $mount_dir \
--header "Referer:https://www.aliyundrive.com/" \
--vfs-cache-mode writes --vfs-read-chunk-size-limit 1G --vfs-read-chunk-size 256M \
--cache-dir $cache_dir --dir-cache-time 5m --vfs-cache-max-size 10G \
--buffer-size 128M \
--no-check-certificate --allow-non-empty --allow-other \
--uid $(id -u jellyfin) --gid $(id -g jellyfin) --umask 022 \
--log-file $log_path $@
}
function stop() {
fusermount -u $mount_dir 2> /dev/null
ps -ef | grep rclone | grep -v grep | awk '{print $2}' | xargs kill 2> /dev/null
}
case "$1" in
"")
stop
bootstrap --daemon
;;
"service")
stop
bootstrap
;;
"space")
echo $mount_dir
;;
"logs")
tail -f $log_path
;;
"stop")
stop
;;
esac
Note: The mounted local directory ($mount_dir
) must have permissions for the jellyfin
user; otherwise, Jellyfin will not be able to recognize it.
Usage:
# Mount as a daemon
$ ./mount-anime.sh
# View logs
$ ./mount-anime.sh logs
# Stop
$ ./mount-anime.sh stop
Additionally, add it to systemd
to mount automatically on reboot.
[Unit]
Description=Mount Anime Directory
Documentation=man:rclone(1)
After=network.target
After=docker.service
Before=jellyfin.service
[Service]
Type=simple
ExecStart=/path/to/mount-anime.sh service
ExecStop=/path/to/mount-anime.sh stop
[Install]
WantedBy=default.target
Modify the path of the
mount-anime.sh
script.
# Move the above service file to the systemd directory
$ cp /path/to/mount-anime.service /etc/systemd/system/
# Restart and start the service
$ systemctl daemon-reload
$ systemctl enable mount-anime.service
$ systemctl start mount-anime.service
nginx (optional)#
You can also expose it to a domain name for easier access.
Modify the following configuration and place it in /etc/nginx/conf.d/jellyfin.conf
. You need to modify:
server_name
: your domain;ssl_certificate
/ssl_certificate_key
: your SSL certificate.
server {
listen 80;
listen [::]:80;
server_name <SERVER_NAME>;
# Uncomment to redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name <SERVER_NAME>;
## The default `client_max_body_size` is 1M, this might not be enough for some posters, etc.
client_max_body_size 20M;
# use a variable to store the upstream proxy
# in this example we are using a hostname which is resolved via DNS
# (if you aren't using DNS remove the resolver line and change the variable to point to an IP address e.g `set $jellyfin 127.0.0.1`)
# set $jellyfin jellyfin;
# resolver 127.0.0.1 valid=30;
set $jellyfin 127.0.0.1;
ssl_certificate <SSL_PEM>;
ssl_certificate_key <SSL_KEY>;
#include /etc/letsencrypt/options-ssl-nginx.conf;
#ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
#ssl_trusted_certificate /etc/letsencrypt/live/DOMAIN_NAME/chain.pem;
ssl_stapling on;
ssl_stapling_verify on;
# Security / XSS Mitigation Headers
# NOTE: X-Frame-Options may cause issues with the webOS app
# add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
# Content Security Policy
# See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
# Enforces https content and restricts JS/CSS to origin
# External Javascript (such as cast_sender.js for Chromecast) must be whitelisted.
# NOTE: The default CSP headers may cause issues with the webOS app
#add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/95/cast_sender.js https://www.gstatic.com/eureka/clank/96/cast_sender.js https://www.gstatic.com/eureka/clank/97/cast_sender.js https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'";
location = / {
return 302 http://$host/web/;
#return 302 https://$host/web/;
}
location / {
# Proxy main Jellyfin traffic
proxy_pass http://$jellyfin:8096;
proxy_set_header Host $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 $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
# Disable buffering when the nginx proxy gets very resource heavy upon streaming
proxy_buffering off;
}
# location block for /web - This is purely for aesthetics so /web/#!/ works instead of having to go to /web/index.html/#!/
location = /web/ {
# Proxy main Jellyfin traffic
proxy_pass http://$jellyfin:8096/web/index.html;
proxy_set_header Host $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 $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}
location /socket {
# Proxy Jellyfin Websockets traffic
proxy_pass http://$jellyfin:8096;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $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 $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}
}
Finally#
Initialize your media library on Jellyfin, add anime-related plugins and the jellyfin-plugin-bangumi plugin, and specify that the media library downloads metadata from these plugins.