├── .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 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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;" --------------------------------------------------------------------------------