├── .github
└── workflows
│ └── MediaLinker.yml
├── .idea
├── .gitignore
├── MediaLinker.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Dockerfile
├── Dockerfile-emby
├── Dockerfile-plex
├── LICENSE
├── README.md
├── check_certificate
├── config
├── emby
│ └── constant.js
├── logrotate.conf
├── plex
│ └── constant.js
└── ssl
├── deploy
├── docker-compose.yml
└── my-MediaLinker.xml
├── entrypoint
└── start_server
/.github/workflows/MediaLinker.yml:
--------------------------------------------------------------------------------
1 | name: MediaLinker Builder
2 | on:
3 | workflow_dispatch:
4 | # push:
5 | # branches:
6 | # - main
7 |
8 | jobs:
9 | Docker-build:
10 | runs-on: ubuntu-latest
11 | name: Build Docker Image
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Set Up QEMU
17 | uses: docker/setup-qemu-action@v3
18 |
19 | - name: Set Up Buildx
20 | uses: docker/setup-buildx-action@v3
21 |
22 | - name: Login DockerHub
23 | uses: docker/login-action@v3
24 | with:
25 | username: ${{ secrets.DOCKER_USERNAME }}
26 | password: ${{ secrets.DOCKER_PASSWORD }}
27 |
28 | - name: MediaLinker-ALl
29 | run: |
30 | docker buildx build --platform linux/amd64,linux/arm64 -t ${{ secrets.DOCKER_USERNAME }}/medialinker:latest -f Dockerfile . --push
31 | env:
32 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
33 |
34 | - name: MediaLinker-Emby
35 | run: |
36 | docker buildx build --platform linux/amd64,linux/arm64 -t ${{ secrets.DOCKER_USERNAME }}/medialinker:emby -f Dockerfile-emby . --push
37 | env:
38 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
39 |
40 | - name: MediaLinker-Plex
41 | run: |
42 | docker buildx build --platform linux/amd64,linux/arm64 -t ${{ secrets.DOCKER_USERNAME }}/medialinker:plex -f Dockerfile-plex . --push
43 | env:
44 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/MediaLinker.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # 环境变量
4 | ENV LANG="C.UTF-8" \
5 | TZ="Asia/Shanghai" \
6 | NGINX_PORT="8091" \
7 | NGINX_SSL_PORT="8095" \
8 | REPO_URL="https://github.com/chen3861229/embyExternalUrl" \
9 | SSL_ENABLE="false" \
10 | SSL_CRON="0 /2 " \
11 | SSL_DOMAIN="" \
12 | AUTO_UPDATE="false" \
13 | SERVER="emby"
14 |
15 | # 安装git
16 | RUN apk --no-cache add nginx nginx-mod-http-js curl busybox git openssl logrotate tzdata && \
17 | cp /usr/share/zoneinfo/$TZ /etc/localtime && \
18 | echo "$TZ" > /etc/timezone && \
19 | mkdir -p /var/cache/nginx/emby/image /opt && \
20 | git clone $REPO_URL /embyExternalUrl && \
21 | curl -L -o /tmp/lego_latest.tar.gz "https://github.com/go-acme/lego/releases/download/v3.7.0/lego_v3.7.0_linux_amd64.tar.gz" && \
22 | tar zxvf /tmp/lego_latest.tar.gz -C /tmp && \
23 | chmod 755 /tmp/lego && \
24 | mv /tmp/lego / && \
25 | rm -rf /tmp/*
26 |
27 | COPY entrypoint /entrypoint
28 | COPY start_server /start_server
29 | COPY check_certificate /check_certificate
30 | COPY config/logrotate.conf /etc/logrotate.d/medialinker
31 |
32 | RUN chmod +x /entrypoint /start_server /check_certificate
33 |
34 | ENTRYPOINT ["/bin/sh", "/entrypoint"]
--------------------------------------------------------------------------------
/Dockerfile-emby:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # 环境变量
4 | ENV LANG="C.UTF-8" \
5 | TZ="Asia/Shanghai" \
6 | NGINX_PORT="8091" \
7 | NGINX_SSL_PORT="8095" \
8 | REPO_URL="https://github.com/chen3861229/embyExternalUrl" \
9 | SSL_ENABLE="false" \
10 | SSL_CRON="0 /2 " \
11 | SSL_DOMAIN="" \
12 | AUTO_UPDATE="false" \
13 | SERVER="emby"
14 |
15 | # 安装git
16 | RUN apk --no-cache add nginx nginx-mod-http-js curl busybox git openssl logrotate tzdata && \
17 | cp /usr/share/zoneinfo/$TZ /etc/localtime && \
18 | echo "$TZ" > /etc/timezone && \
19 | mkdir -p /var/cache/nginx/emby/image /opt && \
20 | git clone $REPO_URL /embyExternalUrl && \
21 | curl -L -o /tmp/lego_latest.tar.gz "https://github.com/go-acme/lego/releases/download/v3.7.0/lego_v3.7.0_linux_amd64.tar.gz" && \
22 | tar zxvf /tmp/lego_latest.tar.gz -C /tmp && \
23 | chmod 755 /tmp/lego && \
24 | mv /tmp/lego / && \
25 | rm -rf /tmp/*
26 |
27 | COPY entrypoint /entrypoint
28 | COPY start_server /start_server
29 | COPY check_certificate /check_certificate
30 | COPY config/logrotate.conf /etc/logrotate.d/medialinker
31 |
32 | RUN chmod +x /entrypoint /start_server /check_certificate
33 |
34 | ENTRYPOINT ["/bin/sh", "/entrypoint"]
--------------------------------------------------------------------------------
/Dockerfile-plex:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | # 环境变量
4 | ENV LANG="C.UTF-8" \
5 | TZ="Asia/Shanghai" \
6 | NGINX_PORT="8091" \
7 | NGINX_SSL_PORT="8095" \
8 | REPO_URL="https://github.com/chen3861229/embyExternalUrl" \
9 | SSL_ENABLE="false" \
10 | SSL_CRON="0 /2 " \
11 | SSL_DOMAIN="" \
12 | AUTO_UPDATE="false" \
13 | SERVER="plex"
14 |
15 | # 安装git
16 | RUN apk --no-cache add nginx nginx-mod-http-js curl busybox git openssl logrotate tzdata && \
17 | cp /usr/share/zoneinfo/$TZ /etc/localtime && \
18 | echo "$TZ" > /etc/timezone && \
19 | mkdir -p /var/cache/nginx/emby/image /opt && \
20 | git clone $REPO_URL /embyExternalUrl && \
21 | curl -L -o /tmp/lego_latest.tar.gz "https://github.com/go-acme/lego/releases/download/v3.7.0/lego_v3.7.0_linux_amd64.tar.gz" && \
22 | tar zxvf /tmp/lego_latest.tar.gz -C /tmp && \
23 | chmod 755 /tmp/lego && \
24 | mv /tmp/lego / && \
25 | rm -rf /tmp/*
26 |
27 | COPY entrypoint /entrypoint
28 | COPY start_server /start_server
29 | COPY check_certificate /check_certificate
30 | COPY config/logrotate.conf /etc/logrotate.d/medialinker
31 |
32 | RUN chmod +x /entrypoint /start_server /check_certificate
33 |
34 | ENTRYPOINT ["/bin/sh", "/entrypoint"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2023 ChenyangGao
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Github][Github-image]][Github-url]
2 | [![commit activity][commit-activity-image]][commit-activity-url]
3 | [![docker version][docker-version-image]][docker-version-url]
4 | [![docker pulls][docker-pulls-image]][docker-pulls-url]
5 | [![docker stars][docker-stars-image]][docker-stars-url]
6 | [![docker image size][docker-image-size-image]][docker-image-size-url]
7 |
8 | [Github-image]: https://img.shields.io/static/v1?label=Github&message=MediaLinker&color=brightgreen
9 | [Github-url]: https://github.com/thsrite/MediaLinker
10 | [commit-activity-image]: https://img.shields.io/github/commit-activity/m/thsrite/MediaLinker
11 | [commit-activity-url]: https://github.com/thsrite/MediaLinker
12 | [docker-version-image]: https://img.shields.io/docker/v/thsrite/medialinker?style=flat
13 | [docker-version-url]: https://hub.docker.com/r/thsrite/medialinker/tags?page=1&ordering=last_updated
14 | [docker-pulls-image]: https://img.shields.io/docker/pulls/thsrite/medialinker?style=flat
15 | [docker-pulls-url]: https://hub.docker.com/r/thsrite/medialinker
16 | [docker-stars-image]: https://img.shields.io/docker/stars/thsrite/medialinker?style=flat
17 | [docker-stars-url]: https://hub.docker.com/r/thsrite/medialinker
18 | [docker-image-size-image]: https://img.shields.io/docker/image-size/thsrite/medialinker?style=flat
19 | [docker-image-size-url]: https://hub.docker.com/r/thsrite/medialinker
20 |
21 | 原作者仓库:https://github.com/chen3861229/embyExternalUrl
22 |
23 | 本项目为大佬项目的docker版本,旨在简化部署方式、方便更新。
24 |
25 | 非容器运行问题请去原作者仓库提issue,请给原作者大佬点赞!
26 |
27 | ### 环境配置
28 | | 参数 | 是否必填 | 说明 |
29 | |-----------------|:-------------------------|----------------------------------------------------------------------------------------------------|
30 | | AUTO_UPDATE | 可选 | 重启自动更新,true/false,默认`false` |
31 | | SERVER | 可选 | 服务端,emby/plex,默认`emby` |
32 | | NGINX_PORT | 可选 | nginx端口,默认`8091` |
33 | | NGINX_SSL_PORT | 可选 | nginx ssl端口,默认`8095` |
34 | | REPO_URL | 可选 | 仓库地址,默认`https://github.com/chen3861229/embyExternalUrl` |
35 | | SSL_ENABLE | 可选 | 是否开启ssl,true/false,默认`false` |
36 | | SSL_CRON | 可选 | ssl证书更新时间,默认每2小时执行一次 |
37 | | SSL_DOMAIN | 可选 | 域名,开启SSL的时候必填 |
38 | | GIT_COMMIT_HASH | 可选(AUTO_UPDATE=false时生效) | 拉取指定commit运行 |
39 | | NGINX_LOG_LEVEL | 可选 | Nginx控制台日志级别,error/access/all或者空(空为默认,等同于all) |
40 | | 证书路径 | 开启SSL必填 | 映射到宿主机/opt/fullchain.pem |
41 | | 证书路径 | 开启SSL必填 | 映射到宿主机/opt/privkey.pem |
42 | | 证书申请命令 | 开启SSL必填 | 映射到宿主机/opt/ssl [ssl示例](config%2Fssl) |
43 | | 配置文件 | `必填` | 映射到宿主机/opt/constant.js [emby示例](config%2Femby%2Fconstant.js) [plex示例](config%2Fplex%2Fconstant.js) |
44 |
45 | ### 部署方式
46 |
47 | #### docker部署
48 | /home/MediaLinker/下创建证书文件、配置文件constant.js [emby示例](config%2Femby%2Fconstant.js) [plex示例](config%2Fplex%2Fconstant.js)
49 |
50 | ```
51 | docker run -d \
52 | --name MediaLinker \
53 | -p 8091:8091 \
54 | -v /home/MediaLinker/:/opt/ \
55 | thsrite/medialinker:latest
56 | ```
57 |
58 | #### [docker-compose](deploy/docker-compose.yml)
59 |
60 | #### [unraid模版](deploy/my-MediaLinker.xml)
61 |
62 | ### 注意事项
63 |
64 | - 如开启自动更新,且本地访问github困难,可能会导致更新失败,建议配置`HTTPS_PROXY`环境变量
65 | - 本容器日志会存储到/opt/MediaLinker.log,已配置logrotate保留3份日志,每份5M,可自行调整`/etc/logrotate.d/medialinker`
66 | - 应某火柴要求,docker分为三个tag:latest为整合版本,默认SERVER=emby可随时切换emby/plex;emby默认SERVER=emby;plex默认SERVER=plex(亦可互相切换)
67 | - 如自动更新后发现最新代码有问题,可配置`GIT_COMMIT_HASH`环境变量拉取指定commit运行
68 | ## 如开启自动更新,请自行获取[最新配置文件](https://github.com/chen3861229/embyExternalUrl/blob/main/emby2Alist/nginx/conf.d/exampleConfig/constant-all.js)或者关闭自动更新,添加环境变量GIT_COMMIT_HASH=a263a401496c2bb84ed63e249d04610241d5c8cb指定特定commit运行
69 |
--------------------------------------------------------------------------------
/check_certificate:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 申请证书、检查证书
4 | check_certificate() {
5 | # 申请证书
6 | /bin/sh /opt/ssl
7 |
8 | # 检查证书是否被 Let's Encrypt 成功签发
9 | if ls /.lego/certificates | grep "${SSL_DOMAIN}"; then
10 | if [ -e /.lego/certificates/"${SSL_DOMAIN}".crt ] && [ -e /.lego/certificates/"${SSL_DOMAIN}".key ]; then
11 | echo 'The certificate has been successfully issued, and the service is being restarted.'
12 | # 删除原证书
13 | rm -rf /opt/fullchain.pem /opt/privkey.key
14 | # 将证书复制到特定目录
15 | cp /.lego/certificates/"${SSL_DOMAIN}".crt /opt/fullchain.pem
16 | cp /.lego/certificates/"${SSL_DOMAIN}".key /opt/privkey.key
17 | # 软连接证书到 nginx 配置目录
18 | mkdir -p /etc/nginx/conf.d/cert/
19 | ln -s /opt/fullchain.pem /etc/nginx/conf.d/cert/fullchain.pem
20 | ln -s /opt/privkey.key /etc/nginx/conf.d/cert/privkey.key
21 |
22 | # 判断nginx是否正在运行,如果正在运行重启 nginx
23 | if pgrep nginx >/dev/null; then
24 | echo "Nginx service is running, reloading..."
25 | nginx -s reload
26 | echo 'Nginx service reload success'
27 | fi
28 | else
29 | echo 'The certificate file does not exist, and the certificate issuance has failed.'
30 | exit 1
31 | fi
32 | else
33 | echo 'Certificate issuance failed.'
34 | exit 1
35 | fi
36 | }
37 |
38 | if [ "${SSL_ENABLE}" = "true" ]; then
39 | # 检查 /opt/ssl 文件是否存在
40 | if [ -e /opt/ssl ]; then
41 |
42 | # 检查证书和私钥文件是否存在
43 | if [ -e /opt/fullchain.pem ] && [ -e /opt/privkey.key ]; then
44 | # 获取当前日期和时间
45 | current_date=$(date +%s)
46 | # 提取证书的到期日期,并将其转换为 Unix 时间戳
47 | expiry_date=$(openssl x509 -enddate -noout -in /opt/fullchain.pem | cut -d= -f2 | awk '{sub(/ GMT/, ""); print}' | xargs -I {} date -d "{}" +%s)
48 | # 计算证书到期的天数
49 | days_until_expiry=$(( (expiry_date - current_date) / 86400 ))
50 | echo "Certificate expiry date: $days_until_expiry days from now."
51 |
52 | # 判断证书是否在 30 天内到期
53 | if [ $days_until_expiry -le 30 ]; then
54 | echo "The certificate will expire within 30 days, start certificate renewal."
55 | check_certificate
56 | else
57 | echo "The certificate is still valid, no need for renewal."
58 | # 判断nginx证书是否正确配置
59 | if [ ! -e /etc/nginx/conf.d/cert/fullchain.pem ] || [ ! -e /etc/nginx/conf.d/cert/privkey.key ]; then
60 | # 软连接证书到 nginx 配置目录
61 | mkdir -p /etc/nginx/conf.d/cert/
62 | ln -s /opt/fullchain.pem /etc/nginx/conf.d/cert/fullchain.pem
63 | ln -s /opt/privkey.key /etc/nginx/conf.d/cert/privkey.key
64 | fi
65 | fi
66 | else
67 | echo "Start applying for domain certificate."
68 | check_certificate
69 | fi
70 | else
71 | echo "SSL script does not exist."
72 | exit 1
73 | fi
74 | else
75 | echo "SSL is not enabled.Skip certificate check."
76 | fi
77 |
78 |
--------------------------------------------------------------------------------
/config/emby/constant.js:
--------------------------------------------------------------------------------
1 | // 这个总配置单体文件只是备份,生效需要放置在 conf.d 下,且重命名为 constant.js
2 | // 如果使用这个全量总配置文件,忽略 config 下的所有文件,以这个文件为准
3 |
4 | // 全量配置,媒体库混合,本地文件 + rclone/CD2 挂载的 alist 文件 + strm文件 + 软链接(路径和文件名不一致)
5 | // export constant allocation
6 |
7 | // 必填项,根据实际情况修改下面的设置
8 |
9 | // 这里默认 emby/jellyfin 的地址是宿主机,要注意 iptables 给容器放行端口
10 | const embyHost = "http://172.17.0.1:8096";
11 |
12 | // emby/jellyfin api key, 在 emby/jellyfin 后台设置
13 | const embyApiKey = "f839390f50a648fd92108bc11ca6730a";
14 |
15 | // 挂载工具 rclone/CD2 多出来的挂载目录, 例如将 od,gd 挂载到 /mnt 目录下: /mnt/onedrive /mnt/gd ,那么这里就填写 /mnt
16 | // 通常配置一个远程挂载根路径就够了,默认非此路径开头文件将转给原始 emby 处理
17 | // 如果没有挂载,全部使用 strm 文件,此项填[""],必须要是数组
18 | const mediaMountPath = ["/mnt"];
19 |
20 | // 访问宿主机上 5244 端口的 alist 地址, 要注意 iptables 给容器放行端口
21 | const alistAddr = "http://172.17.0.1:5244";
22 |
23 | // alist token, 在 alist 后台查看
24 | const alistToken = "alsit-123456";
25 |
26 | // alist 是否启用了 sign
27 | const alistSignEnable = false;
28 |
29 | // alist 中设置的直链过期时间,以小时为单位,严格对照 alist 设置 => 全局 => 直链有效期
30 | const alistSignExpireTime = 12;
31 |
32 | // 选填项,用不到保持默认即可
33 |
34 | // alist 公网地址,用于需要 alist server 代理流量的情况,按需填写
35 | const alistPublicAddr = "http://youralist.com:5244";
36 |
37 | // 字符串头,用于特殊匹配判断
38 | const strHead = {
39 | lanIp: ["172.", "10.", "192.", "[fd00:"], // 局域网ip头
40 | xEmbyClients: {
41 | seekBug: ["Emby for iOS"],
42 | },
43 | xUAs: {
44 | seekBug: ["Infuse", "VidHub", "SenPlayer"],
45 | clientsPC: ["EmbyTheater"],
46 | clients3rdParty: ["Fileball", "Infuse", "SenPlayer", "VidHub"],
47 | player3rdParty: ["dandanplay", "VLC", "MXPlayer", "PotPlayer"],
48 | blockDownload: ["Infuse-Download"],
49 | infuse: {
50 | direct: "Infuse-Direct",
51 | download: "Infuse-Download",
52 | },
53 | // 安卓与 TV 客户端不太好区分,浏览器 UA 关键字也有交叉重叠,请使用 xEmbyClients 参数或使用正则
54 | },
55 | "115": "115.com",
56 | ali: "aliyundrive.net",
57 | userIds: {
58 | mediaPathMappingGroup01: ["ac0d220d548f43bbb73cf9b44b2ddf0e"],
59 | allowInteractiveSearch: [],
60 | },
61 | filePaths: {
62 | mediaMountPath: [],
63 | redirectStrmLastLinkRule: [],
64 | mediaPathMappingGroup01: [],
65 | },
66 | };
67 |
68 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配)
69 | // 参数2: 匹配类型或来源(字符串参数类型),默认为 "filePath": 本地文件为路径,strm 为远程链接
70 | // ,有分组时不可省略填写,可为表达式
71 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
72 | // ,分组时建议写 "startsWith" 这样的字符串,方便日志中排错
73 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
74 | const ruleRef = {
75 | // 这个 key 值仅仅只是代码中引用的可读性标识,需见名知意,可自定义
76 | // mediaPathMappingGroup01: [
77 | // ["mediaPathMappingGroup01", "filePath", "startsWith", strHead.filePaths.mediaPathMappingGroup01], // 目标地址
78 | // ["mediaPathMappingGroup01", "r.args.X-Emby-Client", "startsWith:not", strHead.xEmbyClients.seekBug], // 链接入参,客户端类型
79 | // ["mediaPathMappingGroup01", "r.args.UserId", "startsWith", strHead.userIds.mediaPathMappingGroup01],
80 | // ],
81 | // directHlsEnable: [
82 | // // 此条规则代表大于等于 4Mbps 码率时生效,XMedia 为固定值,平方使用双星号表示
83 | // ["directHlsEnable", "r.XMedia.Bitrate", ">=", 4 * 1024 ** 2],
84 | // ["directHlsEnable", "r.args.UserId", "==", "ac0d220d548f43bbb73cf9b44b2ddf0e"],
85 | // ]
86 | };
87 |
88 | // 路由缓存配置
89 | const routeCacheConfig = {
90 | // 总开关,是否开启路由缓存,此为一级缓存,添加阶段为 redirect 和 proxy 之前
91 | // 短时间内同客户端访问相同资源不会再做判断和请求 alist,有限的防抖措施,出现问题可以关闭此选项
92 | enable: true,
93 | // 二级缓存开关,仅针对直链,添加阶段为进入单集详情页,clientSelfAlistRule 中的和首页直接播放的不生效
94 | enableL2: false,
95 | // 缓存键表达式,默认值好处是命中范围大,但会导致 routeRule 中针对设备的规则失效,多个变量可自行组合修改,冒号分隔
96 | // 注意 jellyfin 是小写开头 mediaSourceId
97 | keyExpression: "r.uri:r.args.MediaSourceId", // "r.uri:r.args.MediaSourceId:r.args.X-Emby-Device-Id"
98 | };
99 |
100 | // 指定需要获取符号链接真实路径的规则,优先级在 mediaMountPath 和 routeRule 之间
101 | // 注意前提条件是此程序或容器必须挂载或具有对应目录的读取权限,否则将跳过处理,回源中转
102 | // 此参数仅在软链接后的文件名和原始文件名不一致或路径差异较大时使用,其余情况建议用 mediaPathMapping
103 | // 参数1: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
104 | // 参数2: 匹配目标,对象为媒体服务入库的文件路径(Item.Path)
105 | const symlinkRule = [
106 | // [0, "/mnt/sda1"],
107 | ];
108 |
109 | // 路由规则,注意有先后顺序,"proxy"规则优先级最高,其余依次,千万注意规则不要重叠,不然排错十分困难,字幕和图片走了缓存,不在此规则内
110 | // 参数1: 指定处理模式,单规则的默认值为"proxy",但是注意整体规则都不匹配默认值为"redirect",然后下面参数序号-1
111 | // "proxy": 原始媒体服务器处理(中转流量), "redirect": 直链 302,
112 | // "transcode": 转码,稍微有些歧义,大部分情况等同于"proxy",这里只是不做转码参数修改,具体是否转码由 emby 客户端自己判断上报或客户端手动切换码率控制,
113 | // "block": 屏蔽媒体播放和下载, "blockDownload": 只屏蔽下载, "blockPlay": 只屏蔽播放,
114 | // 参数2: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
115 | // 参数3: 匹配类型或来源(字符串参数类型) "filePath": 文件路径(Item.Path), "alistRes": alist返回的链接
116 | // 参数4: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
117 | // 参数5: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
118 | const routeRule = [
119 | // ["filePath", 0, "/mnt/sda1"],
120 | // ["filePath", 1, ".mp3"],
121 | // ["filePath", 2, "Google"],
122 | // ["alistRes", 2, "/NAS/"], // 例如使用 alias 聚合了 nas 本地文件,可能会存在卡顿或花屏
123 | // ["filePath", 3, /private/ig],
124 | // docker 注意必须为 host 模式,不然此变量全部为内网ip,判断无效,nginx 内置变量不带$,客户端地址($remote_addr)
125 | // ["r.variables.remote_addr", 0, strHead.lanIp],
126 | // ["r.headersIn.User-Agent", 2, "IE"], // 请求头参数,客户端UA
127 | // ["r.args.X-Emby-Device-Id", 0, "d4f30461-ec5c-488d-b04a-783e6f419eb1"], // 链接入参,设备id
128 | // ["r.args.X-Emby-Device-Name", 0, "Microsoft Edge Windows"], // 链接入参,设备名称
129 | // ["r.args.UserId", 0, "ac0d220d548f43bbb73cf9b44b2ddf0e"], // 链接入参,用户id
130 | // 注意非"proxy"无法使用"alistRes"条件,因为没有获取 alist 直链的过程
131 | // ["proxy", "filePath", 0, "/mnt/sda1"],
132 | // ["redirect", "filePath", 0, "/mnt/sda2"],
133 | // ["transcode", "filePath", 0, "/mnt/sda3"],
134 | // ["transcode", "115-local", "r.args.X-Emby-Client", 0, strHead.xEmbyClients.XXX],
135 | // ["transcode", "115-local", "filePath", 0, "/mnt/115"],
136 | // ["block", "filePath", 0, "/mnt/sda4"],
137 | // 此条规则代表大于等于 3Mbps 码率的走转码,XMedia 为固定值,平方使用双星号表示,无意义减加仅为示例,注意 emby/jellyfin 码率为 bps 单位
138 | // ["transcode", "r.XMedia.Bitrate", ">=", 3 * 1000 ** 2 - (1 * 1000 ** 2) + (1 * 1000 ** 2)],
139 | // 精确屏蔽指定功能,注意同样是整体规则都不匹配默认走"redirect",即不屏蔽,建议只用下方一条,太复杂的话需要自行测试
140 | // ["blockDownload", "屏蔽下载", "r.headersIn.User-Agent", "includes", strHead.xUAs.clients3rdParty],
141 | // 非必须,该分组内细分为用户 id 白名单,结合上面一条代表 "屏蔽指定标识客户端的非指定用户的下载"
142 | // ["blockDownload", "屏蔽下载", "r.args.UserId", "startsWith:not", ["ac0d220d548f43bbb73cf9b44b2ddf0e"]],
143 | // 非必须,该分组内细分为入库路径黑名单,结合上面两条代表 "屏蔽指定标识客户端的非指定用户的指定入库路径的下载"
144 | // ["blockDownload", "屏蔽下载", "filePath", "startsWith", ["/mnt/115"]],
145 | ];
146 |
147 | // 路径映射,会在 mediaMountPath 之后从上到下依次全部替换一遍,不要有重叠,注意 /mnt 会先被移除掉了
148 | // 参数?.1: 生效规则三维数组,有时下列参数序号加一,优先级在参数2之后,需同时满足,多个组是或关系(任一匹配)
149 | // 参数1: 0: 默认做字符串替换replace一次, 1: 前插, 2: 尾插, 3: replaceAll替换全部
150 | // 参数2: 0: 默认只处理本地路径且不为 strm, 1: 只处理 strm 内部为/开头的相对路径, 2: 只处理 strm 内部为远程链接的, 3: 全部处理
151 | // 参数3: 来源, 参数4: 目标
152 | const mediaPathMapping = [
153 | // [0, 0, "/aliyun-01", "/aliyun-02"],
154 | // [0, 2, "http:", "https:"],
155 | // [0, 2, ":5244", "/alist"],
156 | // [0, 0, "D:", "F:"],
157 | // [0, 0, /blue/g, "red"], // 此处正则不要加引号
158 | // [1, 1, `${alistPublicAddr}/d`],
159 | // [2, 2, "?xxx"],
160 | // 此条是一个规则变量引用,方便将规则汇合到同一处进行管理
161 | // [ruleRef.mediaPathMappingGroup01, 0, 0, "/aliyun-01", "/aliyun-02"],
162 | // 路径映射多条规则会从上至下依次执行,如下有同一个业务关系集的,注意带上区间的闭合条件,不然会被后续重复替换会覆盖
163 | // 以下是按码率条件进行路径映射,全用户设备强制,区分用户和设备可再精确添加条件
164 | // [[["4K 目录映射到 1080P 目录", "r.XMedia.Bitrate", ">", 10 * 1000 ** 2],
165 | // ], 0, 0, "/4K/", "/1080P/"],
166 | // [[["1080P 目录映射到 720P 目录", "r.XMedia.Bitrate", ">", 6 * 1000 ** 2],
167 | // ["1080P 目录映射到 720P 目录", "r.XMedia.Bitrate", "<=", 10 * 1000 ** 2],
168 | // ], 0, 0, "/1080P/", "/720P/"],
169 | // [[["720P 目录映射到 480P 目录", "r.XMedia.Bitrate", ">", 3 * 1000 ** 2],
170 | // ["720P 目录映射到 480P 目录", "r.XMedia.Bitrate", "<=", 6 * 1000 ** 2],
171 | // ], 0, 0, "/720P/", "/480P/"],
172 | ];
173 |
174 | // 仅针对 alist 返回的 raw_url 进行路径映射,优先级在 mediaPathMapping 和 clientSelfAlistRule 后,使用方法一样
175 | // 参数?.1: 生效规则三维数组,有时下列参数序号加一,优先级在参数2之后,需同时满足,多个组是或关系(任一匹配)
176 | // 参数1: 0: 默认做字符串替换replace一次, 1: 前插, 2: 尾插, 3: replaceAll替换全部
177 | // 参数2: 0: 默认只处理本地路径且不为 strm, 1: 只处理 strm 内部为/开头的相对路径, 2: 只处理 strm 内部为远程链接的, 3: 全部处理
178 | // 参数3: 来源, 参数4: 目标
179 | const alistRawUrlMapping = [
180 | // [0, 0, "/alias/movies", "/aliyun-01"],
181 | ];
182 |
183 | // 指定是否转发由 njs 获取 strm/远程链接 重定向后直链地址的规则,例如 strm/远程链接 内部为局域网 ip 或链接需要验证
184 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
185 | // 参数2: 匹配类型或来源(字符串参数类型),默认为 "filePath": mediaPathMapping 映射后的 strm/远程链接 内部链接
186 | // ,有分组时不可省略填写,可为表达式
187 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
188 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
189 | const redirectStrmLastLinkRule = [
190 | [0, strHead.lanIp.map(s => "http://" + s)],
191 | // [0, alistAddr],
192 | // [0, "http:"],
193 | // 参数5: 请求验证类型,当前 alistAddr 不需要此参数
194 | // 参数6: 当前 alistAddr 不需要此参数,alistSignExpireTime
195 | // [3, "http://otheralist1.com", "sign", `${alistToken}:${alistSignExpireTime}`],
196 | // useGroup01 同时满足才命中
197 | // ["useGroup01", "filePath", "startsWith", strHead.lanIp.map(s => "http://" + s)], // 目标地址
198 | // ["useGroup01", "r.args.X-Emby-Client", "startsWith:not", strHead.xEmbyClients.seekBug], // 链接入参,客户端类型
199 | // docker 注意必须为 host 模式,不然此变量全部为内网ip,判断无效,nginx 内置变量不带$,客户端地址($remote_addr)
200 | // ["useGroup01", "r.variables.remote_addr", 0, strHead.lanIp], // 远程客户端为内网
201 | ];
202 |
203 | // 指定客户端自己请求并获取 alist 直链的规则,代码优先级在 redirectStrmLastLinkRule 之后
204 | // 特殊情况使用,则此处必须使用域名且公网畅通,用不着请保持默认
205 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
206 | // 参数2: 匹配类型或来源(字符串参数类型),优先级高"filePath": 文件路径(Item.Path),默认为"alistRes": alist 返回的链接 raw_url
207 | // ,有分组时不可省略填写,可为表达式,然后下面参数序号-1
208 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
209 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
210 | // 参数5: 指定转发给客户端的 alist 的 host 前缀,兼容 sign 参数
211 | const clientSelfAlistRule = [
212 | // Infuse 客户端对于 115 的进度条拖动可能依赖于此
213 | // 如果 nginx 为 https,则此 alist 也必须 https,浏览器行为客户端会阻止非 https 请求
214 | [2, strHead["115"], alistPublicAddr],
215 | // [2, strHead.ali, alistPublicAddr],
216 | // 优先使用 filePath,可省去一次查询 alist,如驱动为 alias,则应使用 alistRes
217 | // ["115-local", "filePath", 0, "/mnt/115", alistPublicAddr],
218 | // ["115-local", "r.args.X-Emby-Client", 0, strHead.xEmbyClients.seekBug], // 链接入参,客户端类型
219 | // ["115-alist", "alistRes", 2, strHead["115"], alistPublicAddr],
220 | // ["115-alist", "r.args.X-Emby-Client", 0, strHead.xEmbyClients.seekBug],
221 | ];
222 |
223 | // 响应重定向链接前是否检测有效性,无效链接时转给媒体服务器回源中转处理
224 | const redirectCheckEnable = false;
225 |
226 | // 媒体服务/alist 查询失败后是否使用原始链接回源中转流量处理,如无效则直接返回 500
227 | const fallbackUseOriginal = true;
228 |
229 | // 转码配置,默认 false,将按之前逻辑禁止转码处理并移除转码选项参数,与服务端允许转码配置有相关性
230 | const transcodeConfig = {
231 | enable: false, // 此大多数情况下为允许转码的总开关
232 | enableStrmTranscode: false, // 默认禁用 strm 的转码,体验很差,仅供调试使用
233 | type: "distributed-media-server", // 负载类型,可选值, ["nginx", "distributed-media-server"]
234 | maxNum: 3, // 单机最大转码数量,有助于加速轮询, 参数暂无作用,接口无法查询转码情况,忽略此参数
235 | redirectTransOptEnable: true, // 是否保留码率选择,不保留官方客户端将无法手动切换至转码
236 | targetItemMatchFallback: "redirect", // 目标服务媒体匹配失败后的降级后路由措施,可选值, ["redirect", "proxy"]
237 | // 如果只需要当前服务转码,enable 改为 true,server 改为下边的空数组
238 | server: [],
239 | // !!!实验功能,主库和所有从库给用户开启[播放-如有必要,在媒体播放期间允许视频转码]+[倒数7行-允许媒体转换]
240 | // type: "nginx", nginx 负载均衡,好处是使用简单且内置均衡参数选择,缺点是流量全部经过此服务器,
241 | // 且使用条件很苛刻,转码服务组中的媒体 id 需要和主媒体库中 id 一致,自行寻找实现主从同步,完全同步后,ApiKey 也是一致的
242 | // type: "distributed-media-server", 分布式媒体服务负载均衡(暂未实现均衡),优先利用 302 真正实现流量的 LB,且灵活,
243 | // 不区分主从,当前访问服务即为主库,可 emby/jellyfin 混搭,挂载路径可以不一致,但要求库中的标题和语种一致且原始文件名一致
244 | // 负载的服务组,需要分离转码时才使用,注意下列 host 必须全部为公网地址,会 302 给客户端访问,若参与负载下边手动添加
245 | // server: [
246 | // {
247 | // type: "emby",
248 | // host: "http://yourdomain.com:8096",
249 | // apiKey: "f839390f50a648fd92108bc11ca6730a",
250 | // },
251 | // {
252 | // type: "jellyfin",
253 | // host: "http://yourdomain.com:8097",
254 | // apiKey: "f839390f50a648fd92108bc11ca6730a",
255 | // },
256 | // ]
257 | };
258 |
259 | // 图片缓存策略,包括主页、详情页、图片库的原图,路由器 nginx 请手动调小 conf 中 proxy_cache_path 的 max_size
260 | // 0: 不同尺寸设备共用一份缓存,先访问先缓存,空间占用最小但存在小屏先缓存大屏看的图片模糊问题
261 | // 1: 不同尺寸设备分开缓存,空间占用适中,命中率低下,但契合 emby 的图片缩放处理
262 | // 2: 不同尺寸设备共用一份缓存,空间占用最大,移除 emby 的缩放参数,直接原图高清显示
263 | // 3: 关闭 nginx 缓存功能,已缓存文件不做处理
264 | const imageCachePolicy = 0;
265 |
266 | // 对接 emby 通知管理员设置,目前只发送是否直链成功和屏蔽详情,依赖 emby/jellyfin 的 webhook 配置并勾选外部通知
267 | const embyNotificationsAdmin = {
268 | enable: false,
269 | includeUrl: false, // 链接太长,默认关闭
270 | name: "【emby2Alist】",
271 | };
272 |
273 | // 对接 emby 设备控制推送通知消息,目前只发送是否直链成功,此处为统一开关,范围为所有的客户端,通知目标只为当前播放的设备
274 | const embyRedirectSendMessage = {
275 | enable: false,
276 | header: "【emby2Alist】",
277 | timeoutMs: -1, // 消息通知弹窗持续毫秒值
278 | };
279 |
280 | // 按路径匹配规则隐藏部分接口返回的 items
281 | // 参数1: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
282 | // 参数2: 匹配目标,对象为 Item.Path
283 | // 参数3: 0: 默认同时过滤下列所有类型接口, 1: 只隐藏[搜索建议(不会过滤搜索接口)]接口,
284 | // 2: 只隐藏[更多类似(若当前浏览项目位于规则中,将跳过隐藏)]接口, 3: 只隐藏第三方使用的[海报推荐]接口
285 | // 4: 只隐藏[首页最新项目]接口,
286 | const itemHiddenRule = [
287 | // [0, "/mnt/sda1"],
288 | // [1, ".mp3", 1],
289 | // [2, "Google", 2],
290 | // [3, /private/ig],
291 | ];
292 |
293 | // 串流配置
294 | const streamConfig = {
295 | // 默认不启用,因违反 HTTP 规范,链接中携带未编译中文,可能存在兼容性问题,如发现串流访问失败,请关闭此选项,
296 | // !!! 谨慎开启,启用后将修改直接串流链接为真实文件名,方便第三方播放器友好显示和匹配,
297 | // 该选项只对 emby 有用, jellyfin 为前端自行拼接的
298 | useRealFileName: false,
299 | };
300 |
301 | // 搜索接口增强配置
302 | const searchConfig = {
303 | // 开启脚本的部分交互性功能
304 | interactiveEnable: false,
305 | // 快速交互,启用后将根据指令头匹配,直接返回虚拟搜索结果,不经过回源查询,优化搜索栏失焦的自动搜索
306 | interactiveFast: false,
307 | // 限定交互性功能的隔离,取值来源为带参数的 request_uri 字符串
308 | // 不带协议与域名,仅作包含匹配,多个值为或的关系,未定义或空数组为不隔离
309 | //interactiveEnableRule: [
310 | // "ac0d220d548f43bbb73cf9b44b2ddf0e", // request_uri path level userId
311 | // "2d427412-43e1-49e4-a1db-fa17c04d49db", // X-Emby-Device-Id
312 | //],
313 | };
314 |
315 | // 115网盘 web cookie, 会覆盖从 alist 获取到的 cookie
316 | const webCookie115 = "";
317 | // 网盘转码直链配置,当前仅支持 115(必填 webCookie115) 和 emby 挂载媒体环境
318 | const directHlsConfig = {
319 | enable: false,
320 | // 仅在首次占位未获取清晰度时,默认播放最小,开启后默认播放最大,版本缓存有效期内客户端自行选择
321 | defaultPlayMax: false,
322 | // 启用规则,仅在 enable = true 时生效
323 | enableRule: ruleRef.directHlsEnable ?? [],
324 | };
325 |
326 | // PlaybackInfo 接口的一些增强配置
327 | const playbackInfoConfig = {
328 | enabled: true,
329 | // 根据规则组指定播放源排序规则(与 redirectStrmLastLinkRule 配置类似,但必须设置分组名)
330 | // sourcesSortRules 为旧版兼容排序规则,同时做为未匹配的默认排序规则,不要使用这个组名
331 | // 匹配规则越靠前优先级越高
332 | // 参数1: 分组名,组内为与关系(全部匹配),排序规则 key 需要与分组名相同
333 | // 参数2: 匹配类型或来源(字符串参数类型),不支持 filePath 和 alistRes 变量
334 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
335 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
336 | sourcesSortFitRule: [
337 | // ["useGroup01", "r.variables.remote_addr", 0, strHead.lanIp], // 客户端为内网
338 | // ["useGroup01", "r.args.X-Emby-Client", "startsWith", strHead.xEmbyClients.seekBug], // Emby 客户端类型
339 | // ["useGroup02", "r.variables.remote_addr", "startsWith:not", strHead.lanIp[0]], // 公网
340 | // ["useGroup02", "r.variables.remote_addr", "startsWith:not", strHead.lanIp[3]], // 公网
341 | // ["useGroup03", "r.args.X-Emby-Client", 2, "Emby Web"], // Emby 客户端类型为浏览器
342 | // ["useGroup03", "r.headersIn.user-agent", 2, "Chrome"], // 通用客户端 UA 标识
343 | ],
344 | // 多版本播放源排序规则,对接口数据 MediaSources 数组进行排序,优先级从上至下,数组内从左至右,支持正则表达式
345 | // key 使用"."进行层级,分割后的键按层级从 MediaSources 获取,根据分割键获取下一层值时若对象为数组,则过滤[Type === 分割键]的第一行数据
346 | // (如: "MediaStreams.Video.Height"规则中 MediaSources.MediaStreams 值为数组,则取数组中[Type === "Video"]的对象的 Height 值)
347 | // ":length"为关键字,用于数组长度排序
348 | // value 只有三种类型, "asc": 正序, "desc": 倒序, 字符串/正则混合数组: 指定按关键字顺序排序
349 | // 非正则情况下, value 不区分大小写(简化书写), 只有正则区分大小写
350 | sourcesSortRules: {
351 | // "Path": ["1080p", "720p", "480p", "hevc", "h265", "h264"], // 按原文件名路径关键字排序
352 | // "Path": [/^\d{4}p$/g, /^\d{3}p$/g, "hevc", "h265", "h264"], // 正则匹配 4 位数字排在 3 位数字前面并忽略大小写
353 | // "MediaStreams.Video.Height": "desc", // 按视频高度倒序
354 | // "MediaStreams.Video.Codec": ["AV1", "HEVC", "H264"], // 按视频编码排序
355 | // "MediaStreams.Subtitle:length": "desc", // 更多字幕的排在前面
356 | // "MediaStreams.Video.BitRate": "asc", // 码率正序
357 | },
358 | // useGroup01: {
359 | // "MediaStreams.Video.Width": "desc",
360 | // "MediaStreams.Video.ExtendedVideoSubType": ["DoviProfile5", "DoviProfile8", "Hdr10", "DoviProfile7", "None"], // 视频编码子类型
361 | // "MediaStreams.Video.BitRate": "desc", // 码率倒序
362 | // "MediaStreams.Video.RealFrameRate": "desc", // 帧率倒序
363 | // },
364 | // useGroup02: {
365 | // "MediaStreams.Video.BitRate": "asc",
366 | // "MediaStreams.Video.RealFrameRate": "asc",
367 | // },
368 | // useGroup03: {
369 | // "MediaStreams.Video.VideoRange": ["SDR"], // 视频动态范围
370 | // },
371 | }
372 |
373 | // nginx 配置 Start
374 |
375 | const nginxConfig = {
376 | // 禁用上游服务的 docs 页面
377 | disableDocs: true,
378 | };
379 |
380 | // for js_set
381 | function getDisableDocs(r) {
382 | const value = nginxConfig.disableDocs
383 | && !ngx.shared["tmpDict"].get("opendocs");
384 | // r.log(`getDisableDocs: ${value}`);
385 | return value;
386 | }
387 |
388 | // nginx 配置 End
389 |
390 | // for js_set
391 | function getEmbyHost(r) {
392 | return embyHost;
393 | }
394 | function getTranscodeEnable(r) {
395 | return transcodeConfig.enable;
396 | }
397 | function getTranscodeType(r) {
398 | return transcodeConfig.type;
399 | }
400 | function getImageCachePolicy(r) {
401 | return imageCachePolicy;
402 | }
403 | function getUsersItemsLatestFilterEnable(r) {
404 | return itemHiddenRule.some(rule => !rule[2] || rule[2] == 0 || rule[2] == 4);
405 | }
406 |
407 | export default {
408 | embyHost,
409 | embyApiKey,
410 | mediaMountPath,
411 | alistAddr,
412 | alistToken,
413 | alistSignEnable,
414 | alistSignExpireTime,
415 | alistPublicAddr,
416 | strHead,
417 | routeCacheConfig,
418 | symlinkRule,
419 | routeRule,
420 | mediaPathMapping,
421 | alistRawUrlMapping,
422 | redirectStrmLastLinkRule,
423 | clientSelfAlistRule,
424 | redirectCheckEnable,
425 | fallbackUseOriginal,
426 | transcodeConfig,
427 | embyNotificationsAdmin,
428 | embyRedirectSendMessage,
429 | itemHiddenRule,
430 | streamConfig,
431 | searchConfig,
432 | webCookie115,
433 | directHlsConfig,
434 | playbackInfoConfig,
435 | getEmbyHost,
436 | getTranscodeEnable,
437 | getTranscodeType,
438 | getImageCachePolicy,
439 | getUsersItemsLatestFilterEnable,
440 | nginxConfig,
441 | getDisableDocs,
442 | }
--------------------------------------------------------------------------------
/config/logrotate.conf:
--------------------------------------------------------------------------------
1 | /opt/MediaLinker.log {
2 | size 5M
3 | rotate 2
4 | copytruncate
5 | missingok
6 | notifempty
7 | su root root
8 | }
9 |
--------------------------------------------------------------------------------
/config/plex/constant.js:
--------------------------------------------------------------------------------
1 | // 这个总配置单体文件只是备份,生效需要放置在 conf.d 下,且重命名为 constant.js
2 | // 如果使用这个全量总配置文件,忽略 config 下的所有文件,以这个文件为准
3 |
4 | // 全量配置,媒体库混合,本地文件 + rclone/CD2 挂载的 alist 文件 + strm文件 + 软链接(路径和文件名不一致)
5 | // export constant allocation
6 |
7 | // 必填项,根据实际情况修改下面的设置
8 |
9 | // 这里默认 plex 的地址是宿主机,要注意 iptables 给容器放行端口
10 | const plexHost = "http://172.17.0.1:32400";
11 |
12 | // rclone 的挂载目录, 例如将od, gd挂载到/mnt目录下: /mnt/onedrive /mnt/gd ,那么这里就填写 /mnt
13 | // 通常配置一个远程挂载根路径就够了,默认非此路径开头文件将转给原始 plex 处理
14 | const mediaMountPath = ["/mnt"];
15 |
16 | // 访问宿主机上 5244 端口的 alist 地址, 要注意 iptables 给容器放行端口
17 | const alistAddr = "http://172.17.0.1:5244";
18 |
19 | // alist token, 在 alist 后台查看
20 | const alistToken = "alsit-123456";
21 |
22 | // alist 是否启用了 sign
23 | const alistSignEnable = false;
24 |
25 | // alist 中设置的直链过期时间,以小时为单位,严格对照 alist 设置 => 全局 => 直链有效期
26 | const alistSignExpireTime = 12;
27 |
28 | // 选填项,用不到保持默认即可
29 |
30 | // alist 公网地址, 用于需要 alist server 代理流量的情况, 按需填写
31 | const alistPublicAddr = "http://youralist.com:5244";
32 |
33 | // 字符串头,用于特殊匹配判断
34 | const strHead = {
35 | lanIp: ["172.", "10.", "192.", "[fd00:"], // 局域网ip头
36 | xEmbyClients: {
37 | seekBug: ["Emby for iOS"],
38 | },
39 | xUAs: {
40 | seekBug: ["Infuse", "VidHub", "SenPlayer"],
41 | clientsPC: ["EmbyTheater"],
42 | clients3rdParty: ["Fileball", "Infuse", "SenPlayer", "VidHub"],
43 | player3rdParty: ["dandanplay", "VLC", "MXPlayer", "PotPlayer"],
44 | blockDownload: ["Infuse-Download"],
45 | infuse: {
46 | direct: "Infuse-Direct",
47 | download: "Infuse-Download",
48 | },
49 | // 安卓与 TV 客户端不太好区分,浏览器 UA 关键字也有交叉重叠,请使用 xEmbyClients 参数或使用正则
50 | },
51 | "115": "115.com",
52 | ali: "aliyundrive.net",
53 | userIds: {
54 | mediaPathMappingGroup01: ["ac0d220d548f43bbb73cf9b44b2ddf0e"],
55 | allowInteractiveSearch: [],
56 | },
57 | filePaths: {
58 | mediaMountPath: [],
59 | redirectStrmLastLinkRule: [],
60 | mediaPathMappingGroup01: [],
61 | },
62 | };
63 |
64 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配)
65 | // 参数2: 匹配类型或来源(字符串参数类型),默认为 "filePath": 本地文件为路径,strm 为远程链接
66 | // ,有分组时不可省略填写,可为表达式
67 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
68 | // ,分组时建议写 "startsWith" 这样的字符串,方便日志中排错
69 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
70 | const ruleRef = {
71 | // 这个 key 值仅仅只是代码中引用的可读性标识,需见名知意,可自定义
72 | // mediaPathMappingGroup01: [
73 | // ["mediaPathMappingGroup01", "filePath", "startsWith", strHead.filePaths.mediaPathMappingGroup01], // 目标地址
74 | // ["mediaPathMappingGroup01", "r.args.X-Emby-Client", "startsWith:not", strHead.xEmbyClients.seekBug], // 链接入参,客户端类型
75 | // ["mediaPathMappingGroup01", "r.args.UserId", "startsWith", strHead.userIds.mediaPathMappingGroup01],
76 | // ],
77 | };
78 |
79 | // 路由缓存配置
80 | const routeCacheConfig = {
81 | // 总开关,是否开启路由缓存,此为一级缓存,添加阶段为 redirect 和 proxy 之前
82 | // 短时间内同客户端访问相同资源不会再做判断和请求 alist,有限的防抖措施,出现问题可以关闭此选项
83 | enable: true,
84 | // 二级缓存开关,仅针对直链,添加阶段为进入单集详情页,clientSelfAlistRule 中的和首页直接播放的不生效
85 | enableL2: false,
86 | // 缓存键表达式,默认值好处是命中范围大,但会导致 routeRule 中针对设备的规则失效,多个变量可自行组合修改,冒号分隔
87 | keyExpression: "r.uri:r.args.path:r.args.mediaIndex:r.args.partIndex", //"xxx:r.args.X-Plex-Client-Identifier"
88 | };
89 |
90 | // 指定需要获取符号链接真实路径的规则,优先级在 mediaMountPath 和 routeRule 之间
91 | // 注意前提条件是此程序或容器必须挂载或具有对应目录的读取权限,否则将跳过处理,回源中转
92 | // 此参数仅在软链接后的文件名和原始文件名不一致或路径差异较大时使用,其余情况建议用 mediaPathMapping
93 | // 参数1: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
94 | // 参数2: 匹配目标,对象为媒体服务入库的文件路径(Item.Path)
95 | const symlinkRule = [
96 | // [0, "/mnt/sda1"],
97 | ];
98 |
99 | // 路由规则,注意有先后顺序,"proxy"规则优先级最高,其余依次,千万注意规则不要重叠,不然排错十分困难,字幕和图片走了缓存,不在此规则内
100 | // 参数1: 指定处理模式,单规则的默认值为"proxy",但是注意整体规则都不匹配默认值为"redirect",然后下面参数序号-1
101 | // "proxy": 原始媒体服务器处理(中转流量), "redirect": 直链302, "block": 屏蔽媒体播放和下载
102 | // pelx 不需要 "transcode", 可用 "proxy" 代替,稍微有些歧义,这里只是不做修改,交给原始服务中转处理,具体是否转码由 plex 客户端自己判断上报的
103 | // 参数2: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
104 | // 参数3: 匹配类型或来源(字符串参数类型) "filePath": 文件路径(Item.Path), "alistRes": alist返回的链接
105 | // 参数4: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
106 | // 参数5: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
107 | const routeRule = [
108 | // ["filePath", 0, "/mnt/sda1"],
109 | // ["filePath", 1, ".mp3"],
110 | // ["filePath", 2, "Google"],
111 | // ["alistRes", 2, "/NAS/"], // 例如使用 alias 聚合了 nas 本地文件,可能会存在卡顿或花屏
112 | // ["filePath", 3, /private/ig],
113 | // docker 注意必须为 host 模式,不然此变量全部为内网ip,判断无效,nginx 内置变量不带$,客户端地址($remote_addr)
114 | // ["r.variables.remote_addr", 0, strHead.lanIp],
115 | // ["r.headersIn.User-Agent", 2, "IE"], // 请求头参数,客户端UA
116 | // ["r.args.X-Emby-Device-Id", 0, "d4f30461-ec5c-488d-b04a-783e6f419eb1"], // 链接入参,设备id
117 | // ["r.args.X-Emby-Device-Name", 0, "Microsoft Edge Windows"], // 链接入参,设备名称
118 | // ["r.args.UserId", 0, "ac0d220d548f43bbb73cf9b44b2ddf0e"], // 链接入参,用户id
119 | // 注意非"proxy"无法使用"alistRes"条件,因为没有获取 alist 直链的过程
120 | // ["proxy", "filePath", 0, "/mnt/sda1"],
121 | // ["redirect", "filePath", 0, "/mnt/sda2"],
122 | // ["block", "filePath", 0, "/mnt/sda4"],
123 | // 此条规则代表大于等于 3Mbps 码率的走转码,XMedia 为固定值,平方使用双星号表示,无意义减加仅为示例,注意 plex 码率为 Kbps 单位
124 | // ["transcode", "r.XMedia.bitrate", ">=", 3 * 1000 - (1 * 1000) + (1 * 1000)],
125 | // 精确屏蔽指定功能,注意同样是整体规则都不匹配默认走"redirect",即不屏蔽,建议只用下方一条,太复杂的话需要自行测试
126 | // ["blockDownload", "屏蔽下载01", "r.headersIn.User-Agent", "includes", strHead.xUAs.blockDownload],
127 | // 非必须,该分组内细分为用户 id 白名单,结合上面一条代表 "屏蔽指定标识客户端的非指定用户的下载"
128 | // ["blockDownload", "屏蔽下载01", "r.args.UserId", "startsWith:not", ["ac0d220d548f43bbb73cf9b44b2ddf0e"]],
129 | // 非必须,该分组内细分为入库路径黑名单,结合上面两条代表 "屏蔽指定标识客户端的非指定用户的指定入库路径的下载"
130 | // ["blockDownload", "屏蔽下载01", "filePath", "startsWith", ["/mnt/115"]],
131 | ];
132 |
133 | // 路径映射,会在 mediaMountPath 之后从上到下依次全部替换一遍,不要有重叠,注意 /mnt 会先被移除掉了
134 | // 参数?.1: 生效规则三维数组,有时下列参数序号加一,优先级在参数2之后,需同时满足,多个组是或关系(任一匹配)
135 | // 参数1: 0: 默认做字符串替换replace一次, 1: 前插, 2: 尾插, 3: replaceAll替换全部
136 | // 参数2: 0: 默认只处理本地路径且不为 strm, 1: 只处理 strm 内部为/开头的相对路径, 2: 只处理 strm 内部为远程链接的, 3: 全部处理
137 | // 参数3: 来源, 参数4: 目标
138 | const mediaPathMapping = [
139 | // [0, 0, "/aliyun-01", "/aliyun-02"],
140 | // [0, 2, "http:", "https:"],
141 | // [0, 2, ":5244", "/alist"],
142 | // [0, 0, "D:", "F:"],
143 | // [0, 0, /blue/g, "red"], // 此处正则不要加引号
144 | // [1, 1, `${alistPublicAddr}/d`],
145 | // [2, 2, "?xxx"],
146 | // 此条是一个规则变量引用,方便将规则汇合到同一处进行管理
147 | // [ruleRef.mediaPathMappingGroup01, 0, 0, "/aliyun-01", "/aliyun-02"],
148 | // 路径映射多条规则会从上至下依次执行,如下有同一个业务关系集的,注意带上区间的闭合条件,不然会被后续重复替换会覆盖
149 | // 以下是按码率条件进行路径映射,全用户设备强制,区分用户和设备可再精确添加条件
150 | // [[["4K 目录映射到 1080P 目录", "r.XMedia.bitrate", ">", 10 * 1000],
151 | // ], 0, 0, "/4K/", "/1080P/"],
152 | // [[["1080P 目录映射到 720P 目录", "r.XMedia.bitrate", ">", 6 * 1000],
153 | // ["1080P 目录映射到 720P 目录", "r.XMedia.bitrate", "<=", 10 * 1000],
154 | // ], 0, 0, "/1080P/", "/720P/"],
155 | // [[["720P 目录映射到 480P 目录", "r.XMedia.bitrate", ">", 3 * 1000],
156 | // ["720P 目录映射到 480P 目录", "r.XMedia.bitrate", "<=", 6 * 1000],
157 | // ], 0, 0, "/720P/", "/480P/"],
158 | ];
159 |
160 | // 仅针对 alist 返回的 raw_url 进行路径映射,优先级在 mediaPathMapping 和 clientSelfAlistRule 后,使用方法一样
161 | // 参数?.1: 生效规则三维数组,有时下列参数序号加一,优先级在参数2之后,需同时满足,多个组是或关系(任一匹配)
162 | // 参数1: 0: 默认做字符串替换replace一次, 1: 前插, 2: 尾插, 3: replaceAll替换全部
163 | // 参数2: 0: 默认只处理本地路径且不为 strm, 1: 只处理 strm 内部为/开头的相对路径, 2: 只处理 strm 内部为远程链接的, 3: 全部处理
164 | // 参数3: 来源, 参数4: 目标
165 | const alistRawUrlMapping = [
166 | // [0, 0, "/alias/movies", "/aliyun-01"],
167 | ];
168 |
169 | // 指定是否转发由 njs 获取 strm/远程链接 重定向后直链地址的规则,例如 strm/远程链接 内部为局域网 ip 或链接需要验证
170 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
171 | // 参数2: 匹配类型或来源(字符串参数类型),默认为 "filePath": mediaPathMapping 映射后的 strm/远程链接 内部链接
172 | // ,有分组时不可省略填写,可为表达式
173 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
174 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
175 | const redirectStrmLastLinkRule = [
176 | [0, strHead.lanIp.map(s => "http://" + s)],
177 | // [0, alistAddr],
178 | // [0, "http:"],
179 | // 参数5: 请求验证类型,当前 alistAddr 不需要此参数
180 | // 参数6: 当前 alistAddr 不需要此参数,alistSignExpireTime
181 | // [3, "http://otheralist1.com", "sign", `${alistToken}:${alistSignExpireTime}`],
182 | // useGroup01 同时满足才命中
183 | // ["useGroup01", "filePath", "startsWith", ["https://youdomain.xxx.com:88"]], // 目标地址
184 | // ["useGroup01", "r.headersIn.User-Agent", "startsWith:not", ["Infuse"]], // 链接入参,客户端类型
185 | // docker 注意必须为 host 模式,不然此变量全部为内网ip,判断无效,nginx 内置变量不带$,客户端地址($remote_addr)
186 | // ["useGroup01", "r.variables.remote_addr", 0, strHead.lanIp], // 远程客户端为内网
187 | ];
188 |
189 | // 指定客户端自己请求并获取 alist 直链的规则,代码优先级在 redirectStrmLastLinkRule 之后
190 | // 特殊情况使用,则此处必须使用域名且公网畅通,用不着请保持默认
191 | // 参数1: 分组名,组内为与关系(全部匹配),多个组和没有分组的规则是或关系(任一匹配),然后下面参数序号-1
192 | // 参数2: 匹配类型或来源(字符串参数类型),优先级高"filePath": 文件路径(Item.Path),默认为"alistRes": alist 返回的链接 raw_url
193 | // ,有分组时不可省略填写,可为表达式,然后下面参数序号-1
194 | // 参数3: 0: startsWith(str), 1: endsWith(str), 2: includes(str), 3: match(/ain/g)
195 | // 参数4: 匹配目标,为数组的多个参数时,数组内为或关系(任一匹配)
196 | // 参数5: 指定转发给客户端的 alist 的 host 前缀,兼容 sign 参数
197 | const clientSelfAlistRule = [
198 | // Infuse 客户端对于 115 的进度条拖动可能依赖于此
199 | // 如果 nginx 为 https,则此 alist 也必须 https,浏览器行为客户端会阻止非 https 请求
200 | [2, strHead["115"], alistPublicAddr],
201 | // [2, strHead.ali, alistPublicAddr],
202 | // 优先使用 filePath,可省去一次查询 alist,如驱动为 alias,则应使用 alistRes
203 | // ["115-local", "filePath", 0, "/mnt/115", alistPublicAddr],
204 | // ["115-local", "r.args.X-Emby-Client", 0, strHead.xEmbyClients.seekBug], // 链接入参,客户端类型
205 | // ["115-alist", "alistRes", 2, strHead["115"], alistPublicAddr],
206 | // ["115-alist", "r.args.X-Emby-Client", 0, strHead.xEmbyClients.seekBug],
207 | ];
208 |
209 | // 响应重定向链接前是否检测有效性,无效链接时转给媒体服务器回源中转处理
210 | const redirectCheckEnable = false;
211 |
212 | // 媒体服务/alist 查询失败后是否使用原始链接回源中转流量处理,如无效则直接返回 500
213 | const fallbackUseOriginal = true;
214 |
215 | // 转码配置,默认 false,将按之前逻辑强制直接播放
216 | // plex 只能用自身服务转码,只有下面一个参数,多填写没用
217 | const transcodeConfig = {
218 | enable: false, // 此为允许转码的总开关
219 | };
220 |
221 | // for js_set
222 | function getPlexHost(r) {
223 | return plexHost;
224 | }
225 | function getTranscodeEnable(r) {
226 | return transcodeConfig.enable;
227 | }
228 |
229 | export default {
230 | plexHost,
231 | mediaMountPath,
232 | routeCacheConfig,
233 | symlinkRule,
234 | routeRule,
235 | alistAddr,
236 | alistToken,
237 | alistSignEnable,
238 | alistSignExpireTime,
239 | alistPublicAddr,
240 | strHead,
241 | clientSelfAlistRule,
242 | redirectCheckEnable,
243 | fallbackUseOriginal,
244 | mediaPathMapping,
245 | alistRawUrlMapping,
246 | redirectStrmLastLinkRule,
247 | transcodeConfig,
248 | getPlexHost,
249 | getTranscodeEnable,
250 | }
--------------------------------------------------------------------------------
/config/ssl:
--------------------------------------------------------------------------------
1 | #https://go-acme.github.io/lego/dns/ dns模板
2 |
3 | CLOUDFLARE_DNS_API_TOKEN=【apikey】 \
4 | /lego --email 【CF邮箱账号】 --dns cloudflare --domains "${SSL_DOMAIN}" --accept-tos run
--------------------------------------------------------------------------------
/deploy/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | medialinker:
4 | restart: always
5 | volumes:
6 | - '/volume1/docker/medialinker/:/opt/'
7 | environment:
8 | - AUTO_UPDATE=false
9 | - SERVER=emby
10 | - NGINX_PORT=8091
11 | - NGINX_SSL_PORT=8095
12 |
13 | container_name: medialinker
14 | image: 'thsrite/medialinker:latest'
15 | network_mode: "host"
--------------------------------------------------------------------------------
/deploy/my-MediaLinker.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | MediaLinker
4 | thsrite/medialinker:latest
5 | https://github.com/orgs/linuxserver/packages/container/package/nginx
6 | bridge
7 |
8 | sh
9 | false
10 | https://github.com/linuxserver/docker-nginx/issues/new/choose
11 | https://nginx.org/
12 | chen3861229/embyExternalUrl docker版本
13 | Network:Web Network:Proxy
14 | http://[IP]:[PORT:8091]
15 | https://raw.githubusercontent.com/linuxserver/templates/master/unraid/nginx.xml
16 | https://mirror.ghproxy.com/https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins-Official/main/icons/Nginx_D.png
17 |
18 |
19 |
20 | 1716642117
21 | Donations
22 | https://www.linuxserver.io/donate
23 |
24 | true
25 | emby
26 | 8091
27 | 8093
28 | /mnt/user/appdata/MediaLinker/
29 | 8091
30 |
31 |
--------------------------------------------------------------------------------
/entrypoint:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 启动 logrotate 并定时运行
4 | while true; do
5 | /usr/sbin/logrotate /etc/logrotate.d/medialinker
6 | sleep 300
7 | done &
8 |
9 | # 启动 MediaLinker 服务
10 | /bin/sh /start_server 2>&1 | tee -a /opt/MediaLinker.log
--------------------------------------------------------------------------------
/start_server:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck shell=bash
3 | # shellcheck disable=SC2016
4 | # shellcheck disable=SC2154
5 | # 自动更新
6 | if [[ "${AUTO_UPDATE}" = "true" ]];then
7 | echo "The automatic update is detected, and the main program is being updated...."
8 | cd /embyExternalUrl && git pull
9 | echo "The main program update is complete."
10 | else
11 | if [[ -z "${GIT_COMMIT_HASH}" ]];then
12 | echo "The GIT_COMMIT_HASH environment variable is not set, will not update the main program."
13 | else
14 | echo "The GIT_COMMIT_HASH environment variable is set, the main program is being updated to the specified commit hash ${GIT_COMMIT_HASH}..."
15 | cd /embyExternalUrl && git checkout ${GIT_COMMIT_HASH}
16 | echo "The main program checkout is complete."
17 | fi
18 | fi
19 |
20 | # 软链接ngx_http_js_module.so
21 | if [ ! -e "/etc/nginx/modules/ngx_http_js_module.so" ]; then
22 | ln -s /usr/lib/nginx/modules/ngx_http_js_module.so /etc/nginx/modules/ngx_http_js_module.so
23 | fi
24 |
25 | # emby
26 | if [ "${SERVER}" = "emby" ]; then
27 | echo "The emby service is detected, and the configuration file is being linked...."
28 | # 删除原有配置文件
29 | rm -rf /etc/nginx/conf.d /etc/nginx/nginx.conf
30 | # 根据服务获取不同的配置文件
31 | cp -rf /embyExternalUrl/emby2Alist/nginx/conf.d /etc/nginx/conf.d
32 | cp -rf /embyExternalUrl/emby2Alist/nginx/nginx.conf /etc/nginx/nginx.conf
33 | fi
34 |
35 | # plex
36 | if [ "${SERVER}" = "plex" ]; then
37 | echo "The plex service is detected, and the configuration file is being linked...."
38 | # 删除原有配置文件
39 | rm -rf /etc/nginx/conf.d /etc/nginx/nginx.conf
40 | # 根据服务获取不同的配置文件
41 | cp -rf /embyExternalUrl/plex2Alist/nginx/conf.d /etc/nginx/conf.d
42 | cp -rf /embyExternalUrl/plex2Alist/nginx/nginx.conf /etc/nginx/nginx.conf
43 | fi
44 |
45 | # 判断是否存在配置文件,不存在则创建
46 | if [ ! -e "/opt/constant.js" ]; then
47 | echo "The configuration file is not mapped, creating a new one..."
48 | if [ "${SERVER}" = "plex" ]; then
49 | cp /embyExternalUrl/plex2Alist/nginx/conf.d/exampleConfig/constant-all.js /opt/constant.js
50 | elif [ "${SERVER}" = "emby" ]; then
51 | cp /embyExternalUrl/emby2Alist/nginx/conf.d/exampleConfig/constant-all.js /opt/constant.js
52 | else
53 | echo "The server is not specified, please specify the server name in the environment variable SERVER."
54 | exit 1
55 | fi
56 | fi
57 |
58 | # 软连接配置文件
59 | if [ ! -h "/etc/nginx/conf.d/constant.js" ]; then
60 | rm -rf /etc/nginx/conf.d/constant.js
61 | ln -s /opt/constant.js /etc/nginx/conf.d/constant.js
62 | fi
63 |
64 | # nginx日志输出到stdout
65 | if [[ -z "${NGINX_LOG_LEVEL}" ]]; then
66 | if ! grep -q "error_log /dev/stdout info;" /etc/nginx/nginx.conf; then
67 | sed -i '/pid\s*\/var\/run\/nginx.pid;/i error_log /dev/stdout info;' /etc/nginx/nginx.conf
68 | fi
69 | if ! grep -q "access_log /dev/stdout main;" /etc/nginx/nginx.conf; then
70 | sed -i '/sendfile\s*on;/i\ access_log /dev/stdout main;' /etc/nginx/nginx.conf
71 | fi
72 | else
73 | if [[ "${NGINX_LOG_LEVEL}" == "error" ]]; then
74 | if ! grep -q "error_log /dev/stdout info;" /etc/nginx/nginx.conf; then
75 | sed -i '/pid\s*\/var\/run\/nginx.pid;/i error_log /dev/stdout info;' /etc/nginx/nginx.conf
76 | fi
77 | elif [[ "${NGINX_LOG_LEVEL}" == "access" ]]; then
78 | if ! grep -q "access_log /dev/stdout main;" /etc/nginx/nginx.conf; then
79 | sed -i '/sendfile\s*on;/i\ access_log /dev/stdout main;' /etc/nginx/nginx.conf
80 | fi
81 | elif [[ "${NGINX_LOG_LEVEL}" == "all" ]]; then
82 | if ! grep -q "error_log /dev/stdout info;" /etc/nginx/nginx.conf; then
83 | sed -i '/pid\s*\/var\/run\/nginx.pid;/i error_log /dev/stdout info;' /etc/nginx/nginx.conf
84 | fi
85 | if ! grep -q "access_log /dev/stdout main;" /etc/nginx/nginx.conf; then
86 | sed -i '/sendfile\s*on;/i\ access_log /dev/stdout main;' /etc/nginx/nginx.conf
87 | fi
88 | fi
89 | fi
90 |
91 | # 替换http、https端口
92 | sed -i "s/8091/${NGINX_PORT}/g" "/etc/nginx/conf.d/includes/http.conf"
93 | if [ -n "${NGINX_SSL_PORT}" ]; then
94 | sed -i "s/8095/${NGINX_SSL_PORT}/g" "/etc/nginx/conf.d/includes/https.conf"
95 | fi
96 |
97 | # SSL开启
98 | if [ "${SSL_ENABLE}" = "true" ]; then
99 | sed -i '/#\s*include \/etc\/nginx\/conf.d\/includes\/https\.conf;/ {
100 | s/^#//;
101 | a\include \/etc\/nginx\/conf.d\/includes\/https.conf;
102 | d
103 | }' /etc/nginx/conf.d/${SERVER}.conf
104 | echo "SSL is enabled, the https.conf file has been included."
105 |
106 | # SSL_DOMAIN、SSL变量替换
107 | cd / && /bin/sh /check_certificate
108 | if [ $? -ne 0 ]; then
109 | echo "In SSL mode, certificate verification failed, exiting the program."
110 | exit 1
111 | fi
112 |
113 | # 清空定时任务
114 | crontab -d
115 | # 将新的定时任务写入crontab
116 | echo "${SSL_CRON} cd / && /bin/sh /check_certificate" | crontab -
117 | # 重启cron服务使修改生效
118 | pkill -f crond
119 | /usr/sbin/crond
120 | echo "The scheduled task '${SSL_CRON} cd / && /bin/sh /check_certificate' has been written."
121 | else
122 | sed -i '/include \/etc\/nginx\/conf.d\/includes\/https\.conf;/ {
123 | s/.*//;
124 | a\#include \/etc\/nginx\/conf.d\/includes\/https.conf;
125 | d
126 | }' /etc/nginx/conf.d/${SERVER}.conf
127 | echo "SSL is disabled, the https.conf file has been commented out."
128 | fi
129 |
130 | # 后台启动nginx
131 | nginx -g "daemon off;"
--------------------------------------------------------------------------------