基于 E-mote 模型的动态猫娘展示与部署教程看门见山，先给一个展示地址看看效果：猫娘乐园角色E-mote图鉴Github Pages部署的纯静态版本：猫娘乐园角色E-mote图鉴这篇文章或许是最符合网站域名的文章之一？项目缘起很久之前，在我切换到Linux系统后，发现我之前最喜欢的猫娘乐园动态壁纸没办法在Linux上使用，然后根据那个第三方壁纸的相关信息，刨根问底发现了FreeMote这个开源项目，并且看到了他的Web段实现了Demo，就萌生了这个项目的想法了。但是碍于那时候技术能力和能分配的时间有限，于是这个想法就一直搁置了。最近不知道哪根经搭错了，突然就想把这个多年前的想法去实现，重新翻阅了相关的开源项目，借助AI，然后发现当时不能成功是疏漏了一些东西，于是打算再次去尝试实现。一开始的那几天天天碰壁，试了很多不同的方法都不行，但是，偶然的一次机会，我观察到提取的模型和解包的模型有相似之处，于是我就尝试从中间进行合成，没想到，真成功了！接下来的工作就顺风顺水了，但是就是很累人，要组织和转换每一个模型，八个猫娘，主要的六个猫娘还有好几套衣服，那个工作量是真的巨大啊！但是，如你所见，经过不懈努力，这个项目做出来了！接下来，请让我为你介绍——「NekoWebShow」开发技术路线简单来说，这个项目最核心的部分就是FreeMote-SDK，没有这个开源的项目，本项目也无法实现。本项目是在弄清楚FreeMote这套框架逻辑后，将从UlyssesWu制作出来的的猫娘乐园角色动态壁纸解包出来的模型转换为可以让FreeMote-SDK的WebGL驱动的pure.psb格式，然后将各个模型和对应的声音组织好，写一个用于切换不同角色的前端框架，这就是我所做的事情。前期的一部分开发工作记录，我已经写了一篇文章：试玩FreeMote，把二次元老婆搬上浏览器，感兴趣的可以去看看，这篇文章关注点是如何使用和部署。因为前端需要实现的功能比较简单，所以我是直接面向LLM编程了，没有使用其他框架。本来想用MDUI的，但是后面想想还是算了。在这里，要特别感谢嘉然小姐对本项目的支持，她帮助我解决了驱动库无法加载超过32MB的pure.psb文件问题。没有她的支持，部分角色衣服的模型就没办法正常展示出来了。项目分支说明项目有两个分支，一个是php版本，另一个是html版本。之前是只打算做php版本的，毕竟可以代码复用，减少文件数量和代码量，但是后面考虑到如果是本地部署起来做动态壁纸，还要再跑一个php-fpm有点太地狱了，于是就搞了html的版本，纯静态实现，本地只需要部署一个web服务器即可。不过按理来说应该是可以直接打开本地文件而不需要服务器的，但是不知道为什么firefox打开就是报错，显示被阻止，所以也只好设置一个服务器了。不过，后面我试了一下，使用KDE桌面的HTML Wallpaper插件，直接指定本地文件就像，不会有报错，也可以正常显示，估计只是firefox的限制（所以，要是这样就非常好了，本地设置成壁纸web服务器都省了。部署方法一：PHP环境部署首先，确保你有一台服务器，部署上web服务器和php环境，我这里选择nginx和php，采用的操作系统为Alpine Linux 3.20。apk add php php-fpm openssl nginx rc-update add php-fpm83 default rc-update add nginx default service php-fpm82 start service nginx start这样一通操作下来，我们的基本环境就有了。接下来就是配置一下默认页面的规则，当然这是可选项，你可以直接一股脑用默认直接http部署，但是我并不推荐，我认为还是设置个域名配合SSL证书比较好。设置默认匹配页面我们先来生成一个默认的SSL自签名证书用于没有匹配到域名时错误提示页面使用。mkdir -p /etc/nginx/ssl/ openssl genpkey -algorithm RSA -out /etc/nginx/ssl/selfsigned.key -pkeyopt rsa_keygen_bits:2048 openssl req -new -x509 -key /etc/nginx/ssl/selfsigned.key -out /etc/nginx/ssl/selfsigned.crt -days 365 -subj "/"这样子就生成了自签名证书，证书和密钥位置分别在/etc/nginx/ssl/selfsigned.crt和/etc/nginx/ssl/selfsigned.key然后，删除/etc/nginx/http.d/default.conf，再重新创建：rm /etc/nginx/http.d/default.conf nano /etc/nginx/http.d/default.conf内容替换为如下内容：server { listen 80 default_server; #listen [::]:443 ssl default_server; server_name _; # 这是一个通配符，表示任何未匹配的主机名 #return 444; # 关闭连接，不发送任何响应 root /var/www/; # 静态文件的位置 index index.html; # 默认主页 location / { try_files $uri $uri/ =404; # 尝试提供请求的文件或目录，如果不存在则返回404 } # 处理错误页面 error_page 404 /404.html; location = /404.html { internal; } } server { listen 443 ssl default_server; #listen [::]:443 ssl default_server; server_name _; # 这是一个通配符，表示任何未匹配的主机名 ssl_certificate /etc/nginx/ssl/selfsigned.crt; ssl_certificate_key /etc/nginx/ssl/selfsigned.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; root /var/www/; # 静态文件的位置 index index.html; # 默认主页 location / { try_files $uri $uri/ =404; # 尝试提供请求的文件或目录，如果不存在则返回404 } # 处理错误页面 error_page 404 /404.html; location = /404.html { internal; } } server { listen 80; server_name *.nekopara.uk; # 302 Redirect all traffic to the corresponding HTTPS URL location / { return 302 https://$host$request_uri; } }记得在/var/www/创建一个错误提示页面index.html:nano /var/www/index.html内容我（宝塔 x)已经帮你想好了： 没有找到站点 没有找到站点 您的请求在Web服务器中没有找到对应的站点！ 可能原因： 您没有将此域名或IP绑定到对应站点! 配置文件未生效! 如何解决： 检查是否已经绑定到对应站点，若确认已绑定，请尝试重载Web服务； 检查端口是否正确； 若您使用了CDN产品，请尝试清除CDN缓存； 普通网站访客，请联系网站管理员； 默认页面设置好了，接下来就是设置对应的站点了。部署NekoWebShow我个人使用习惯是在根目录创建/data目录用于存放各种东西，先让我们克隆项目：mkdir /data cd /data/ git clone --depth 1 https://github.com/Chocola-X/NekoWebShow然后就是申请证书并且解析域名到你的服务器，这里不再赘述，有acme.sh可以用，当然你也可以用我的网页在线生成证书：猫娘☆免费SSL证书申请申请完成的证书，放到/data/certs/目录下。包括证书和私钥，例如_nekopara_uk.pem和_nekopara_uk.key。然后是配置nginx服务器：nano /etc/nginx/http.d/nekowebshow.conf配置文件内容如下：server { listen 443 ssl; server_name chocola.nekopara.uk vanilla.nekopara.uk azuki.nekopara.uk coconut.nekopara.uk fraise.nekopara.uk maple.nekopara.uk milk.nekopara.uk cinnamon.nekopara.uk show.nekopara.uk; # SSL certificate and key paths ssl_certificate /data/certs/_nekopara_uk.pem; ssl_certificate_key /data/certs/_nekopara_uk.key; # SSL settings (optional but recommended for security) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Root directory of the website root /data/nekowebshow; index index.html index.php; # Logging #access_log /var/log/nginx/nekopara.uk.access.log; #error_log /var/log/nginx/nekopara.uk.error.log; # Serve static files directly location / { try_files $uri $uri/ /index.html /index.php?$args; } # PHP processing location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Deny access to hidden files location ~ /\.ht { deny all; } }注意将域名替换为你实际的域名，以及你实际的证书路径，证书建议使用通配符证书。因为我的域名nekopara.uk很符合主题，所以我给每一个角色都添加了子域名，例如chocola.nekopara.uk就很有趣了。对于其他域名，请根据需要自行选择。这时候，打开对应的域名，应该是可以正常工作了，但是你会发现打开新的角色图鉴，就会跳转到我的网站。不要急不要哭，打开index.php让我们来改几个参数即可，这就是为什么我选择php构建的原因。打开index.php找到开头这里：$domain_chocola = 'https://chocola.nekopara.uk'; $domain_vanilla = 'https://vanilla.nekopara.uk'; $domain_azuki = 'https://azuki.nekopara.uk'; $domain_coconut = 'https://coconut.nekopara.uk'; $domain_maple = 'https://maple.nekopara.uk'; $domain_cinnamon = 'https://cinnamon.nekopara.uk'; $domain_milk = 'https://milk.nekopara.uk'; $domain_fraise = 'https://fraise.nekopara.uk'; $background_url = './img/bg.png';替换为对应的域名即可，例如第一条替换为：$domain_chocola = 'https://chocola.example.com';然后背景也是可以修改的，替换掉bg.png或者改为别的背景图URL即可，但是我个人还是认为游戏原本的背景比较搭配。。。修改完成后，也就部署好了。你可以之作为一个普通的角色图鉴使用，当然，我认为更好的做法是把这个做成动态壁纸，使用 KDE 的 HTML Wallpaper 设置对应的URL即可。因为本来我开发这个的初衷，就是想要一个好看的动态壁纸。部署好后Be Like:部署方法二：静态HTML部署相比于方法一，这个就不需要设置php环境了，能够减小系统性能开销。缺点就是，如果你想修改一些网页参数，会比较麻烦。更合适的做法是用在本地桌面背景，而不是展示了。web服务器部署当然，你愿意折腾的话，也不是不行，参照方法一配置nginx环境和证书即可，php就不需要安装了，配置方法这里就不再赘述，就是配置文件nekowebshow.conf要稍微改变一下：server { listen 443 ssl; server_name chocola.nekopara.uk vanilla.nekopara.uk azuki.nekopara.uk coconut.nekopara.uk fraise.nekopara.uk maple.nekopara.uk milk.nekopara.uk cinnamon.nekopara.uk show.nekopara.uk; # SSL certificate and key paths ssl_certificate /data/certs/_nekopara_uk.pem; ssl_certificate_key /data/certs/_nekopara_uk.key; # SSL settings (optional but recommended for security) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Root directory of the website root /data/nekowebshow; index index.html index.php; # Logging #access_log /var/log/nginx/nekopara.uk.access.log; #error_log /var/log/nginx/nekopara.uk.error.log; location / { try_files $uri $uri/ =404; # 尝试提供请求的文件或目录，如果不存在则返回404 } # 处理错误页面 error_page 404 /404.html; location = /404.html { internal; } }要注意，克隆的时候，请克隆html_version分支：git clone --depth 1 --branch html_version https://github.com/Chocola-X/NekoWebShow当然，纯html的版本我已经在Github Pages部署了一份了，如果只是想要设置个动态壁纸不想部署的话，直接设置这个URL就好了。但是缺点是断网就寄。直接用Github Pages链接部署成桌面背景Be Like:本地文件部署如果你设置动态壁纸的软件支持直接读取本地文件且不报错的话，推荐采用这种方式。当然，本地打开html文件，firefox已经拦截跨文件夹请求了（找一个你适合存放这个壁纸的文件夹，壁纸体积大概2.9G，然后和上面一样克隆项目：git clone --depth 1 --branch html_version https://github.com/Chocola-X/NekoWebShow然后在HTML Wallpaper设置壁纸路径即可享用。虽然但是有点吃性能就是了（
我们都在被监控着众所周知，在现代的信息社会，想保持不被监控是非常困难的事情。由于种种原因，总有人会希望监控你。例如，某些视频平台（例如B站）为了提高视频推送准确度，不惜用各种手段刺探用户喜好，虽然我不好说是用了什么手段，因为没有调查就没有发言权，但是有我这种感受的人估计不在少数。我就有过类似的经历：在QQ群谈论一个事物（例如洋垃圾平台），然后打开B站首页给你推的就是刚刚你谈论过的平台相关的内容，细思极恐啊兄弟们！还有一些购物网站也会这样，为了分析用户喜好，也会想尽各种办法来刺探用户的兴趣。我也有过刚谈论完某个感兴趣的物品，淘宝或者闲鱼一打开就是相关内容。给人各种不爽。值得一提的是，在我们日常生活中，对个人隐私泄漏最严重的设备是——手机。手机也是我最鄙视的电子产品之一，尤其是现在国内手机厂商开始搞封闭，限制用户自己安装应用，非要从他们提供的如同公厕一般的垃圾应用商店去下载APP。无语至极！你想换个系统？或者删掉自带的垃圾应用（例如广告浏览器）？没门，新的国内安卓系统已经给你堵死了，解锁Bootloader更是不可能。既然都要封闭起来给用户喂屎，为什么我不选择应用商店环境更好的苹果？扯远了。。。那么，在现在这个信息化时代，还有没有办法说悄悄话呢？答案是肯定的，绝对有办法，而且方法很多。例如：用记事本写下相关的消息，然后使用约定好的密码加密压缩打包后发送。或者，如果有能力的，可以自己写一套简单的文本消息加密算法。而这次，我打算尝试搭建一个有趣的开源项目——FioraFiora是什么？Fiora是由碎碎酱使用node.js，react和socket.io打造的一款在线聊天室，可以自己托管运行，具有较高的隐私安全性。简单来说就有点类似与简单的QQ，但是服务是有你自己进行托管的。可以在一定程度上规避监控和审查。所以适合用来键（bushi不过这个项目似乎不再维护了，最后一次更新已经是两年前的2023年了，不过只是更新了Readme，代码最后一次更新已经是2022年了。。。Fiora的搭建项目的文档有详细的搭建教程和方法，至于为什么我还要写这篇博客，那是因为我遇到了一些问题，按照官方的搭建教程文档无法进行下去，所以再次整理总结经验。注意，本教程操作系统环境为Alpine Linux 3.20!搭建环境准备我们需要做的事情有：安装 Node.js安装 MongoDB安装 redis安装Node.js和redis比较简单，一条命令搞定：apk add redis npm nodejs但是，安装MongoDB就相对麻烦一点了，由于新版的Alpine将MongoDB移出了软件包列表，我们只能通过旧版的软件源安装。首先，编辑apk的软件源：nano /etc/apk/repositories添加Alpine 3.9的软件源：http://mirrors.ustc.edu.cn/alpine/v3.9/main http://mirrors.ustc.edu.cn/alpine/v3.9/community然后安装MongoDB：apk update apk add mongodb安装完成后，一定，一定，一定要删除掉刚刚添加的软件源，不然后期安装软件可能导致系统崩溃！！！然后启动相关服务：rc-update add mongodb default rc-update add redis default rc-service mongodb start rc-service redis start运行项目先克隆项目到本地：git clone https://github.com/yinxin630/fiora.git -b master然后安装yarn：npm install -g yarn安装项目依赖：yarn install注意，这一步可能会因为网络的原因报错，因为依赖在GitHub上面，GitHub国内访问经常不稳定，所以可能需要挂代理才能正常安装依赖。终端代理：export https_proxy=http://192.168.0.18:20171 export http_proxy=http://192.168.0.18:20171然后接下来就是一个意想不到的大坑：构建客户端代码：yarn build:web然后你就会喜提如下报错：localhost:/mnt/data/fiora# yarn build:web yarn run v1.22.22 $ npx lerna run build:web --stream lerna notice cli v4.0.0 lerna info versioning independent lerna info Executing command in 1 package: "yarn run build:web" @fiora/web: warning package.json: "dependencies" has dependency "@testing-library/jest-dom" with range "^5.14.1" that collides with a dependency in "devDependencies" of the same name with version "^4.2.4" @fiora/web: $ rm -rf dist && cross-env NODE_ENV=production DOTENV_CONFIG_PATH=../../.env webpack --config build/webpack.prod.js && cp -r -f dist/fiora/* ../server/public @fiora/web: ℹ Compiling Webpack @fiora/web: Error: error:0308010C:digital envelope routines::unsupported @fiora/web: at new Hash (node:internal/crypto/hash:79:19) @fiora/web: at Object.createHash (node:crypto:139:10) @fiora/web: at BulkUpdateDecorator.hashFactory (/mnt/data/fiora/packages/web/node_modules/webpack/lib/util/createHash.js:145:18) @fiora/web: at BulkUpdateDecorator.update (/mnt/data/fiora/packages/web/node_modules/webpack/lib/util/createHash.js:46:50) @fiora/web: at RawSource.updateHash (/mnt/data/fiora/packages/web/node_modules/webpack/node_modules/webpack-sources/lib/RawSource.js:64:8) @fiora/web: at NormalModule._initBuildHash (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:868:17) @fiora/web: at handleParseResult (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:934:10) @fiora/web: at /mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:1026:4 @fiora/web: at processResult (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:743:11) @fiora/web: at /mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:807:5 @fiora/web: Browserslist: caniuse-lite is outdated. Please run: @fiora/web: npx browserslist@latest --update-db @fiora/web: Why you should do it regularly: @fiora/web: https://github.com/browserslist/browserslist#browsers-data-updating @fiora/web: node:internal/crypto/hash:79 @fiora/web: this[kHandle] = new _Hash(algorithm, xofLen, algorithmId, getHashCache()); @fiora/web: ^ @fiora/web: Error: error:0308010C:digital envelope routines::unsupported @fiora/web: at new Hash (node:internal/crypto/hash:79:19) @fiora/web: at Object.createHash (node:crypto:139:10) @fiora/web: at BulkUpdateDecorator.hashFactory (/mnt/data/fiora/packages/web/node_modules/webpack/lib/util/createHash.js:145:18) @fiora/web: at BulkUpdateDecorator.update (/mnt/data/fiora/packages/web/node_modules/webpack/lib/util/createHash.js:46:50) @fiora/web: at RawSource.updateHash (/mnt/data/fiora/packages/web/node_modules/webpack/node_modules/webpack-sources/lib/RawSource.js:64:8) @fiora/web: at NormalModule._initBuildHash (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:868:17) @fiora/web: at handleParseResult (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:934:10) @fiora/web: at /mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:1026:4 @fiora/web: at processResult (/mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:743:11) @fiora/web: at /mnt/data/fiora/packages/web/node_modules/webpack/lib/NormalModule.js:807:5 { @fiora/web: opensslErrorStack: [ @fiora/web: 'error:03000086:digital envelope routines::initialization error', @fiora/web: 'error:0308010C:digital envelope routines::unsupported' @fiora/web: ], @fiora/web: library: 'digital envelope routines', @fiora/web: reason: 'unsupported', @fiora/web: code: 'ERR_OSSL_EVP_UNSUPPORTED' @fiora/web: } @fiora/web: Node.js v20.15.1 @fiora/web: error Command failed with exit code 1. @fiora/web: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. lerna ERR! yarn run build:web exited 1 in '@fiora/web' error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. localhost:/mnt/data/fiora# 这错误通常与 Node.js 和 OpenSSL 的版本兼容性有关。这个错误在较新版本的 Node.js (如 v20.15.1) 中可能出现，尤其是在使用旧版本的依赖库或工具（比如 Webpack）时。问题的根本原因在于新的 Node.js 版本默认启用了更严格的 OpenSSL 配置，而一些旧的软件可能不支持这些配置。解决方案是设置一个环境变量，然后再构建：export NODE_OPTIONS=--openssl-legacy-provider yarn build:web配置 JwtSecret：echo "JwtSecret=GTX690" > .env这里你可以根据你的需求，设置秘密文本，我这里设置的是GTX690然后就是启动Fiora了：yarn start个人建议是使用Tmux这类工具挂着：tmux new -t fiora yarn start不出意外的话，至此，Fiora应该是可以正常运行了。Fiora的管理和配置相关的配置就写在项目目录下的.env文件中，以键值对的形式存在，官方有详细的说明：https://yinxin630.github.io/fiora/zh-Hans/docs/config写的很详细，我这里就不再赘述了。但是，我在尝试运行自带的管理脚本的时候，出现问题了：localhost:/mnt/data/fiora# fiora doctor /mnt/data/fiora/packages/i18n/node.index.ts:15 return languages[locale][key] || enUS[key] || key; ^ TypeError: Cannot read properties of undefined (reading 'getUserIdDescription') at Object.i18n [as default] (/mnt/data/fiora/packages/i18n/node.index.ts:15:29) at Object.<anonymous> (/mnt/data/fiora/index.ts:14:22) at Module._compile (node:internal/modules/cjs/loader:1358:14) at Module.m._compile (/mnt/data/fiora/node_modules/ts-node/src/index.ts:1225:23) at Module._extensions..js (node:internal/modules/cjs/loader:1416:10) at Object.require.extensions.<computed> [as .ts] (/mnt/data/fiora/node_modules/ts-node/src/index.ts:1228:12) at Module.load (node:internal/modules/cjs/loader:1208:32) at Function.Module._load (node:internal/modules/cjs/loader:1024:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12) at main (/mnt/data/fiora/node_modules/ts-node/src/bin.ts:330:12) localhost:/mnt/data/fiora# 由于是Alpine Linux，默认最小化安装，所有有可能是缺了什么东西导致的问题。经过一番排查，我找到了问题所在——Locale支持缺失以下是解决方案：apk add libc6-compat libintl ettext export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8然后再运行管理工具就不会报错了。至此，搭建完成。配置SSL由于Fiora默认使用的是http通信，为明文通信，存在安全隐患，所以最好是用Nginx套一层反向代理实现Https的访问。为了使用Https，你需要有一个域名，或者一个固定的IP地址用于签发证书。以下是Nginx的配置文件：server { listen 80; # 修改为你的域名 server_name chat.nekopara.uk; return 301 https://$host$request_uri; } server { listen 443 ssl; # 修改为你的域名 server_name chat.nekopara.uk; # 修改为你的ssl证书位置 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header X-NginX-Proxy true; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; proxy_pass http://localhost:9200; } }配置完成即可实现Https访问DLC:修复Fiora背景覆盖问题我个人推断，应该是当时碎碎酱设计Fiora的时候，还是1080P屏幕的天下，为了实现毛玻璃效果，图片没有设置成随着浏览器窗口大小进行缩放剪裁。正因如此，导致了现在在高分辨率屏幕拉伸窗口，超过默认图片背景大小后剩余部分就是白色，十分的不好看。并且背景图也没有能随着浏览器窗口改变大小，让我很不爽。虽然我没用过nodejs，但是网页前端还是稍微懂一点的，解决这个问题很简单，为背景图的css样式添加两条css规则 backgroundPosition: center center;和backgroundSize: cover;即可。然而，这个项目似乎是用nodejs生成的css样式和html代码，给我的改造工作带来了不小的阻力。终于在阴差阳错下，我找到了对应的位置：fiora/packages/web/src/App.tsx在这个文件的第99行，就是要修改的位置，我贴出我修改那部分的代码： // 主体样式 const style = useMemo( () => ({ backgroundImage: `url(${backgroundImage})`, backgroundSize: `${backgroundWidth}px ${backgroundHeight}px`, backgroundRepeat: 'no-repeat', backgroundPosition: 'center center', backgroundSize: 'cover', }), [backgroundImage, backgroundWidth, backgroundHeight], ); // 聊天窗口样式 const childStyle = useMemo( () => ({ width: `${width * 100}%`, height: `${height * 100}%`, left: `${((1 - width) / 2) * 100}%`, top: `${((1 - height) / 2) * 100}%`, }), [width, height], ); // 模糊背景样式 const blurStyle = useMemo( () => ({ ...childStyle, }), [width, height, style, childStyle], );由于我技术水平有限，我只能把毛玻璃效果干掉了，如果要保留的话我不知道怎么设置图片的截取部分，这样子就直接让聊天背景透明了。效果还行吧。修改后的Fiora:Fiora.7z
为Alpine Linux 3.20 编译安装ZeroTier没有ZeroTier软件包！由于我云服务器使用的是Alpine Linux来最大程度节省资源用于实际服务，而ZeroTier官方没有给出Alpine Linux的安装包，所以要用点野路子才能安装上。使用官方的安装脚本 Be Like:Neko_SG:~# curl -s https://install.zerotier.com | bash *** ZeroTier Service Quick Install for Unix-like Systems *** Tested OSes / distributions: *** MacOS (10.13+) (just installs ZeroTier One.pkg) *** Debian Linux (7+) *** RedHat/CentOS Linux (6+) *** Fedora Linux (16+) *** SuSE Linux (12+) *** Mint Linux (20+) *** Kali Linux (2024.1+) *** Supported architectures vary by OS / distribution. We try to support *** every system architecture supported by the target. *** Please report problems by opening a GitHub issue or Pull Request at: *** https://github.com/zerotier/install.zerotier.com *** Please include the content of `/etc/os-release` for your distribution. *** Detecting Linux Distribution *** Unknown or unsupported distribution! Aborting. 想从包管理器安装 Be Like:Neko_SG:~# apk add zerotier fetch https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86_64/APKINDEX.tar.gz fetch https://dl-cdn.alpinelinux.org/alpine/latest-stable/community/x86_64/APKINDEX.tar.gz ERROR: unable to select packages: zerotier (no such package): required by: world[zerotier] Neko_SG:~# apk add zerotier-one ERROR: unable to select packages: zerotier-one (no such package): required by: world[zerotier-one] Neko_SG:~# apk search zerotier Neko_SG:~# 一个思路是去用别的发行版的软件包，解包提取二进制文件。但是由于Alpine Linux是用的C语言库是musl而不是libc，大部分发行版用点都是libc，所以不一定可以兼容。还有一个办法，那就相当硬核了，自己编译后，安装。这个方法肯定会遇到很多技术上的问题，所以也是最有挑战性的一条路子，但是，我还是决定开干。下载源代码之所以可以这么玩，是因为ZeroTier是开源的。 开源大法好！先创建一个用于干活的文件夹，然后直接从Github的Release下载源代码：mkdir /data/zerotier cd /data/zerotier wget https://github.com/zerotier/ZeroTierOne/archive/refs/tags/1.14.2.zip unzip 1.14.2.zip cd ZeroTierOne-1.14.2/准备相关环境进行编译因为Alpine Linux是轻量发行版，所以缺失的库很多，需要自己手动装。我也是边报错边安装，搞了好久。我的版本是Alpine Linux 3.20，以下是需要安装的软件包：apk add util-linux pkgconfig openssl-dev libffi cargo linux-headers g++ gcc make进行编译进入源代码目录，执行：make -j$(nproc)如果没有报错，一个会顺利在目录生成一个zerotier-one文件，然后还会有zerotier-cli和zerotier-idtool这两个软链接链接到zerotier-one。如果是这样，那就编译成功了。运行前的准备如果你直接运行。那大概率并不会出现ZeroTier的虚拟网络接口，因为相关的内核模块并没有启用。当然，这个虚拟的接口需要先加入了网络才会开启，不然也是没有的。运行这个命令开启内核模块：modprobe tun如果报错不行的话，试一下重启系统，模块要重启才能生效。但是，这样子设置的话，肯定重启就没了。所以要持久化设置，就需要修改/etc/modules文件：nano /etc/modules在文件后面加上tun：af_packet ipv6 tun这样设置后，重启也可以正常生效了。ZeroTier，启动！直接在编译好的目录下，执行./zerotier-one -d即可运行。但是，ZeroTier也还是会在/usr/lib/zerotier-one/目录下生成相关的配置文件，我个人更倾向于把那三个生成的文件放到这个目录下，跟官方的一致。不出意外的话，就能在ifconfig里面看到虚拟的接口了。编译好的文件分享如果你用的也是Alpine Linux，那你可以用我编译好的ZeroTier。这是编译好的文件下载地址：zerotier-musl.zip同样的，需要安装对应的软件包才能使用：apk add util-linux cargo linux-headers装完之后，参照上面的运行前准备部分，照做就行了。
速通Golang后的想法前段时间我处于个人兴趣目的速通了Golang的基础知识，然后就想要干些什么玩一下，想到了我QQ群里面有一个Bot还在苟活，于是有点想改造一下。机器人使用的是ZeroBot-Plugin，项目是在Github开源的，使用的是Go语言编写，这下专业对口了。正好前几天折腾LLM本地部署，尝试了一下Ollama部署各种模型，觉得挺好玩的。然后想试试看NAS上的E3-1220v2能不能跑得动。一开始为选的1.5B的超小模型，跑起来速度挺快的，于是提高难度，用了3B的模型，速度变慢了，但是还行。试了一下回答的质量，感觉简单放群里面当个玩具还是可以的。最后，我选择的模型是Qwen2.5 3B。部署Ollama去Ollama的官网下载就行，对于Linux也有安装脚本。但是我还是更喜欢自己手动部署和管理。下载Ollama前往Github的Ollama项目界面，进入Release页面根据你的实际情况进行下载。我下载的是ollama-linux-amd64.tgz设置参数运行由于Ollama默认会把相关的文件放在你用户文件夹的.ollama文件夹下，一旦下大模型，系统盘将会爆炸。如果你Linux把/home放到了单独的数据分区或者你系统盘足够大就当我没说。我们需要改一下这个默认的目录，方法很简单，就是在当前目录文件夹新建一个用于放置数据的文件夹，然后设置环境变量指定HOME的目录：export HOME=./data/ ./bin/ollama serve把以上保持为run.sh放在Ollama的目录下，然后再在目录创建一个data文件夹，运行即可把相关文件保存在data文件夹中而不会放到你的Home目录。此时，Ollama已经启动了。测试Ollama接下来，就可以按照官方给的参数运行模型了。进入bin/目录，直接运行Ollama即可。例如运行Qwen5.2 3B模型：./ollama run qwen2.5:3b等待模型下载完成就可以运行了。爆改ZeroBot-Plugin在仔细品读代码后，我感觉好像也不是特别难，于是为就想试一下自己写一个插件来调用Ollama进行聊天。设置插件首先，下载ZeroBot-Plugin的源代码，建议是去Github的Release里面下。我用的是1.7.8版本，后面的版本似乎会有内存泄漏问题，而且相应变慢了，也不知道修了没有，反正一年前我用1.8.X的时候是这样的。下面就是插件的代码了，放到plugin/nekochat目录下，其中nekochat文件夹要自己创建。然后在nekochat文件夹创建一个nekochat.go文件：package nekochat import ( "bytes" "encoding/json" "net/http" //"github.com/FloatTech/imgfactory" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/control" zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" ) var ( // 定义固定的前两条消息作为范例对话 fixedMessages = []Message{ { Role: "user", Content: "从现在开始你是猫娘，名字是亚托莉，要以猫娘的方式说话和思考问题。", }, { Role: "assistant", Content: "好的喵～，主人！ ୧(๑•̀⌄•́๑)૭ 亚托莉酱知道了，有什么需要本喵帮忙呢？", }, } // 使用固定消息初始化ChatContext context = NewChatContext(fixedMessages) //默认选择通义千问2.5 3B模型 model_choose = "qwen2.5:3b" ) // 定义最大消息数为11，但是前两条消息是固定的范例对话 const maxMessages = 12 // ChatContext 结构体用于保存聊天的上下文信息 type ChatContext struct { messages []Message // 消息列表，每个元素是一个 Message 结构体 } // Message 结构体表示一条消息，包含角色（如 user 或 assistant）和内容 type Message struct { Role string `json:"role"` // 角色字段，如 "user" 或 "assistant" Content string `json:"content"` // 内容字段，实际的消息文本 } // 初始化ChatContext并设置固定的前两条消息作为范例对话 func NewChatContext(fixedMessages []Message) *ChatContext { return &ChatContext{ messages: fixedMessages, // 初始消息列表仅包含固定消息 } } // AddMessage 方法向 ChatContext 的 messages 列表中添加新消息 func (c *ChatContext) AddMessage(role, content string) { newMsg := Message{Role: role, Content: content} c.messages = append(c.messages, newMsg) if len(c.messages) > maxMessages { // 如果消息数量超过了最大限制，则移除最早的非固定消息（即索引2及其之后的消息） c.messages = append(c.messages[:2], c.messages[3:]...) } } // SendRequestAndExtractResponse 函数发送请求至指定API端点，并提取出回答内容 func (c *ChatContext) SendRequestAndExtractResponse(userMessage string) (string, error) { // 添加用户的新消息到上下文中 c.AddMessage("user", userMessage) payload, err := json.Marshal(map[string]interface{}{ "model": model_choose, "stream": false, "messages": c.messages, }) if err != nil { return "", err } resp, err := http.Post("http://localhost:11434/api/chat", "application/json", bytes.NewBuffer(payload)) if err != nil { return "", err } defer resp.Body.Close() var response Response if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return "", err } return response.Message.Content, nil } // Response 结构体定义了API响应的结构 type Response struct { Model string `json:"model"` CreatedAt string `json:"created_at"` Message Message `json:"message"` DoneReason string `json:"done_reason"` Done bool `json:"done"` TotalDuration int64 `json:"total_duration"` LoadDuration int64 `json:"load_duration"` PromptEvalCnt int `json:"prompt_eval_count"` PromptEvalDur int64 `json:"prompt_eval_duration"` EvalCnt int `json:"eval_count"` EvalDur int64 `json:"eval_duration"` } func init() { engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Brief: "猫娘聊天", Help: "本插件调用本地Ollama运行LLM进行回复\n" + "- 呼叫猫娘+要聊天内容\n" + " 例如：呼叫猫娘Python是什么？\n" + "- 猫娘记忆消除\n" + "- 设置猫娘对话模型+模型名称(仅限主人)\n" + "- 猫娘聊天详细说明", }) engine.OnPrefix("呼叫猫娘").SetBlock(true).Handle(get_answer) engine.OnPrefix("猫娘记忆消除").SetBlock(true).Handle(clean_memory) engine.OnPrefix("设置猫娘对话模型", zero.SuperUserPermission).SetBlock(true).Handle(set_model) engine.OnPrefix("猫娘聊天详细说明").SetBlock(true).Handle(get_information) } func get_information(ctx *zero.Ctx) { ctx.SendChain(message.Text("猫娘聊天插件设置详细说明：\n", "本插件目前设置是调用本地的Ollma运行LLM进行回复，默认是使用的Qwen2.5 3B模型，如果需要其他模型可以通过设置对话模型进行改变。\n", "请在部署完成Ollama后，运行 ollama run qwen2.5:3b 进行Qwen2.5 3B模型部署。\n", "后续可以对接其他模型，例如如果要换成Qwen2.5 32B：\n", "运行 ollama run qwen2.5:32b 进行模型下载初始化，然后给Bot发送命令: 设置猫娘对话模型qwen2.5:32b\n", "详细的内容可以访问我的博客： nekopara.uk", )) } func set_model(ctx *zero.Ctx) { previous_model := model_choose temp_choose := ctx.State["args"].(string) if temp_choose == "" { ctx.SendChain(message.Text("设置失败：没有填写模型名称\n", "可以发送“/用法 nekochat”查看使用方法！", )) return } model_choose = temp_choose ctx.SendChain(message.Text("已将模型 ", previous_model, " 更换为：", model_choose)) } // 消除模型记忆上下文 func clean_memory(ctx *zero.Ctx) { //question := ctx.State["args"].(string) name := ctx.Event.Sender.NickName context = NewChatContext(fixedMessages) ctx.SendChain(message.Text("回复 ", name, " :\n", "猫猫的记忆已经消除喵～开始新的聊天吧！", )) } // 回答问题或聊天对话 func get_answer(ctx *zero.Ctx) { question := ctx.State["args"].(string) name := ctx.Event.Sender.NickName if question == "" { ctx.SendChain(message.Text("回复 ", name, " :\n", "可以发送“/用法 nekochat”查看使用方法！", )) return } responseContent, err := context.SendRequestAndExtractResponse(question) if err != nil { ctx.SendChain(message.Text("回复 ", name, " :\n", "获取回答出错了喵！", err, )) return } if responseContent == "" { ctx.SendChain(message.Text("回复 ", name, " :\n", "获取回答出错了喵！", )) return } ctx.SendChain(message.Text("回复 ", name, " 喵:\n", "", responseContent, )) } 记得要放到中优先级里面。编译好像是需要Go的1.20版本，新版有一些插件不兼容。这里我把Go放到了项目外面一层的文件夹，运行编译命令：../go/bin/go build -ldflags="-s -w" -o ZeroBot-Plugin-1.7.8-MOD然后就可以执行了：./ZeroBot-Plugin-1.7.8-MOD -c config.jsonconfig.json根据项目的Readme设置即可。
自建Jellyfin私人媒体服务器，告别屌毛网易云！
起因∶网易云无故被封促进我自建媒体库的原因，是因为网易云乱封号，两次把我的号给封了。理由居然是——播放数据异常真就是，想封你，随便编个理由，直接就封。纯脑残！其实之前我就有点想搞媒体服务器了，但是由于我看番都是用种子下载看番，而且番剧看一次也就过去了，我觉得也没用留着的必要。毕竟番剧的一话，1080P 60帧，都要差不多1GB了，看番多的话，文件破TB级那是轻轻松松。这次封号，算是坚定了我的想法。我觉得国内互联网公司绝大部分都是狗屎，让人无语。其实是2024年10月封的号，但是我2025年1月才开始折腾媒体服务器。因为之前都在学校，课业繁忙，乐的要死，因为整理音乐库费时费力，所以我打算放假再搞。事实证明我的想法是对的，我前前后后整理了快一个星期，整理了绝大部分，但是还有相当一部分歌曲还没整理。太累了，让我歇歇。目前已经整理了1300+首歌，文件夹已经90G+了。。。为什么选择了Jellyfin？免费，开源。开源，开源，开源，还是他妈的开源！我是支持GNU自由软件运动的人，自然更倾向于使用开源的解决方案，尤其是使用GPL协议的开源软件。顺带一提，我主力操作系统早就已经换成GNU/Linux了。部署Jellyfin下载Jellyfin直接前往官方网页下载即可，我用的最新版，目前是10.10.3。下载链接：repo.jellyfin.org/?path=/server/linux/latest-stable下载FFmpeg由于我服务器使用的是AlmaLinux8.10，是服务器发行版，其自带的FFmpeg比较老，新版的Jellyfin并不兼容。虽然可以通过yum包管理器直接安装对应版本的Jellyfin，并且不会出现兼容问题。但是旧版的Jellyfin并没有歌词支持，对我来说比较蛋疼，因为我主要想做的就是歌曲的媒体服务器。我下载的是7.0.2版本的，这种的话，我更倾向于使用最新版的前一个稳定版。下载地址：ffmpeg.org/releases/ffmpeg-7.0.2.tar.xz自定义环境变量，以使用自定义的ffmpeg我的做法是，把FFmpeg和Jellyfin解压后，分别放到了不同的文件夹，然后在根目录文件夹放了一个启动脚本。以下是启动脚本的内容：#!/bin/bash # 定义FFmpeg二进制文件所在的路径 FFMPEG_PATH="./ffmpeg/bin" # 将FFmpeg路径添加到PATH环境变量的最前面 export PATH="$FFMPEG_PATH:$PATH" # 启动Jellyfin ./jellyfin/jellyfin脚本保存为run.sh，然后再：chmod +x ./run.sh试一下执行这个脚本，看看Jellyfin有没有启动成功，如果启动成功后就完事了。配置Jellyfin初始化配置打开浏览器，输入服务器对应的ip+8096端口，例如：127.0.0.1:8096按照指引，设置用户名和密码。这部过于简单，有手就行，不多赘述。配置HTTPS访问这个Jellyfin比较特别，需要使用pfx证书，而不是常见的pem+key的证书组合。我们需要把pem和key证书转成pfx格式。下面是一个shell脚本，可以便利当前目录的pem+key证书，并且转换为pfx证书放到当前目录的pfx_cert/文件夹内：#!/bin/bash # 定义输出目录 OUTPUT_DIR="pfx_cert" # 创建输出目录（如果它不存在） mkdir -p "$OUTPUT_DIR" # 遍历所有的 .key 文件 for key in *.key; do # 获取不带扩展名的文件名 base_name="${key%.key}" # 检查对应的 .pem 文件是否存在 if [[ -f "${base_name}.pem" ]]; then echo "正在处理 $base_name..." # 转换为 PFX 格式 openssl pkcs12 -export \ -out "${OUTPUT_DIR}/${base_name}.pfx" \ -inkey "$key" \ -in "${base_name}.pem" \ -certfile le-chain.pem echo "已生成 ${OUTPUT_DIR}/${base_name}.pfx" else echo "警告：找不到与 $key 对应的 .pem 文件" fi done把以上内容保存到to_pfx.sh，然后：chmod +x ./to_pfx.sh ./to_pfx.sh当要求输入密码的时候，直接回车，不设置密码。在Jellyfin设置证书，选择得到的pfx证书文件。记得勾上强制HTTPS和允许远程连接保存后，重启Jellyfin，应该就可以通过HTTPS访问了。别忘了添加域名解析记录。至此，基本上配置完成。添加媒体注意事项如果是音乐的话，最好的目录分类方式应该是：歌手 > 专辑 > 音乐文件/歌词文件例如，如果是Taylor Swift的《Red》专辑，目录结构就长这样：/path/to/music_library/ │ ├── Taylor Swift │ └── Red (2012) │ ├── 01. State of Grace.mp3 │ ├── 01. State of Grace.lrc │ ├── 02. Red.mp3 │ ├── 02. Red.lrc │ ├── 03. Treacherous.mp3 │ ├── 03. Treacherous.lrc │ ├── 04. I Knew You Were Trouble.mp3 │ ├── 04. I Knew You Were Trouble.lrc │ ├── 05. All Too Well.mp3 │ ├── 05. All Too Well.lrc │ ├── 06. 22.mp3 │ ├── 06. 22.lrc │ ├── 07. I Almost Do.mp3 │ ├── 07. I Almost Do.lrc │ ├── 08. We Are Never Ever Getting Back Together.mp3 │ ├── 08. We Are Never Ever Getting Back Together.lrc │ ├── 09. Stay Stay Stay.mp3 │ ├── 09. Stay Stay Stay.lrc │ ├── 10. The Last Time.mp3 │ ├── 10. The Last Time.lrc │ ├── 11. Holy Ground.mp3 │ ├── 11. Holy Ground.lrc │ ├── 12. Sad Beautiful Tragic.mp3 │ ├── 12. Sad Beautiful Tragic.lrc │ ├── 13. Begin Again.mp3 │ ├── 13. Begin Again.lrc │ ├── 14. Girl At Home.mp3 │ ├── 14. Girl At Home.lrc │ └── cover.jpg # 封面图片整理好后，在Jellyfin添加媒体库，选择音乐，在添加上对应的文件夹路径，就可以了。如果是按照这个方法添加的话，Jellyfin是可以自动检索元数据并且添加的，可以改善音乐库的观感：歌词的效果：效果还是可以的！音乐库去哪找？我是用了网易云无损解析这类工具，把歌曲一首一首下回来，自己整理，工作量巨大！好处是，有一些工具可以一并把歌词下来，比较舒服。这种工具估计网上一搜，都一大把，很多都是网站在线解析的，并不需要下载软件。什么？你不会找？找不到？是不是搜索引擎是垃圾百度啊？还是360搜索？如果还在用国内这种垃圾搜索引擎，还不会换，那我只能说你是赛博文盲！当然，你也可以去某宝，花几块钱去购买并下载资源，但是大概率给你个百度网盘链接（改名百度软盘得了），然后下载龟速。2333我的收集方法我是用了这个:api.toubiec.cn/wyapi.html 我是一次下一个专辑，由于下载的每首歌都是一个zip压缩包，本来下载就费时费力，整理更加费时费力。于是，我就整了一点小工具做一下辅助：open_zip.sh放在下载目录，负责遍历解压压缩包，文件内容如下：#!/bin/bash # 检查是否有zip文件存在 if [ "$(ls -A *.zip 2> /dev/null)" ]; then # 遍历所有.zip文件 for zip_file in *.zip; do # 获取不带扩展名的文件名 base_name="${zip_file%.zip}" # 创建以文件名为名的新文件夹 mkdir -p "$base_name" # 解压到新建的文件夹内 unzip -q "$zip_file" -d "$base_name" # 检查解压是否成功 if [ $? -eq 0 ]; then echo "Extracted $zip_file into folder $base_name" # 如果解压成功，删除zip文件 rm "$zip_file" echo "Deleted $zip_file" else echo "Failed to extract $zip_file" fi done else echo "No zip files found." fiout_folder.sh放在下载目录，负责遍历移动子文件夹里面的内容到工作目录，文件内容如下：#!/bin/bash # 使用 find 命令查找所有文件并将它们移动到当前目录。 # -mindepth 2 确保我们只获取子文件夹中的文件，而不是当前目录中的文件。 # -maxdepth 2 确保我们不会深入到更深层次的子文件夹中。 # -type f 确保我们只处理文件，而不处理文件夹。 find . -mindepth 2 -maxdepth 2 -type f -exec mv {} . \; # 查找并删除空的子文件夹 # -empty 匹配空文件或空文件夹 # -type d 确保我们只处理文件夹 # -delete 删除匹配到的文件夹 find . -mindepth 1 -maxdepth 1 -type d -empty -delete echo "文件移动完成，并且空文件夹已被删除。"rename_songs.py负责重命名和整理歌曲相关文件，依据截取到的html进行排序整理。需要把网易云音乐网页版的专辑曲目部分的的html代码保存到同目录的content.txt文件中，执行后会自动解析保存的html代码并依据此进行重命名和排序。文件内容如下：import os from bs4 import BeautifulSoup import re import unicodedata def sanitize_title(title): """清理标题以进行精确匹配，保留非ASCII字符""" # 只移除ASCII范围内的非字母数字字符，并转换为小写 return ''.join([c for c in unicodedata.normalize('NFKC', title) if c.isalnum() or not (0 <= ord(c) < 128)]).lower() def clean_title(title): """清理并标准化歌曲标题""" # 将HTML实体替换为空格 title = re.sub(r' ', ' ', title) # 移除多余的空白字符，但保留单词间的单个空格 title = re.sub(r'\s+', ' ', title).strip() # 去除首尾可能出现的特殊字符或符号 title = re.sub(r'^[^\w\s]+|[^\w\s]+$', '', title) # 统一下划线和空格 title = re.sub(r'_', ' ', title) return title def parse_html(html_content): soup = BeautifulSoup(html_content, 'html.parser') # 查找所有的表格行，仅限于 中的行 rows = soup.select('tbody tr') songs = [] for row in rows: # 获取编号 num_tag = row.find('span', class_='num') number = num_tag.get_text(strip=True) if num_tag else 'N/A' # 尝试获取歌曲标题，考虑了可能存在的多种情况 title_tag = row.select_one('td:nth-of-type(2) b, td:nth-of-type(2) a') if title_tag: # 清理标题文本，移除干扰元素 for unwanted in title_tag.find_all('div', class_='soil'): unwanted.extract() # 移除不需要的 标签 title = clean_title(title_tag.get('title') or title_tag.get_text(strip=True)) sanitized_title = sanitize_title(title) else: title = 'Title Not Found' sanitized_title = sanitize_title(title) # 只有当number不是'N/A'且title不是'Title Not Found'时加入列表 if number != 'N/A' and title != 'Title Not Found': songs.append((number, title, sanitized_title)) print("Parsed Songs:") for song in songs: print(f"Number: {song[0]}, Title: {song[1]}, Sanitized Title: {song[2]}") return songs def standardize_filename(filename): """标准化文件名，将下划线替换为空格，并清理多余空格""" base_name, ext = os.path.splitext(filename) standardized_base_name = re.sub(r'_', ' ', base_name) standardized_base_name = re.sub(r'\s+', ' ', standardized_base_name).strip() return f"{standardized_base_name}{ext}" def rename_files_in_directory(directory_path, songs): # 遍历目录中的所有文件 renamed_files = set() # 用于跟踪已经重命名的文件，避免重复重命名 for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) # 如果是文件并且文件名（不包括扩展名）与解析出的歌曲名称匹配，则重命名 if os.path.isfile(file_path) and not filename.endswith('.txt') and not filename.endswith('.py'): standardized_filename = standardize_filename(filename) base_name, ext = os.path.splitext(standardized_filename) matched = False for i, (number, title, sanitized_title) in enumerate(songs, start=1): sanitized_base_name = sanitize_title(base_name) # 使用清理后的字符串进行精确匹配 if sanitized_base_name == sanitized_title: # 格式化编号为两位数 formatted_number = f"{i:02d}" new_filename = f"{formatted_number}. {base_name}{ext}" # 使用标准化后的文件名作为歌曲名 new_file_path = os.path.join(directory_path, new_filename) if not os.path.exists(new_file_path): # 检查新文件名是否已存在 if filename not in renamed_files: print(f'Renaming "{filename}" to "{new_filename}"...') try: os.rename(file_path, new_file_path) renamed_files.add(filename) # 添加到已重命名集合中 matched = True except Exception as e: print(f"Error renaming '{filename}': {e}") else: print(f"New filename '{new_filename}' already exists.") break # 文件名匹配成功后跳出循环，避免重复重命名 if not matched: print(f"No match found for file '{filename}'.") def handle_jpg_files(directory_path): """处理目录中的所有 .jpg 文件，保留一个并重命名为 cover.jpg，删除其余的""" jpg_files = [f for f in os.listdir(directory_path) if f.endswith('.jpg')] if len(jpg_files) > 0: # 选择第一个找到的 .jpg 文件作为封面 cover_file = jpg_files[0] cover_path = os.path.join(directory_path, cover_file) new_cover_path = os.path.join(directory_path, 'cover.jpg') # 如果cover.jpg不存在，则重命名第一个找到的.jpg文件为cover.jpg if not os.path.exists(new_cover_path): os.rename(cover_path, new_cover_path) print(f'Renamed "{cover_file}" to "cover.jpg".') # 删除其他的 .jpg 文件 for jpg in jpg_files[1:]: jpg_path = os.path.join(directory_path, jpg) os.remove(jpg_path) print(f'Deleted "{jpg}".') else: print("No .jpg files found to process.") if __name__ == "__main__": # 获取当前脚本所在的目录 directory_path = os.path.dirname(os.path.abspath(__file__)) # 处理 .jpg 文件 handle_jpg_files(directory_path) # 读取同目录下的 content.txt 文件获取 HTML 内容 content_file_path = os.path.join(directory_path, 'content.txt') try: with open(content_file_path, 'r', encoding='utf-8') as file: html_content = file.read() except FileNotFoundError: print("Error: The file 'content.txt' was not found in the current directory.") exit(1) except Exception as e: print(f"An error occurred while reading 'content.txt': {e}") exit(1) songs = parse_html(html_content) rename_files_in_directory(directory_path, songs) print("All processes completed.")至于怎么获取专辑曲目部分的的html代码，直接在浏览器右键歌曲列表，检查代码，一层一层往上找，当显示是整个列表的内容的元素，那就找到了。然后，右键，复制内部html，这就是需要的东西了。把这些html代码粘贴到content.txt就行了。注意，上述的四个文件：open_zip.sh，out_folder.sh，rename_songs.py，content.txt都是放在专辑的下载目录下。以上准备工作做完之后，在当前目录打开命令行，就可以施展组合拳了：./open_zip.sh ./out_folder.sh python rename_songs.py什么？Python提示ModuleNotFoundError: No module named 'xxx'?缺模块就去装啊，自己去搜，看了资料还不会装的话，只能说你唐！唐完了！！！如果一切顺利，目录就会变成上面示例目录结构专辑文件夹里面的样子了。小福利：Taylor Swift 的专辑为了更直观的看到是怎么组织文件目录的，我就把我整理的Taylor Swift的几个专辑分享出来吧！分别是：《Red》《1989》《Fearless》《Lover》《Speak Now》《Taylor Swift》以下是压缩分卷，解压密码是GTX1080Ti：Taylor Swift.part1.rar Taylor Swift.part5.rar Taylor Swift.part4.rar Taylor Swift.part3.rar Taylor Swift.part2.rar 别告诉我你不会解压缩，不然我只会骂你唐货！
Filebroswer的优点为什么选择Filebroswer呢？原因不外乎就是以下几点∶支持https实时同步文件目录更改，更新及时不需要数据库界面美观友好管理方便支持多用户支持分享文件可以定制界面而最后这一点，也为后面进行美化奠定了基础。前置准备为了保证操作的环境相同，如果你是按照这篇教程来，记得选择File Browser 2.28.0。FileBrowser的基本配置，可以参考我之前的文章：个性化Filebrowser界面，NAS也要Kawaii~~~!个性化Filebrowser你可以快速把我预设的文件下载后直接解压到Filebrowser所在文件夹，修改替换背景图，完成个性化。文件目录结构长这样：├── filebrowser ├── filebrowser.db └── style ├── custom.css ├── favicon.ico ├── img │ ├── background.jpg │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ └── safari-pinned-tab.svg │ ├── login.jpg │ ├── logo.svg │ └── lolicon.ttf └── manifest.json 4 directories, 21 files其中，最重要的配置文件就是custom.css了。这个文件决定了全局的外观。style文件夹我已经打包好了：style.7z下载回去，解压到程序的目录按照下图设置即可看到效果：下面是设置完成后的效果，我设置的是粉色主题的，稍微搭配一点蓝色：登陆页面：主界面：多选文件：复制文件对话框：移动文件对话框：删除文件对话框：切换不同的显示方式：下载文件对话框：上传文件对话框：文件信息对话框：搜索界面：设置界面：上传文件界面：CSS文件内容和解释custom.css文件内容这个文件定义了几乎全部的样式，文件内容如下： /** 页面字体 */ @font-face { font-family: 'cute'; src: local('cute'), local('cute'), url(/static/img/lolicon.ttf) format('truetype'); } * { font-family: "cute", sans-serif; } /** 加载动画 */ .spinner > div { background-color: #ff5f77; backdrop-filter: blur(6px); background-color: orangered\0; /* IE 样式 */ } /** 普通文字、字体图标颜色 */ .action, #listing h2, #search .boxes h3, .card h3, .collapsible > label *, .dashboard p label, .message { color: #ff5f77; } /** 链接文字颜色 */ a { color: #ff5f77; backdrop-filter: blur(6px); } /** 输入框、表格标题文字颜色 */ #search.active i, #search.active input, table th { backdrop-filter: blur(6px); color: #ff5f77; } /** 主体、弹窗颜色 */ #loading, #login, #search.active #input, body, header, .ace-chrome .ace_gutter-active-line { background-image: url(/static/img/background.jpg); background-repeat: no-repeat; background-attachment: fixed; background-position: center center; background-attachment: fixed; background-size: cover; color: #ff5f77 ; } /** 文件夹颜色 */ .file-icons [data-dir="true"] i { color: #ff5f77; } /** 搜索界面图标颜色 */ #search .boxes > div > div { background: #ff5f77; color: rgb(255, 255, 255); text-align: center; width: 10em; padding: 1em; cursor: pointer; margin-bottom: 1em; margin-right: 1em; -moz-box-flex: 1; flex-grow: 1; } /** 多选状态栏颜色 */ #listing #multiple-selection { position: fixed; bottom: -4em; left: 0px; z-index: 99999; width: 100%; background-color: #ff5f77a1; height: 4em; padding: 0.5em 0.5em 0.5em 1em; -moz-box-pack: justify; justify-content: space-between; transition: bottom 0.2s; } /** 多选项目颜色 */ #listing .item[aria-selected="true"] { background: #66ccffc4 !important; color: var(--item-selected) !important; } /* 设置容量显示条颜色 */ .vue-simple-progress { background: #66ccff !important; } /* 设置容量显示条颜色 */ .vue-simple-progress-bar { background: #ff6781 !important; } /* 设置选项卡颜色 */ .dashboard #nav ul li.active { border-color: #CD1076; color: #CD1076; } /** 定义卡片 */ .card{ border-radius: 2em; border: 1px solid #ff6781; background-color:transparent; backdrop-filter: blur(6px); color: #CD1076 ; } /** 定义卡片按钮 */ .button { outline: 0px; padding: 0.5em 1em; border-radius: 0.1em; cursor: pointer; background:#ff5f77; color: rgb(255, 255, 255); border: 1px solid rgba(0, 0, 0, 0.05); box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 5px; transition: 0.1s; } /** 定义卡片按钮 */ .button--flat { color: #e93654; background: transparent; -webkit-box-shadow: 0 0 0; box-shadow: 0 0; border: 0; text-transform: uppercase; } /** 定义容量显示下面的小字颜色 */ .credits { font-size: 0.6em; margin: 3em 2.5em; color: #e56c81; } /** 取消变暗覆盖 */ .overlay { background-color: rgba(0, 0, 0, 0); position: fixed; top: 0px; left: 0px; height: 100%; width: 100%; z-index: 9999; visibility: hidden; opacity: 0; animation: 0.1s forwards show; } /** 定义搜索区 */ #search .boxes{ background-color:transparent; backdrop-filter: blur(6px); color: #CD1076 ; } /** 定义搜索框 */ #search input { border-radius: 2em; padding: .5em 1em; background: #fff5f6b8; border: 2px solid #ffd5dc; -webkit-transition: .2s ease all; transition: .2s ease all; color: #ff5474; margin: 3; } /** 搜索页面、列表标题样式 */ #search #input, #search #result, .input, #listing.list .item.header, .ace-chrome .ace_gutter, .share__box__download, .file-list li:hover { background-color:transparent; backdrop-filter: blur(6px); color: #CD1076 ; } /** 列表项目、文本编辑、命令行、文件分享样式 */ #listing .item, .ace-chrome, .shell, .share__box { background-color:transparent; backdrop-filter: blur(6px); color: #CD1076; } /** 加载页面、登录页面信息提示 */ #loading::after, #login::after { position: absolute; bottom: 6px; width: 100%; display: block; font-size: 22px; text-align: center; content: ''; color: #FF1493; /* IE 样式 */ bottom: 30px\0; font-size: 36px\0; color: tomato\0; } /** 登录页面背景图 */ #login { background-image: url(/static/img/login.jpg); } /** 登录页面LOGO大小 */ #login img { width: 8em; height: 8em; } /** 登录页面按钮定义 */ #login .button { outline: 0; border: 0; padding: .5em 1em; border-radius: 2em; cursor: pointer; background: #ff6781; color: #fff; border: 2px solid #ffd5dc; -webkit-box-shadow: 0 0 5px rgba(0,0,0,.05); box-shadow: 0 0 5px rgba(0,0,0,.05); -webkit-transition: .1s ease all; transition: .1s ease all; } /** 定义输入框 */ .input { border-radius: 2em; padding: .5em 1em; background: #fff5f6b8; border: 2px solid #ffd5dc; -webkit-transition: .2s ease all; transition: .2s ease all; color: #ff5474; margin: 3; } /** 定义登陆界面提示框 */ #login .wrong { border-radius: 2em; background: #ff5474; color: #fff; padding: .5em; text-align: center; -webkit-animation: .2s opac forwards; animation: .2s opac forwards; }文件每个功能区块有注释解释，可以根据需要修改，下面是详细说明。每个部分的详细说明页面字体定义了一个名为 'cute' 的自定义字体，并应用于整个页面。/** 页面字体 */ @font-face { font-family: 'cute'; src: local('cute'), url(/static/img/lolicon.ttf) format('truetype'); } * { font-family: "cute", sans-serif; }加载动画为加载动画中的元素设置了背景颜色和模糊效果。特别地，为了兼容IE浏览器，添加了额外的样式。/** 加载动画 */ .spinner > div { background-color: #ff5f77; backdrop-filter: blur(6px); background-color: orangered\0; /* IE 样式 */ }文字颜色为普通文字、链接文字、输入框及表格标题文字设置了统一的颜色。/** 普通文字、字体图标颜色 */ .action, #listing h2, #search .boxes h3, .card h3, .collapsible > label *, .dashboard p label, .message { color: #ff5f77; } /** 链接文字颜色 */ a { color: #ff5f77; backdrop-filter: blur(6px); } /** 输入框、表格标题文字颜色 */ #search.active i, #search.active input, table th { backdrop-filter: blur(6px); color: #ff5f77; }主体、弹窗颜色为页面主体、弹出窗口等设置了背景图片和文本颜色。/** 主体、弹窗颜色 */ #loading, #login, #search.active #input, body, header, .ace-chrome .ace_gutter-active-line { background-image: url(/static/img/background.jpg); background-repeat: no-repeat; background-attachment: fixed; background-position: center center; background-size: cover; color: #ff5f77; }文件夹颜色、搜索界面图标颜色、多选状态栏颜色、多选项目颜色这些规则为特定元素设置了颜色。/** 文件夹颜色 */ .file-icons [data-dir="true"] i { color: #ff5f77; } /** 搜索界面图标颜色 */ #search .boxes > div > div { background: #ff5f77; color: rgb(255, 255, 255); text-align: center; width: 10em; padding: 1em; cursor: pointer; margin-bottom: 1em; margin-right: 1em; flex-grow: 1; } /** 多选状态栏颜色 */ #listing #multiple-selection { position: fixed; bottom: -4em; left: 0px; z-index: 99999; width: 100%; background-color: rgba(255, 85, 119, 0.63); height: 4em; padding: 0.5em; justify-content: space-between; transition: bottom 0.2s; } /** 多选项目颜色 */ #listing .item[aria-selected="true"] { background: rgba(102, 204, 255, 0.77); color: var(--item-selected) !important; }容量显示条颜色、选项卡颜色、卡片定义、按钮定义、登录界面样式这些规则为UI组件如进度条、选项卡、卡片、按钮以及登录界面定义了样式。/* 设置容量显示条颜色 */ .vue-simple-progress { background: #66ccff !important; } .vue-simple-progress-bar { background: #ff6781 !important; } /* 设置选项卡颜色 */ .dashboard #nav ul li.active { border-color: #CD1076; color: #CD1076; } /** 定义卡片 */ .card{ border-radius: 2em; border: 1px solid #ff6781; background-color:transparent; backdrop-filter: blur(6px); color: #CD1076 ; } /** 定义卡片按钮 */ .button { outline: 0px; padding: 0.5em 1em; border-radius: 0.1em; cursor: pointer; background:#ff5f77; color: rgb(255, 255, 255); border: 1px solid rgba(0, 0, 0, 0.05); box-shadow: rgba(0, 0, 0, 0.05) 0px 0px 5px; transition: 0.1s; } .button--flat { color: #e93654; background: transparent; box-shadow: 0 0; border: 0; text-transform: uppercase; } .credits { font-size: 0.6em; margin: 3em 2.5em; color: #e56c81; } /** 取消变暗覆盖 */ .overlay { background-color: rgba(0, 0, 0, 0); position: fixed; top: 0px; left: 0px; height: 100%; width: 100%; z-index: 9999; visibility: hidden; opacity: 0; animation: 0.1s forwards show; } /** 登录页面样式 */ #login { background-image: url(/static/img/login.jpg); img { width: 8em; height: 8em; } .button { outline: 0; border: 0; padding: .5em 1em; border-radius: 2em; cursor: pointer; background: #ff6781; color: #fff; border: 2px solid #ffd5dc; box-shadow: 0 0 5px rgba(0,0,0,.05); transition: .1s ease all; } .wrong { border-radius: 2em; background: #ff5474; color: #fff; padding: .5em; text-align: center; animation: .2s opac forwards; } }
SELinux是个大坑！说在前面，本次折腾最大的绊脚石就是SELinux这个逼养的玩意，这玩意权限控制是自己一套的，例如，就算你给了Samba读写的目录777的权限，也一样会Permission Denied，如果是新手不知道的话，很容易在配置过程中红温。SELinux我肏你妈！！！但是，实际上SELinux可以给系统一套额外的安全防护，对于注重安全的用户来说还是很有必要的。所有，如果遇到了奇奇怪怪的Permission Denied，不妨试试看关掉SELinux，看看服务是否可以正常运行了。如果可以运行了，就设置SELinux的规则就好了。部署Nginx服务安装Nginxyum install nginx配置Nginx配置文件根据MCSManager的官方文档精神，需要创建一个Nginx配置文件来实现反向代理并加入TLS以实现Https。创建一个配置文件，位于：/etc/nginx/conf.d/，名字可以自己取。例如：nano /etc/nginx/conf.d/mcsm-proxy.conf然后填入以下内容：# MCSM面板端开启HTTPS反向代理 server { # 面板端公网HTTPS端口(可用多个listen监听多个端口) listen 2333 ssl http2; #IPV4 listen [::]:2333 ssl http2; #IPv6 # 开启HSTS 开启后将强制使用HTTPS连接面板并在取消此策略后持续一年除非在浏览器手动清除策略。 # 默认未开启，可取消注释开启. #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; # DNS服务器，仅在目标面板需要使用域名连接时需要。 resolver 8.8.8.8; # 自动重定向HTTP连接至HTTPS error_page 497 https://$host:$server_port$request_uri; proxy_hide_header Upgrade; location / { # 请求头 一般无需更改 proxy_set_header Host $host; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; #目标面板的地址与端口。支持使用域名及https连接。 proxy_pass http://127.0.0.1:23333; # 支持反代 WebSocket proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 最大文件上传大小限制。设置0为不限制 client_max_body_size 0; # 关闭缓存 proxy_request_buffering off; proxy_buffering off; } # HTTPS 证书与私钥位置 ssl_certificate /etc/nginx/ssl/domain.crt; ssl_certificate_key /etc/nginx/ssl/domain.key; # 传输时默认开启gzip压缩 gzip on; # 传输时会被压缩的类型(应当依据文件压缩效果添加) gzip_types text/plain text/css application/javascript application/xml application/json; # 反向代理时，启用压缩 gzip_proxied any; # 传输时压缩等级，等级越高压缩消耗CPU越多，最高9级，通常5级就够了 gzip_comp_level 5; # 传输时大小达到1k才压缩，压缩小内容无意义 gzip_min_length 1k; # 支持协议、算法与超时时间等 一般无需更改 ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; } # MCSM节点端开启HTTPS反向代理 server { # 节点 公网HTTPS端口(可用多个listen监听多个端口) listen 2444 ssl http2; #IPV4 listen [::]:2444 ssl http2; #IPv6 # 开启HSTS 开启后将强制使用HTTPS连接节点并在取消此策略后持续一年除非在浏览器手动清除策略。 # 默认未开启，可取消注释开启. #add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; # DNS服务器，仅在目标节点需要使用域名连接时需要。 resolver 8.8.8.8; # 自动重定向HTTP连接至HTTPS error_page 497 https://$host:$server_port$request_uri; proxy_hide_header Upgrade; location / { # 请求头 一般无需更改 proxy_set_header Host $host; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; #目标节点的地址与端口。支持使用域名及https连接。 proxy_pass http://127.0.0.1:24444; # 支持反代 WebSocket proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 最大文件上传大小限制。设置0为不限制 client_max_body_size 0; # 关闭缓存 proxy_request_buffering off; proxy_buffering off; } # HTTPS 证书与私钥位置 ssl_certificate /etc/nginx/ssl/domain.crt; ssl_certificate_key /etc/nginx/ssl/domain.key; # 传输时默认开启gzip压缩 gzip on; # 传输时会被压缩的类型(应当依据文件压缩效果添加) gzip_types text/plain text/css application/javascript application/xml application/json; # 反向代理时，启用压缩 gzip_proxied any; # 传输时压缩等级，等级越高压缩消耗CPU越多，最高9级，通常5级就够了 gzip_comp_level 5; # 传输时大小达到1k才压缩，压缩小内容无意义 gzip_min_length 1k; # 支持协议、算法与超时时间等 一般无需更改 ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; }记得把/etc/nginx/ssl/domain.crt和/etc/nginx/ssl/domain.key替换成对应的你的证书和私钥路径。按F2，y保存文件。启动Nginxsystemctl start nginx然后轻松翻车：[root@NekoWorks conf.d]# systemctl start nginx Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.查看原因：# journalctl -xe -- Support: https://access.redhat.com/support -- -- The unit nginx.service has successfully entered the 'dead' state. Nov 10 18:20:59 NekoWorks systemd[1]: Stopped The nginx HTTP and reverse proxy server. -- Subject: Unit nginx.service has finished shutting down -- Defined-By: systemd -- Support: https://access.redhat.com/support -- -- Unit nginx.service has finished shutting down. Nov 10 18:20:59 NekoWorks systemd[1]: Starting The nginx HTTP and reverse proxy server... -- Subject: Unit nginx.service has begun start-up -- Defined-By: systemd -- Support: https://access.redhat.com/support -- -- Unit nginx.service has begun starting up. Nov 10 18:20:59 NekoWorks nginx[2300538]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok Nov 10 18:20:59 NekoWorks nginx[2300538]: nginx: [emerg] bind() to 0.0.0.0:2333 failed (13: Permission denied) Nov 10 18:20:59 NekoWorks nginx[2300538]: nginx: configuration file /etc/nginx/nginx.conf test failed Nov 10 18:20:59 NekoWorks systemd[1]: nginx.service: Control process exited, code=exited status=1 Nov 10 18:20:59 NekoWorks systemd[1]: nginx.service: Failed with result 'exit-code'. -- Subject: Unit failed -- Defined-By: systemd -- Support: https://access.redhat.com/support -- -- The unit nginx.service has entered the 'failed' state with result 'exit-code'. Nov 10 18:20:59 NekoWorks systemd[1]: Failed to start The nginx HTTP and reverse proxy server. -- Subject: Unit nginx.service has failed -- Defined-By: systemd -- Support: https://access.redhat.com/support -- -- Unit nginx.service has failed. -- -- The result is failed.关掉SELinux，重新启动服务，一切正常。。。原因很明显，那就是SELinux阻止了Nginx进行端口监听！SELinux出来挨打！设置SELinux允许Nginx监听端口我们来设置允许Nginx进行端口监听：semanage port -a -t http_port_t -p tcp 2333 semanage port -a -t http_port_t -p tcp 2444这下再次启动Nginx，应该是正常启动了。等等，怎么打开网页居然是502错误？！不对啊喂！关掉SELinux，又一切正常，看来还是暗藏玄坤。设置SELinux允许Nginx进行网络连接我们来设置允许HTTP服务进行网络连接。setsebool -P httpd_can_network_connect on执行完后，再次访问管理面板，就一切正常了。