V100 GPU 初体验：AlmaLinux 8.10 快速部署 ComfyUI
在 AlmaLinux 8.10 上部署 ComfyUI最近买了一张 V100，为了爽玩 AI 绘图，我单独组装了一台 X99 平台主机来运行 ComfyUI。操作系统方面，我选择了 AlmaLinux 8.10，看中的是其稳定性以及对多版本 Python 的良好支持。硬件介绍显卡本体Tesla V100 显卡展示（和RTX2080Ti同框）：安装到转接板上：转接板是200买的，虽然比最便宜的99元转接板贵了一倍，但用的是原装座子，原装座子的转接板就算是10X10CM的小板子都170+了。我加多30，买个带温控的，尺寸大一点的板，还自带螺栓孔位固定，完美安装，他不香吗？装上散热器后的样子：我为了尽可能降低温度，延长使用寿命，买了最大的散热器，到手发现确实巨大无比，比想象中的要大得多。但是其实现在看，买小一号的9CM高度的可能更合适，因为更好安装8CM风扇，不会浪费散热面积。我最后还是选择了8CM的风扇来散热，放弃了之前使用12CM风扇的想法——因为没办法彻底吹透散热器，温度还高。装上风扇Be Like:因为两边会漏风，导致无法彻底吹透散热器，所以搞了一张纸包了一下，迫使气流贯穿散热器。配套硬件平台开头我先说一下我踩到的坑，首先平台选择就先栽了一头：一开始我想用我闲置的H61平台来带这个玩意，而且也确认看过那个主板有Above 4G Decoding选项，也打开了，但是就是死活没办法驱动这个显卡，系统识别到了这个设备，但是NVIDIA驱动无法初始化设备，导致无法使用。这个问题折磨了我很久，检查日志具体表现是：[root@Neko-P104 ~]# dmesg | tail -100 | grep -i -E 'nvidia|nvrm|gpu|bar' [ 3.121900] NVRM: This PCI I/O region assigned to your NVIDIA device is invalid: NVRM: BAR4 is 0M @ 0x0 (PCI:0000:01:00.0) [ 3.121902] NVRM: This PCI I/O region assigned to your NVIDIA device is invalid: NVRM: BAR5 is 0M @ 0x0 (PCI:0000:01:00.0) [ 3.322080] NVRM: loading NVIDIA UNIX x86_64 Kernel Module 580.105.08 Tue Apr 8 12:41:17 UTC 2025 [ 3.369648] nvidia-modeset: Loading NVIDIA Kernel Mode Setting Driver for UNIX platforms 580.105.08 Tue Apr 8 12:09:34 UTC 2025 [ 3.376189] [drm] [nvidia-drm] [GPU ID 0x00000100] Loading driver [ 3.376192] [drm] Initialized nvidia-drm 0.0.0 20160202 for 0000:01:00.0 on minor 0 [ 112.149308] caller _nv041720rm+0x3a/0xb0 [nvidia] mapping multiple BARs [ 112.163383] NVRM: GPU 0000:01:00.0: RmInitAdapter failed! (0x24:0x72:1568) [ 112.163415] NVRM: GPU 0000:01:00.0: rm_init_adapter failed, device minor number 0 [ 112.285687] nvidia-uvm: Loaded the UVM driver, major device number 237. [ 379.801748] caller _nv041720rm+0x3a/0xb0 [nvidia] mapping multiple BARs [ 379.815873] NVRM: GPU 0000:01:00.0: RmInitAdapter failed! (0x24:0x72:1568) [ 379.815912] NVRM: GPU 0000:01:00.0: rm_init_adapter failed, device minor number 0这表明：BAR 分配失败：GPU 的某些地址空间没有正确映射到系统内存中。RmInitAdapter 失败：驱动尝试初始化 GPU 时遇到了错误，无法完成初始化。所以这是平台限制，必须换平台了。后面我在我主力机（X99双路服务器）上面强行安装上显卡，结果成功驱动了。作死照片：于是，我就从我家NAS拆了这套X99，然后NAS换回H61，因为NAS用X99本来就性能过剩。之前手痒痒给家里云升级了X99，现在又被拆走，回归最开始的H61，这算是回归本质了（）以下是配置：主板：山寨X99主板，H81芯片组魔改。CPU：E5-2630 v4内存：SK REG ECC DDR4 2133p 2Rx4 16G 两条硬盘：500G机械硬盘GPU：Tesla V100装好起来就长这样了：如果你仔细观察的话，你会发现带显卡的是独立的服务器电源，也就是这个电脑用了两个电源。问就是靠继电器触发服务器电源给显卡提供额外的12V供电，继电器的电源是主电源的12V供电，这样就可以同步上电。这样就可以不用花这么多钱买大电源了，而且服务器拆机电源用料还好，垃圾佬基本操作（）搁这装炸弹呢（bushi硬件介绍完了，接下来就开始折腾吧！驱动安装因为默认安装的AlmaLinux 8.10并没有自带NVIDIA驱动，所以，我我们需要手动安装驱动。具体的操作我博客之前的文章有介绍过，可以搜索看一下，这里我就简要过一遍：yum install epel-release dnf config-manager --add-repo http://developer.download.nvidia.com/compute/cuda/repos/rhel8/$(uname -i)/cuda-rhel8.repo dnf install kernel-headers-$(uname -r) kernel-devel-$(uname -r) tar bzip2 make automake gcc gcc-c++ pciutils elfutils-libelf-devel libglvnd-opengl libglvnd-glx libglvnd-devel acpid pkgconfig dkms dnf module install nvidia-driver:580-dkms安装完成后，重启，执行nvidia-smi能看到显卡信息，就说明成功了：[rin@Neko-P104 ~]$ nvidia-smi Fri Dec 5 20:50:54 2025 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.105.08 Driver Version: 580.105.08 CUDA Version: 13.0 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 Tesla V100-SXM2-16GB Off | 00000000:03:00.0 Off | 0 | | N/A 45C P0 153W / 300W | 2733MiB / 16384MiB | 99% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | 0 N/A N/A 319443 C ...2a49dd2e4e7a546c540f/rend.exe 2730MiB | +-----------------------------------------------------------------------------------------+部署 ComfyUI截至我写这篇文章的时候，ComfyUI的版本为0.3.76，本文写的教材都是在这个版本下进行操作的，进行参考时请注意。由于0.3.76版本的ComfyUI推荐Python版本是3.12，我们需要安装Python3.12。得益于AlmaLinux8.10支持多版本Python共存安装，事情变得很简单，不需要Anaconda这种东西。而且这个机器因为是单一的服务器用途，大概率相关的Python项目只会部署一个ComfyUI，所以直接在系统的Python环境部署即可。安装Python3.12：yum install python3.12 python3.12-pip下载并解压ComfyUI：wget https://github.com/comfyanonymous/ComfyUI/archive/refs/tags/v0.3.76.zip unzip v0.3.76.zip rm v0.3.76.zippip换源：cd ~ mkdir .pip nano .pip/pip.conf将以下内容写入配置文件：[global] index-url = https://pypi.mirrors.ustc.edu.cn/simple/ trusted-host = pypi.mirrors.ustc.edu.cn安装依赖环境：cd ComfyUI-0.3.76/ pip3.12 install -r requirements.txt如果你之前用过Stable Diffusion WebUI的话，可以照着extra_model_paths.yaml.example创建一个extra_model_paths.yaml，导入之前的模型。取消注释对应的部分，填上路径就可以了：a111: base_path: /home/rin/stable-diffusion-webui/ checkpoints: models/Stable-diffusion configs: models/Stable-diffusion vae: models/VAE loras: | models/Lora models/LyCORIS upscale_models: | models/ESRGAN models/RealESRGAN models/SwinIR embeddings: embeddings hypernetworks: models/hypernetworks controlnet: models/ControlNet一切设置妥当后，就可以ComfyUI启动了：python3.12 main.py --listen 0.0.0.0不出意外的话，访问服务器的IP地址+8188端口（例如192.168.6.6:8188）就可以看到界面了。尝试跑几张图，没问题就部署完成了。缺失模型下载技巧如果你的服务器位于国内，因为众所周知的原因，是没办法根据它给出的地址直接用wget拉取的，但是我发现了一个小技巧，不需要给服务器魔法上网环境，也可以高速拉取模型。例如，使用这个模板的时候缺失了模型：点击复制连接，然后找一台国外的VPS使用curl来获取下载连接：NyankoHost [~]# curl https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.0.safetensors Found. Redirecting to https://cas-bridge.xethub.hf.co/xet-bridge-us/689761ce345c5cafa2ebc6a6/dbc67890a57bd825922462d911e0d32c7ee7706ee7985f8ddf105b615a1e8e32?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=cas%2F20251205%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251205T131704Z&X-Amz-Expires=3600&X-Amz-Signature=84def4e6cfc1dcd8ba5d646142f7d19680a030829568a0f67b4a887d35ecdb4b&X-Amz-SignedHeaders=host&X-Xet-Cas-Uid=public&response-content-disposition=inline%3B+filename*%3DUTF-8%27%27Qwen-Image-Lightning-8steps-V1.0.safetensors%3B+filename%3D%22Qwen-Image-Lightning-8steps-V1.0.safetensors%22%3B&x-id=GetObject&Expires=1764944224&Policy=[binary]&Signature=coo9prmThYpuZYwNqFpZafSP8LHUXhb4DYd6PZbIn4EnSgC%7EDQP6gE3EneVg4I-1-QJcoGBj1lihIWOn8qkrtH5xJfekUCYSSzA1m8jQl89MmrxUxwEZEfYBR3ZturhNeXY5FOdqiYElE98gqr7LHocp0ue5EJJUCbYmlwlJGr4HnwqpM7VkvN1MAATGsIwUbztXOvsvPD6WyjxFGPfeHUPidQVWfCkZiMm-yDHFWRyMT3TYeh6YH8f0ZsVPO2TMvBUFcKRjaGoyKUOHA0EuCz0yKH%7EfrLzkqAVsc2V9MpWEVE5gcCseZufIFFLPDg7garAB%7E2wJddHK4WPw5H-K9g__&Key-Pair-Id=K2L8F4GPSG1IFC NyankoHost [~]# 获取到了下载连接后，我们在服务器上进入对应的目录位置进行拉取：cd ComfyUI-0.3.76/models/loras/ wget -O Qwen-Image-Lightning-8steps-V1.0.safetensors 'https://cas-bridge.xethub.hf.co/xet-bridge-us/689761ce345c5cafa2ebc6a6/dbc67890a57bd825922462d911e0d32c7ee7706ee7985f8ddf105b615a1e8e32?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=cas%2F20251205%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251205T131704Z&X-Amz-Expires=3600&X-Amz-Signature=84def4e6cfc1dcd8ba5d646142f7d19680a030829568a0f67b4a887d35ecdb4b&X-Amz-SignedHeaders=host&X-Xet-Cas-Uid=public&response-content-disposition=inline%3B+filename*%3DUTF-8%27%27Qwen-Image-Lightning-8steps-V1.0.safetensors%3B+filename%3D%22Qwen-Image-Lightning-8steps-V1.0.safetensors%22%3B&x-id=GetObject&Expires=1764944224&Policy=[binary]&Signature=coo9prmThYpuZYwNqFpZafSP8LHUXhb4DYd6PZbIn4EnSgC%7EDQP6gE3EneVg4I-1-QJcoGBj1lihIWOn8qkrtH5xJfekUCYSSzA1m8jQl89MmrxUxwEZEfYBR3ZturhNeXY5FOdqiYElE98gqr7LHocp0ue5EJJUCbYmlwlJGr4HnwqpM7VkvN1MAATGsIwUbztXOvsvPD6WyjxFGPfeHUPidQVWfCkZiMm-yDHFWRyMT3TYeh6YH8f0ZsVPO2TMvBUFcKRjaGoyKUOHA0EuCz0yKH%7EfrLzkqAVsc2V9MpWEVE5gcCseZufIFFLPDg7garAB%7E2wJddHK4WPw5H-K9g__&Key-Pair-Id=K2L8F4GPSG1IFC'然后你就能获得起飞一般的下载速度：[rin@Neko-P104 loras]$ wget -O Qwen-Image-Lightning-8steps-V1.0.safetensors 'https://cas-bridge.xethub.hf.co/xet-bridge-us/689761ce345c5cafa2ebc6a6/dbc67890a57bd825922462d911e0d32c7ee7706ee7985f8ddf105b615a1e8e32?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=cas%2F20251205%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251205T131704Z&X-Amz-Expires=3600&X-Amz-Signature=84def4e6cfc1dcd8ba5d646142f7d19680a030829568a0f67b4a887d35ecdb4b&X-Amz-SignedHeaders=host&X-Xet-Cas-Uid=public&response-content-disposition=inline%3B+filename*%3DUTF-8%27%27Qwen-Image-Lightning-8steps-V1.0.safetensors%3B+filename%3D%22Qwen-Image-Lightning-8steps-V1.0.safetensors%22%3B&x-id=GetObject&Expires=1764944224&Policy=[binary]&Signature=coo9prmThYpuZYwNqFpZafSP8LHUXhb4DYd6PZbIn4EnSgC%7EDQP6gE3EneVg4I-1-QJcoGBj1lihIWOn8qkrtH5xJfekUCYSSzA1m8jQl89MmrxUxwEZEfYBR3ZturhNeXY5FOdqiYElE98gqr7LHocp0ue5EJJUCbYmlwlJGr4HnwqpM7VkvN1MAATGsIwUbztXOvsvPD6WyjxFGPfeHUPidQVWfCkZiMm-yDHFWRyMT3TYeh6YH8f0ZsVPO2TMvBUFcKRjaGoyKUOHA0EuCz0yKH%7EfrLzkqAVsc2V9MpWEVE5gcCseZufIFFLPDg7garAB%7E2wJddHK4WPw5H-K9g__&Key-Pair-Id=K2L8F4GPSG1IFC' --2025-12-05 21:21:03-- https://cas-bridge.xethub.hf.co/xet-bridge-us/689761ce345c5cafa2ebc6a6/dbc67890a57bd825922462d911e0d32c7ee7706ee7985f8ddf105b615a1e8e32?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=cas%2F20251205%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251205T131704Z&X-Amz-Expires=3600&X-Amz-Signature=84def4e6cfc1dcd8ba5d646142f7d19680a030829568a0f67b4a887d35ecdb4b&X-Amz-SignedHeaders=host&X-Xet-Cas-Uid=public&response-content-disposition=inline%3B+filename*%3DUTF-8%27%27Qwen-Image-Lightning-8steps-V1.0.safetensors%3B+filename%3D%22Qwen-Image-Lightning-8steps-V1.0.safetensors%22%3B&x-id=GetObject&Expires=1764944224&Policy=[binary]&Signature=coo9prmThYpuZYwNqFpZafSP8LHUXhb4DYd6PZbIn4EnSgC%7EDQP6gE3EneVg4I-1-QJcoGBj1lihIWOn8qkrtH5xJfekUCYSSzA1m8jQl89MmrxUxwEZEfYBR3ZturhNeXY5FOdqiYElE98gqr7LHocp0ue5EJJUCbYmlwlJGr4HnwqpM7VkvN1MAATGsIwUbztXOvsvPD6WyjxFGPfeHUPidQVWfCkZiMm-yDHFWRyMT3TYeh6YH8f0ZsVPO2TMvBUFcKRjaGoyKUOHA0EuCz0yKH%7EfrLzkqAVsc2V9MpWEVE5gcCseZufIFFLPDg7garAB%7E2wJddHK4WPw5H-K9g__&Key-Pair-Id=K2L8F4GPSG1IFC Resolving cas-bridge.xethub.hf.co (cas-bridge.xethub.hf.co)... 13.33.183.18, 13.33.183.37, 13.33.183.46, ... Connecting to cas-bridge.xethub.hf.co (cas-bridge.xethub.hf.co)|13.33.183.18|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1698951104 (1.6G) Saving to: ‘Qwen-Image-Lightning-8steps-V1.0.safetensors’ Qwen-Image-Lightning-8steps-V1.0.safetens 100%[==================================================================================>] 1.58G 28.8MB/s in 1m 3s 2025-12-05 21:23:08 (13.1 MB/s) - ‘Qwen-Image-Lightning-8steps-V1.0.safetensors’ saved [1698951104/1698951104] [rin@Neko-P104 loras]$ 能这么操作的原因是，huggingface.co的域名被阻断了，而真正用于分发下载的cas-bridge.xethub.hf.co域名并没有被阻断，而且因为是亚太的amazon节点，速度还很快。番外：V100 的 SheepIt 战力我用顺带跑了一下SheepIt渲染农场的渲染速度，测试了几次，基本上就是大概116%的算力，还是可以的。
二次开发萌化VisitorLoggerPro插件其实之前我一直在用VisitorLoggerPro这个插件，但是由于之前的版本不够完善，也没想着进一步提升改造。前几天偶然翻了翻这个项目的GitHub仓库，发现基本上更新完善了，上一次更改已经是三个月之前，遂下了最新版回来尝试一下，发现功能非常满意。但是，美中不足的是，插件从jsdelivr.net下载相关的静态文件容易卡半天加载不出来。虽然插件写有智能回退使用网站的服务器进行加载，但是我发现它缺失了文件且返回路径不对，导致如果jsdelivr.net无法访问，页面会加载异常。于是，就萌生了修复这个插件的问题并且改造进行个性化的想法。打开了后台页面观摩了一下，并用浏览器的开发者工具随手调了一下CSS样式，最终明确了个性化的路径：设置一个全局背景图看板卡片进行半透明化于是说干就干，我先Fork了原项目，想了想要不就取名VisitorLoggerPro-Enhanced吧！然后开始了我的折腾和修改之旅。Github项目地址：VisitorLoggerPro-Enhanced可以去直接下载使用。修复静态文件回退到本地失败这肯定是最先需要解决的问题。如果不解决这个问题，该插件在我这里就处于不可用状态。补回缺失的本地文件这一步对于有网站搭建经验的人很简单，F12打开浏览器开发者工具，然后刷新页面观察网页都发送了那些外链请求，一下子就抓出来了：本地缺失的js：https://cdn.jsdelivr.net/npm/flatpickr本地缺失的css：https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css其中，flatpickr抓取回来是没有后缀的，得加上个.js后缀变成flatpickr.js。然后对号入座，flatpickr.js放到js/目录，新建一个css/目录放置flatpickr.min.css。补回文件后的插件目录文件树应该是这样的：tree . ├── action │ └── visitor-stats.php ├── Action.php ├── adapter.php ├── css │ └── flatpickr.min.css ├── getTrendData.php ├── getVisitStatistic.php ├── ip2region │ └── src │ ├── ip2region.xdb │ └── XdbSearcher.php ├── ipdata │ └── src │ ├── ipdbv6.func.php │ ├── IpLocation.php │ ├── ipv6wry-country.db │ ├── ipv6wrys.db │ ├── qqwry.dat │ ├── qqwrye.dat │ └── zxipv6wry.db ├── js │ ├── echarts.min.js │ ├── flatpickr.js │ └── README.md ├── panel.php ├── Plugin.php ├── README.md ├── trend.php └── visitor-stats.php 8 directories, 23 files修复完善静态文件获取逻辑原本插件本地静态文件的相对链接写法是：localScript.src = './js/echarts.min.js';这会导致获取文件时发生404错误，因为在插件页面上下文中，该相对路径会被解析为https://www.nekopara.uk/admin/js/echarts.min.js，那包404的。正确的URL连接位置应该是https://www.nekopara.uk/usr/plugins/VisitorLoggerPro/js/echarts.min.js，所以，这个相对链接应该改成../usr/plugins/VisitorLoggerPro/js/echarts.min.js。而且，原插件仅实现了echarts.min.js的本地回退逻辑，未处理另外两个静态文件（flatpickr.js和flatpickr.min.css），因此即使修复了路径，还是会卡加载。还有一个致命的缺陷是，原本的回退本地源的逻辑是等待失败，但是一等就是大几十秒，非常影响体验。于是我就设置了超时时间，一旦第三方的静态文件加载时间超过2秒，立即回退到本地，避免长时间等待。以下是完善后的逻辑代码：对于panel.php内嵌js部分的修改，在原文件64行的地方开始修改： 对于visitor-stats.php内嵌js部分的修改，在原文件90行的地方开始修改： 对于trend.php内嵌js部分的修改，在原文件12行的地方开始修改： 对实现显示图表的trend.php，panel.php和visitor-stats.php的修复主要解决以下核心问题：加载超时问题：原插件等待第三方CDN加载超时（大几十秒），现添加2秒超时机制，一旦CDN加载超时立即回退到本地，极大提升加载体验。加载逻辑完善：原逻辑仅处理echarts.min.js，现完整支持echarts.min.js、flatpickr.js和flatpickr.min.css的智能加载与回源。错误处理增强：添加加载成功/失败日志，以及加载失败时的用户提示，提升用户体验与问题排查效率。以上修复确保在jsdelivr.net不可用时，插件仍能快速加载并正常运行，同时保持原有功能完整性。修复我已经为GitHub上的原项目提交了的Pull Requests，能不能被合并就不知道了（接下来开始搞我的个性化了）萌化VisitorLoggerPro首先，最简单粗暴的萌化方式就是加上二次元背景图，这是最直接，最有效果的方式。我的修改思路是，先在浏览器开发者工具里面直接修改网页代码查看效果，然后再针对性去修改插件里面对应的代码。为插件添加背景图在插件界面，右键点击页面边缘的空白处，选择检查。然后在HTML代码区一路上划找到页面最底层的 盒子，发现是 ，然后针对性去找CSS样式表，真给我找到了： .main { padding: 20px; background-color: #f5f7fa; min-height: 100vh; }这时可以先在浏览器里面修改CSS样式表看看效果：.main { padding: 20px; min-height: 100vh; background-image: url('https://teachermate.oss-cn-qingdao.aliyuncs.com/irGnf-1725727799259-neko4_n33b.png'); background-repeat: no-repeat; background-position: center center; background-attachment: fixed; background-size: cover; }瞬间就对味了一大截：然后就需要修改规则了，在全部文件中检索.main这个CSS类选择器，然后进行代码修改。好了，那么现在问题来了，我虽然是可以直接找到对应的代码写死URL在上面，但是如果别人想换个图片，就没这么方便了。于是我就在思考，能不能把这个参数作为应该可以给用户设置的插件参数？得益于Typecho插件开发的便利性，答案是肯定的，只需要在插件设置界面给出设置插件的参数的地方，在后面通过Helper::options()获取这个参数即可。找Plugin.php添加多一个设置选项：插件背景设置 /* 插件背景设置 */ $backgroundUrl = new Typecho_Widget_Helper_Form_Element_Text( 'backgroundUrl', null, 'https://pic.nekopara.uk/?format=webp', // 默认值 _t('插件背景设置'), _t('可填写图片API的URL（如随机图片API）或具体图片的URL直链。默认使用猫娘乐园图片API') ); $form->addInput($backgroundUrl);我这里定义参数名称为backgroundUrl，然后在插件代码里面进行获取的方式就是：$backgroundUrl = Helper::options()->plugin('VisitorLoggerPro')->backgroundUrl ?: 'https://pic.nekopara.uk/?format=webp';在嵌入到PHP的CSS样式表部分，我只需要这样子修改： .main { padding: 20px; min-height: 100vh; background-image: url('<?php echo $backgroundUrl; ?>'); background-repeat: no-repeat; background-attachment: fixed; background-position: center center; background-size: cover; }就可以根据设置的URL返回特定的样式表了，让用户可以自定义背景图的问题也就迎刃而解了。插件默认的设置图片来源是我的猫娘乐园CG图API，可以自行更换为其他图片直链或者图片API。为卡片设置半透明颜色但是，只是修改一个背景图，卡片是不透明的把好看的图片都基本上档完了，多没意思？于是，我就想在保证可读性的情况下，让卡片半透明化，让背景图能被看到。继续F12大法，找到每个卡片对应的CSS规则：访客日志页面部分，对应panel.php文件： .page-header { background: #fff; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; } .action-form { background: #fff; padding: 20px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; display: flex; flex-direction: column; } .logs-section { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); /* height: calc(100vh - 200px); */ overflow: auto; min-width: 0; border: 1px solid #e2e8f0; } .stats-section { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); /* height: calc(100vh - 200px); */ display: flex; flex-direction: column; gap: 24px; border: 1px solid #e2e8f0; } .chart-container { flex: 1; min-height: 260px; background: linear-gradient(135deg, #fff 0%, #f8fafc 100%); border-radius: 12px; padding: 8px; border: 1px solid #e2e8f0; position: relative; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; overflow: hidden; }访客日志趋势分析部分，对应trend.php文件： .page-header { background: #fff; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; } .controls-section { background: #fff; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; } .trend-section { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; } .stats-summary { display: flex; justify-content: center; gap: 20px; margin-bottom: 20px; padding: 16px; background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0; flex-wrap: wrap; } .metrics-explanation { background: #fff; border-radius: 12px; padding: 24px; margin-top: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; } .metric-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 20px; transition: all 0.3s ease; } .technical-notes { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 20px; border-left: 4px solid #3498db; }只需要将每个卡片的background: #fff;定义颜色部分设置成半透明即可。虽然最简单的方法就是保持原来的颜色，增加透明度，但是我考虑到这一片卡片后面如果是靠修改代码调整，调整起来也很花时间，不如像图片背景那样给用户输入一个颜色自己配。同样的方法，在Plugin.php中增加配置选项： /* 插件卡片颜色设置 */ $backgroundColour = new Typecho_Widget_Helper_Form_Element_Text( 'backgroundColour', null, '#ffffffc4', // 默认值 _t('插件展示内容卡片颜色设置，采用HTML颜色代码') ); $form->addInput($backgroundColour);然后在panel.php和trend.php加上：// 获取配置的卡片背景色（带默认值） $backgroundColour = Helper::options()->plugin('VisitorLoggerPro')->backgroundColour ?: '#ffffffc4';并针对性的设置需要设置的颜色CSS代码位置，例如： .page-header { background: <?php echo $backgroundColour; ?>; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; }找到上面说的CSS代码，将背景颜色代码部分替换为<?php echo $backgroundColour; ?>就完事了。这次的自定义修改，让我对Typecho的插件和PHP代码逻辑有了更进一步的认识，虽然仍是面向LLM编程，但是至少有实现是思路和方法，不会具体的语法和代码，倒是可以让LLM来写。效果展示最后展示一下萌化后的效果：这下暴露出小破站访问量了，果然是没什么人看的，被打的次数倒是不少（番外：使用方法进入你的Typecho部署目录，然后进入插件文件夹：cd /data/typecho/ cd usr/plugins/随后，克隆项目并重命名文件夹：git clone https://github.com/Chocola-X/VisitorLoggerPro-Enhanced mv VisitorLoggerPro-Enhanced/ VisitorLoggerPro/删除无用文件（可选）：cd VisitorLoggerPro/ rm -rf .git/ rm -rf .gitignore rm README.md 然后进入Typecho后台启用插件即可。当然，如果你凑巧服务器在国内无法正常访问GitHub的话，我这里有直链可以给你下载，进入插件目录下载并解压即可：wget https://teachermate.oss-cn-qingdao.aliyuncs.com/air2Q-1762344686455-VisitorLoggerPro.zip unzip https://teachermate.oss-cn-qingdao.aliyuncs.com/air2Q-1762344686455-VisitorLoggerPro.zip rm https://teachermate.oss-cn-qingdao.aliyuncs.com/air2Q-1762344686455-VisitorLoggerPro.zip
在Manjaro Linux上安装Abaqus 2022本来没什么素材更新文章的，这下因为阴差阳错各种巧合，来素材了（ 刚立flag没几天说是其实在目前阶段我并不需要用这个软件，究其原因，是我朋友的电脑内存不够，他的研究生导师的课题组要用Abaqus这个软件进行有限元分析模拟，不出所料炸内存了。他想起来我有这么一个128G内存的“电脑”（服务器），于是想让我帮忙算一下。好巧不巧，这逼软件有Linux版，那就试试吧！然后试试就逝世，这逼养的古董屎山软件的安装方式，以及各种奇葩的要求和报错属实难绷，挂出来让大家品屎罢！踩坑报错把我搞傻了，国内各种资料网站参考了一遍没能解决，压力ChatGPT解决了，GPT yyds! 记录一下吧，给后面有需要的人指路。理论上这篇教程同样适用于在Arch Linux上安装，应该也可以作为在其他发行版进行安装时的参考。软件获取自己可以找一下，相对而言还算是比较好找的。如果找不到，我这里有：Abaqus_2022_Linux.7z.001 Abaqus_2022_Linux.7z.002 Abaqus_2022_Linux.7z.003 Abaqus_2022_Linux.7z.004 请下载全部压缩分卷后解压，解压密码是：www.nekopara.uk如果你下载我打包的版本，那安装前准备的修改Linux.sh脚本部分可以跳过，已经帮改好了。安装前准备首先，进入安装目录，赋予全部文件可执行权限已经修改权限，不然你会得到报错：Error: This media has been unzipped or copied in such a way that the permissions of some files have been lost. Please unzip or copy the media again.chmod -R 777 DS.SIMULIA.Suite.2022.Linux64/*接着，要修改安装目录里面的每一个Linux.sh文件，强行指定检测的发行版为CentOS或者其他位于支持列表的发行版。这些文件存在于：DS.SIMULIA.Suite.2022.Linux64/1/inst/common/init/Linux.sh （这个好像是主安装程序的，但是实际上主安装程序没屌用，就是套娃然后依此启动3 4 5文件夹里面的安装程序，套娃安装，也是唐完了。）DS.SIMULIA.Suite.2022.Linux64/3/NETVIBES_Exalead_CloudView/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/3/Search_Doc/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/3/SIMULIA_FLEXnet_LicenseServer/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/4/SIMULIA_EstablishedProducts/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_Documentation/AllOS/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_EstablishedProducts_CAA_API/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_Isight/Linux64/1/inst/common/init/Linux.sh找到这一行：DSY_OS_Release=`lsb_release --short --id |sed 's/ //g'`修改为：DSY_OS_Release="CentOS"有一说一，这安装包的目录结构太抽象了，真的是唐完了。修改完成后，就可以开始安装了。安装软件我实测下来，是可以不使用root权限执行安装的，只需要安装在一个普通权限可以写入的文件夹里面即可。权限最小化，不轻易给出root权限！而且，直接不经包管理器修改/usr目录是不太好的。如你享受Windows上面那种套娃安装的操蛋感，那请执行:DS.SIMULIA.Suite.2022.Linux64/1/StartGUI.sh。他会一步一步引导你安装，记得勾选许可证服务器安装，用于验证：然后他就先给你吃一口屎：招笑完了，安装还要我一个一个去手动选择文件夹？《请插入 5 的包络体 3，或者指定其目录路径。》什么抽象翻译？接下来让我们手动一个一个安装，就不跟这个主安装程序一起发癫了。安装FLEXnet License Server进入目录：DS.SIMULIA.Suite.2022.Linux64/3/SIMULIA_FLEXnet_LicenseServer/Linux64/1/执行：./StartGUI.sh这里选择一个别的目录，例如我安装在别的硬盘就填别的目录/run/media/chocola/RAID0-2T/SIMULIA/，记得是替换前面这截：/usr/SIMULIA/，而不是这截粘贴上去，不然目录结构会乱。安装目标目录只要有写入权限即可：记得勾上这个：下一步安装就行了。安装Established Products进入目录：DS.SIMULIA.Suite.2022.Linux64/4/SIMULIA_EstablishedProducts/Linux64/1/，操作方法同上，记得更改目录。在安装界面，根据你的需要进行选择，我是直接全选了：选择跳过许可证设置：选择设置启动命令的地方，类似与快捷方式，用于启用程序，本身并不大，我是一起放在安装目录了：然后让你选择外部插件目录，同样的方式修改：下一步，求解器接口根据自己需求配置，我这里默认，下一步。选择工作目录位置，原本默认目录是/usr/temp很明显不太对，让我们放到安装目录下的temp文件夹：下一步，那个配置什么Tosca Fluid接口，我是直接下一步跳过了，然后开始安装。复制许可证在安装包内找到ABAQUSLM__lmgrd__SSQ.lic这个文件，将其复制到软件的这个目录：SIMULIA/License/2022/linux_a64/code/bin/下，我给出的是相对路径，实际路径你自己根据安装的位置匹配。复制完成后，在这个目录下，运行命令启动许可证管理器：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic然后你会惊喜的发现报错：zsh: 没有那个文件或目录: ./lmgrd然后你发现lmgrd这个逼文件确实是在目录里面，这是怎么回事呢，接下来开始排雷。软件启动排雷安装需要的软件包我们需要安装两个软件包来解决报错和问题，这两个软件包是：patchelf和libjpeg6-turbo：pacman -S patchelf libjpeg6-turbopatchelf用于修补二进制文件，解决无法启动的问题。对应的表现为启动时报错：./lmgrd zsh: 没有那个文件或目录: ./lmgrdlibjpeg6-turbo用于解决Abaqus2022 CAE启动报错：./abq2022 cae /run/media/chocola/RAID0-2T/SIMULIA/EstProducts/2022/linux_a64/code/bin/ABQcaeK: error while loading shared libraries: libjpeg.so.62: cannot open shared object file: No such file or directory Abaqus Error: Abaqus/CAE Kernel exited with an error.修复lmgrd的没有那个文件或目录执行以下命令修补`lmgrd：patchelf --set-interpreter /usr/lib/ld-linux-x86-64.so.2 ./lmgrd然后启动，得到下一个报错：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) ----------------------------------------------- 20:39:47 (lmgrd) Please Note: 20:39:47 (lmgrd) 20:39:47 (lmgrd) This log is intended for debug purposes only. 20:39:47 (lmgrd) In order to capture accurate license 20:39:47 (lmgrd) usage data into an organized repository, 20:39:47 (lmgrd) please enable report logging. Use Flexera's 20:39:47 (lmgrd) software license administration solution, 20:39:47 (lmgrd) FlexNet Manager, to readily gain visibility 20:39:47 (lmgrd) into license usage data and to create 20:39:47 (lmgrd) insightful reports on critical information like 20:39:47 (lmgrd) license availability and usage. FlexNet Manager 20:39:47 (lmgrd) can be fully automated to run these reports on 20:39:47 (lmgrd) schedule and can be used to track license 20:39:47 (lmgrd) servers and usage across a heterogeneous 20:39:47 (lmgrd) network of servers including Windows NT, Linux 20:39:47 (lmgrd) and UNIX. 20:39:47 (lmgrd) 20:39:47 (lmgrd) ----------------------------------------------- 20:39:47 (lmgrd) 20:39:47 (lmgrd) 20:39:47 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:39:47 CST 20:39:47 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:39:47 (lmgrd) Can't make directory /usr/tmp/.flexlm, errno: 2(No such file or directory) 20:39:47 (lmgrd) FlexNet Licensing (v11.16.5.1 build 257031 x64_lsb) started on miku-vm (linux) (10/21/2025) 20:39:47 (lmgrd) Copyright (c) 1988-2019 Flexera. All Rights Reserved. 20:39:47 (lmgrd) World Wide Web: http://www.flexerasoftware.com 20:39:47 (lmgrd) License file(s): ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) lmgrd tcp-port 27800 20:39:47 (lmgrd) (@lmgrd-SLOG@) =============================================== 20:39:47 (lmgrd) (@lmgrd-SLOG@) === LMGRD === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Start-Date: Tue Oct 21 2025 20:39:47 CST 20:39:47 (lmgrd) (@lmgrd-SLOG@) PID: 5586 20:39:47 (lmgrd) (@lmgrd-SLOG@) LMGRD Version: v11.16.5.1 build 257031 x64_lsb ( build 257031 (ipv6)) 20:39:47 (lmgrd) (@lmgrd-SLOG@) 20:39:47 (lmgrd) (@lmgrd-SLOG@) === Network Info === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Listening port: 27800 20:39:47 (lmgrd) (@lmgrd-SLOG@) 20:39:47 (lmgrd) (@lmgrd-SLOG@) === Startup Info === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Server Configuration: Single Server 20:39:47 (lmgrd) (@lmgrd-SLOG@) Command-line options used at LS startup: -c ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) (@lmgrd-SLOG@) License file(s) used: ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) (@lmgrd-SLOG@) =============================================== 20:39:47 (lmgrd) Starting vendor daemons ... 20:39:47 (lmgrd) Started ABAQUSLM (internet tcp_port 44829 pid 5591) 20:39:47 (lmgrd) license daemon: execute process failed: (./ABAQUSLM) -T miku-vm 11.16 7 -c :ABAQUSLM__lmgrd__SSQ.lic: 20:39:47 (lmgrd) license daemon: system error code: No such file or directory 20:39:47 (lmgrd) ABAQUSLM exited with status 45 (Child cannot exec requested server) 20:39:47 (lmgrd) Please correct problem and restart daemons解决办法是，用同样的手法，修补ABAQUSLM：patchelf --set-interpreter /usr/lib/ld-linux-x86-64.so.2 ./ABAQUSLM启动后，你又会得到一个船新的报错：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic 20:42:57 (lmgrd) ----------------------------------------------- 20:42:57 (lmgrd) Please Note: 20:42:57 (lmgrd) 20:42:57 (lmgrd) This log is intended for debug purposes only. 20:42:57 (lmgrd) In order to capture accurate license 20:42:57 (lmgrd) usage data into an organized repository, 20:42:57 (lmgrd) please enable report logging. Use Flexera's 20:42:57 (lmgrd) software license administration solution, 20:42:57 (lmgrd) FlexNet Manager, to readily gain visibility 20:42:57 (lmgrd) into license usage data and to create 20:42:57 (lmgrd) insightful reports on critical information like 20:42:57 (lmgrd) license availability and usage. FlexNet Manager 20:42:57 (lmgrd) can be fully automated to run these reports on 20:42:57 (lmgrd) schedule and can be used to track license 20:42:57 (lmgrd) servers and usage across a heterogeneous 20:42:57 (lmgrd) network of servers including Windows NT, Linux 20:42:57 (lmgrd) and UNIX. 20:42:57 (lmgrd) 20:42:57 (lmgrd) ----------------------------------------------- 20:42:57 (lmgrd) 20:42:57 (lmgrd) 20:42:57 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:42:57 CST 20:42:57 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:42:57 (lmgrd) Can't make directory /usr/tmp/.flexlm, errno: 2(No such file or directory) 根据报错，这逼养的程序想要一个不符合规范的临时目录：/usr/tmp/，正常的临时目录应该是/tmp给他就是了：mkdir -p /usr/tmp chmod 1777 /usr/tmp再次启动，这次应该就正常了？然而并卵：20:49:59 (lmgrd) ----------------------------------------------- 20:49:59 (lmgrd) Please Note: 20:49:59 (lmgrd) 20:49:59 (lmgrd) This log is intended for debug purposes only. 20:49:59 (lmgrd) In order to capture accurate license 20:49:59 (lmgrd) usage data into an organized repository, 20:49:59 (lmgrd) please enable report logging. Use Flexera's 20:49:59 (lmgrd) software license administration solution, 20:49:59 (lmgrd) FlexNet Manager, to readily gain visibility 20:49:59 (lmgrd) into license usage data and to create 20:49:59 (lmgrd) insightful reports on critical information like 20:49:59 (lmgrd) license availability and usage. FlexNet Manager 20:49:59 (lmgrd) can be fully automated to run these reports on 20:49:59 (lmgrd) schedule and can be used to track license 20:49:59 (lmgrd) servers and usage across a heterogeneous 20:49:59 (lmgrd) network of servers including Windows NT, Linux 20:49:59 (lmgrd) and UNIX. 20:49:59 (lmgrd) 20:49:59 (lmgrd) ----------------------------------------------- 20:49:59 (lmgrd) 20:49:59 (lmgrd) 20:49:59 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:49:59 CST 20:49:59 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:50:14 (lmgrd) The TCP port number in the license, 27800, is already in use. 20:50:14 (lmgrd) Possible causes: 20:50:14 (lmgrd) 1) The license server manager (lmgrd) is already running for this license. 20:50:14 (lmgrd) 2) The OS has not "cleared" this port since lmgrd died. 20:50:14 (lmgrd) 3) Another process is using this port number (unlikely). 20:50:14 (lmgrd) Solutions: 20:50:14 (lmgrd) 1) Make sure lmgrd and all vendor daemons for this 20:50:14 (lmgrd) license are not running. 20:50:14 (lmgrd) 2) You may have to wait for the OS to clear this port. 20:50:14 (lmgrd) Retrying for about 5 more minutes 20:50:32 (lmgrd) Still trying... 我们要来释放被占用的端口，这时观察进程列表，发现残留了多个lmgrd，通通干掉，然后再尝试启动：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic命令行出现下面的内容，就说明成功启动了：20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) =============================================== 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Vendor Daemon === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Vendor daemon: ABAQUSLM 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Start-Date: Mon Oct 20 2025 20:31:06 CST 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) PID: 3409793 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) VD Version: v11.16.5.1 build 257031 x64_lsb ( build 257031 (ipv6)) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Startup/Restart Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Options file used: None 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is vendor daemon a CVD: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is FlexNet Licensing Service installed and compatible: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) FlexNet Licensing Service Version: -NA- 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is TS accessed: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) TS access time: -NA- 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Number of VD restarts since LS startup: 0 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Network Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Listening port: 54131 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Daemon select timeout (in seconds): 1 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Host Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Host used in license file: chocola-sa5212m4 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) HostID node-locked in license file: ID=20170101 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) HostID of the License Server: "6c92bf6bb2d1 6c92bf6bb2d0" 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Running on Hypervisor: Not determined - treat as Physical 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) ===============================================软件许可证设置如果不进行这步，直接打开Be Like：./abq2022 cae Failed to startup licensing (err01): 1A000058 ERROR 1A000058: License servers configuration file not found 13:14:57.UTC DSLS server status: 0 license server declared in file /var/DassaultSystemes/Licenses/DSLicSrv.txt File not found. Abaqus Error: Abaqus/CAE Kernel exited with an error.配置方法并不难，找到安装目录的这个位置：SIMULIA/EstProducts/2022/linux_a64/SMA/site/的custom_v6.env文件。注释掉importEnv('licensing.env')，然后加上这两行：license_server_type=FLEXNET abaquslm_license_file="27800@localhost"我给出一个示例方便理解。示例原文件内容：# Installation of Established Products 2022 # Tue Oct 21 21:09:02 2025 plugin_central_dir="/run/media/chocola/RAID0-2T/SIMULIA/CAE/plugins/2022" # retrieve licensing configuration from EstablishedProductsConfig.ini importEnv('licensing.env')示例修改后的内容:# Installation of Established Products 2022 # Mon Oct 20 18:27:00 2025 plugin_central_dir="/run/media/chocola/RAID0-2T/SIMULIA/CAE/plugins/2022" # retrieve licensing configuration from EstablishedProductsConfig.ini license_server_type=FLEXNET abaquslm_license_file="27800@localhost" #importEnv('licensing.env')这时，你应该就可以进入SIMULIA/Commands/目录，启动Abaqus了：./abq2022 cae如果到这里你报错libjpeg.so.62缺失，请返回这个步骤的开头安装libjpeg6-turbo软件包。不出意外的话，你会看到这样一个界面：这就说明你的安装成功了，吗？然而并没有，如果你使用这个软件进行模拟分析，添加自己的分析程序，你会发现无法进行！原因很简单，没有fortran环境！部署Abaqus需要的Fortran环境当发生报错的时候，我整个人是蒙逼的，没有任何技术提示，只是说终止了。然后我才意识到在启动的位置有log文件，也就是在SIMULIA/Commands/目录里面，因为在这里启动的，算是工作目录，打开一看：Analysis initiated from SIMULIA established products Abaqus JOB C3D20_Uel_2 Abaqus 2022 Abaqus License Manager checked out the following licenses: Abaqus/Standard checked out 5 tokens from Flexnet server localhost. <9994 out of 9999 licenses remain available>. Begin Compiling Abaqus/Standard User Subroutines 2025年10月20日 星期一 20时50分43秒 sh: 行 1: ifort: 未找到命令 Abaqus Error: Problem during compilation - /run/media/chocola/Rin/abq_job/C3D20.f Abaqus/Analysis exited with errors问题一目了然：ifort: 未找到命令。安装Intel oneAPI HPC Toolkit上网查阅资料得知，ifort是牙膏厂的Intel oneAPI HPC Toolkit的其中一个工具，所以我们需要去牙膏厂官网下载并安装这个工具，下载入口Bing搜一下就有了。下载回来的安装包是一个2.2GB的.sh文件，属实逆天。下载回来后，执行安装，不需要root权限：chmod +x intel-oneapi-base-toolkit-2025.0.1.46_offline.sh ./intel-oneapi-base-toolkit-2025.0.1.46_offline.sh安装后会占用13.3GB空间，要找一个空间较大的地方安装。安装完成后，进入安装目录，找到setvars.sh文件，然后在目录执行：source ./setvars.sh执行完成后，查看环境设置是否正常:ifx -help如果有输出内容，就没问题了。但是这时候会有一个问题，Abaqus的命令是ifort，而新版的oneAPI HPC Toolkit已经变成ifx了，怎么办呢？如果你经常用Linux，那你已经有答案了——软链接进入安装目录下的oneapi/compiler/2025.0/bin/目录，找到ifx文件，然后：ln -s ./ifx ifort这下就不会ifort: 未找到命令了。带上Fortran环境启动Abaqus在oneAPI的目录使用source ./setvars.sh临时导入环境到终端后，切换到Abaqus的安装目录启动Abaqus即可：source ./setvars.sh cd /run/media/chocola/RAID0-2T/SIMULIA/Commands ./abq2022 cae 不出意外的话，这次就真能运行了：
基于 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设置壁纸路径即可享用。虽然但是有点吃性能就是了（
Alpine Linux 部署私人在线聊天室 Fiora引言众所周知，在现代的信息社会，想保持不被监控是非常困难的事情。由于种种原因，总有人会希望监控你。例如，某些视频平台（例如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私人媒体服务器，告别屌毛网易云！
Jellyfin私人媒体服务器搭建指南成功搭建了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 别告诉我你不会解压缩，不然我只会骂你唐货！2025年12月补充：新文章有更好的音乐爬取方法，详情看用 Python 破解网易云 iframe 反爬，全自动构建本地音乐库 这篇文章
个性化你的Filebrowser！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; } }