@@ -383,7 +383,7 @@ Subsonic 用户目录中的 `subsonic-local-registry.json` 和 `subsonic-local-s
配置备份默认开启,保留 7 天;默认位置为数据目录的 `backups/`。可以用 `CONFIG_BACKUP_DIR` 和 `SNAPSHOT_BACKUP_PATH` 自定义配置备份、歌单快照的存储位置。
-在「系统设置 → 云端备份范围」中,可分别选择 WebDAV 和 OpenList 备份的数据类型。两端默认都选中以下五类,但仍需单独启用对应服务并配置连接信息;同时启用会保存两份副本,不会在两个云端之间同步。
+在「备份管理 → 云端备份 → 备份范围」中,可分别选择 WebDAV 和 OpenList 备份的数据类型。两端默认都选中以下五类,但仍需单独启用对应服务并配置连接信息;同时启用会保存两份副本,不会在两个云端之间同步。
| 类型 | 备份内容 |
| --- | --- |
@@ -404,9 +404,13 @@ Subsonic 用户目录中的 `subsonic-local-registry.json` 和 `subsonic-local-s
类型数组为空表示该端不备份任何数据类型。普通数据备份始终排除缓存、日志、临时文件、运行数据库和本地备份包;程序根目录的音乐和自定义外部音乐库需要另行备份。OpenList 的「永久播放缓存」单独配置,勾选歌曲文件不会自动开启缓存上传或删除本地歌曲。
+新增配置项 `configBackup.intervalHours`(默认 24)、`configBackup.scheduleTime`(默认 `00:00`)、`webdav.retentionCount`(默认 5)和 `webdav.retentionDays`(默认 0,不限天数)通过后台或 `config.js` 设置。OpenList 连接、历史保留和播放来源选项也使用配置键,目前没有 `OPENLIST_*` 等对应环境变量;不要把配置键自动改写成大写变量。完整默认值见 [备份配置参考](docs/guide/configuration.md#备份配置项-后台或-config-js)。
+
WebDAV 增量文件与 ZIP 备份采用相同范围,默认分别每 60 分钟、24 小时执行;OpenList 数据备份保存在其根目录的 `_data/` 下,默认每 60 分钟检查。普通备份保留本地文件。WebDAV 默认也保留云端已有文件,只有显式开启 `backup.webdav.mirrorDeletes` 才会向增量目录传播本地删除,历史 ZIP 不受影响。
-WebDAV 默认不在启动时恢复,需显式开启 `backup.webdav.restoreOnStartup`。手动恢复按该端当前勾选范围下载,覆盖本地同名文件并保留本地独有文件,不会反向上传。恢复不是整个目录的原子替换,完成后应重启服务,使配置、账号、歌单和音乐索引完整重新加载。部署新实例前确认云端目录和备份范围,具体步骤见 [备份与恢复](docs/guide/backup-recovery.md)。
+普通 OpenList 上传只检查响应、存在与大小,不下载正文;需要核对内容时,在「备份管理 → 记录与校验 → OpenList → 内容校验」先查看预计流量再手动启动。上传后删除本地缓存仍需完整内容校验。
+
+WebDAV 默认不在启动时恢复,需显式开启 `backup.webdav.restoreOnStartup`。WebDAV 手动恢复先预览,支持仅补缺失或覆盖同名文件,保留本地独有文件,不反向上传;所有所选文件暂存校验后才写入,写入失败尝试回滚,默认保留恢复前副本。恢复不是整个目录的原子替换,完成后应重启服务,使配置、账号、歌单和音乐索引完整重新加载。部署新实例前确认云端目录和备份范围,具体步骤见 [备份与恢复](docs/guide/backup-recovery.md)。
### 更新与迁移
@@ -424,7 +428,7 @@ docker compose logs --tail=100
系统默认使用 `data/config.js`;`DATA_PATH` 改变数据目录,`CONFIG_PATH` 指定独立配置文件。首次启动时,若目标配置文件不存在,会尝试从程序根目录的旧配置初始化。
-普通启动按默认值、配置文件、环境变量的顺序合并配置。显式启用 WebDAV 启动恢复时,恢复过程还会重新加载远端配置和账号数据;手动恢复后需要重启服务。当前生效值和配置路径可在管理后台查看。
+普通启动按默认值、配置文件、环境变量的顺序合并配置。显式开启 WebDAV 启动恢复后,还会重新加载备份中的配置和账号;手动恢复后需要重启服务。当前生效值和配置路径可在管理后台查看。
环境变量在进程启动时读取。修改 Compose 中的环境变量后需要重建容器;后台配置是否即时生效取决于具体选项,监听端口和路径调整后应按提示重新连接或重启。
diff --git a/README_EN.md b/README_EN.md
index 13404310..9980d769 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -206,7 +206,7 @@ Do not use `/admin`, `/rest` or the player access password for LX sync. Review m
### Subsonic clients
-Compatible clients include Yinliu, Feishin and Symfonium. The server implements Subsonic/OpenSubsonic endpoints for library browsing, search, playback and related features; the UI and available features vary by client.
+Compatible clients include Yinliu, Feishin and Symfonium. The server implements Subsonic/OpenSubsonic endpoints for library browsing, search, playback and related features; the UI and available features vary by client. See [Subsonic protocol support](docs/en/api/subsonic.md) for coverage and limits.
| Field | Value |
| --- | --- |
@@ -220,7 +220,7 @@ By default Subsonic shares the main port. Separate listening requires `subsonic.
Online search uses `subsonic.onlineSearch` and `subsonic.onlineSearchMode`: `fallback` supplements insufficient local results; `merge` combines local and online results; `local_only` stays local. Query prefixes include `wy:`, `tx:`, `kw:`, `kg:`, `mg:`, `online:` and `local:`. Online playback still needs a working custom source.
-Local library selection follows the account's saved storage location plus its enabled custom directory. A temporary browsing switch in the web UI does not change that library. Read-only local/custom playlists can be exposed separately. Pure local playlists stay on the server; only the online portion of mixed playlists enters LX native sync.
+Local library selection follows the account's saved storage location plus its enabled custom directory. A temporary browsing switch in the web UI does not change that library. Read-only local/custom playlists can be exposed separately. Pure local playlists stay on the server; only the online portion of mixed playlists enters LX native sync. See [Subsonic local music library](docs/en/guide/subsonic-local-library.md).
@@ -340,7 +340,7 @@ Keep `subsonic-local-registry.json` and `subsonic-local-state.json` with user da
Playlist snapshots restore playlists; configuration backups restore configuration. Neither substitutes for audio backups. Configuration backups default to a seven-day retention period under the data directory's `backups/` folder.
-In **System settings → Cloud backup scope**, select data types independently for WebDAV and OpenList. Both default to all five types below, but each provider must also be enabled and configured. Enabling both creates two copies; it does not synchronize the providers with each other.
+In **Backup management → Cloud backup → Backup scope**, select data types independently for WebDAV and OpenList. Both default to all five types below, but each provider must also be enabled and configured. Enabling both creates two copies; it does not synchronize the providers with each other.
| Type | Contents |
| --- | --- |
@@ -361,9 +361,13 @@ These system settings are saved in the active `config.js` and have no correspond
An empty type array selects no data for that provider. Ordinary data backups always exclude caches, logs, temporary files, runtime databases and local backup archives. Back up root-directory music and custom external libraries separately. OpenList's **permanent playback cache** is configured separately; selecting music files does not enable cache uploads or local song deletion.
+Set `configBackup.intervalHours` (default 24), `configBackup.scheduleTime` (default `00:00`), `webdav.retentionCount` (default 5) and `webdav.retentionDays` (default 0, no age limit) in the admin UI or `config.js`. OpenList connection/history/playback settings also use configuration keys; there are currently no corresponding `OPENLIST_*` environment mappings. See the [configuration reference](docs/en/guide/configuration.md#backup-configuration-keys-admin-ui-or-config-js) for defaults.
+
WebDAV incremental files and ZIP backups use the same scope, running every 60 minutes and 24 hours by default. OpenList stores data backups under `_data/` within its root and checks every 60 minutes by default. Ordinary backups retain local files. WebDAV also retains existing cloud files by default; explicitly enabling `backup.webdav.mirrorDeletes` propagates local deletions to the incremental directory, without changing historical ZIPs.
-WebDAV startup restore is disabled by default and requires `backup.webdav.restoreOnStartup`. Manual restore downloads the provider's currently selected types, overwrites matching local files and preserves local-only files, without uploading local data. Restore is not an atomic replacement of the entire directory. Restart the service afterward to fully reload configuration, accounts, playlists and music indexes. Confirm the cloud namespace and selected scope before connecting a new installation. See [Backup and recovery](docs/en/guide/backup-recovery.md) for procedures.
+Ordinary OpenList uploads check existence and size without downloading the body. Use **Backup management → Records and verification → OpenList → Content verification** for manual SHA-256 checks and preview the maximum body-read estimate before starting. Upload-and-delete cache mode still requires full verification.
+
+WebDAV startup restore is disabled by default and requires `backup.webdav.restoreOnStartup`. WebDAV manual restore previews the selected scope, supports missing-only or overwrite, and preserves local-only files without uploads. Required files are staged and checked before replacement; commit failure attempts rollback, with pre-restore copies kept by default. Restore is not an atomic replacement of the entire directory. Restart the service afterward to fully reload configuration, accounts, playlists and music indexes. Confirm the cloud namespace and selected scope before connecting a new installation. See [Backup and recovery](docs/en/guide/backup-recovery.md) for procedures.
For container updates:
@@ -427,7 +431,7 @@ Defaults below refer to code defaults. Bundled configuration, saved values and i
| `ENABLE_LOGIN_USER_CACHE_RESTRICTION` | `user.enableLoginCacheRestriction` | Enable cache settings restriction for logged-in non-admin users | `false` |
| `ENABLE_CACHE_SIZE_LIMIT` | `user.enableCacheSizeLimit` | Enable cache size limit (auto-cleanup via LRU) | `false` |
| `CACHE_SIZE_LIMIT` | `user.cacheSizeLimit` | Cache size limit in MB | `2000` |
-| `CONFIG_BACKUP_ENABLE` | `configBackup.enable` | Enable automatic config.js backup (creates daily backup copy) | `true` |
+| `CONFIG_BACKUP_ENABLE` | `configBackup.enable` | Enable automatic config.js backup (default: every 24 hours) | `true` |
| `CONFIG_BACKUP_RETENTION_DAYS` | `configBackup.retentionDays` | Config backup retention period in days (auto cleans up expired backups) | `7` |
| `CONFIG_BACKUP_DIR` | `configBackup.dir` | Backup directory, relative to data; empty uses data/backups | `empty` |
| `SNAPSHOT_BACKUP_PATH` | `snapshot.backupPath` | Custom storage path for playlist snapshots (absolute or relative to `./data`) | - |
diff --git a/changelog.md b/changelog.md
index a869edf2..affb646c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,17 +1,80 @@
# Changelog
-## v2.2.0 (2026-10-31)
+## v2.2.0 (2026-11-11)
### 🌟 新增功能与体验升级
-- **OpenList 备份与统一云端备份范围 (#488 @pyss56)**:支持 OpenList 数据备份、恢复及独立的永久播放缓存;系统设置可分别选择 WebDAV / OpenList 备份歌曲、用户文件、快照、用户数据和系统配置,设置保存至 `config.js`。歌曲备份仅包含数据目录内的 `music/`,不包含缓存或外部音乐库。
+- **后台仪表盘与明/暗主题重构**:仪表盘重新设计为数据看板,保留 KPI 与 CPU/内存实时图并新增 1/5/15 分钟时间窗口和趋势迷你折线;新增「日志活动」(近 12 小时分级柱状图与 24 小时错误/警告统计)、「服务与存储」(WebDAV、OpenList 状态与最近/下次备份、数据盘与进程内存用量)、「用户与设备」在线概览和「最近告警」列表,对应新增 `GET /api/dashboard/overview` 接口。后台界面去除渐变与毛玻璃,改为实色卡片加细边框的单一强调色风格,并新增顶栏明/暗主题切换按钮(默认跟随系统、记住选择,图表颜色同步切换)。
+ - 
+- **Subsonic 本地歌单缓存筛选**:在“返回本地歌曲歌单”下新增“返回缓存目录歌曲”,默认关闭,仅返回 `music` 歌曲;开启后将 `cache` 歌曲合并到本地歌单。
+ - **返回缓存目录歌曲**
+ - 
+ - 获取歌单列表时跳过歌曲详情转换,详情仅读取一次收藏状态;自定义目录与内置 `music` / `cache` 重合时优先按内置目录归属,避免本地歌曲被错误隐藏。
+- **Subsonic 评分歌单与联动补数据 (#508 @pyss56)**:新增按 1–5 星汇总的虚拟评分歌单,1 星歌单包含命中不喜欢规则的歌曲,空星级不生成歌单;接入评分快照创建、导出与恢复,并在后台提供目标用户选择和两个方向的手动联动补数据入口。
+- **快照导入与恢复完善 (#508 @pyss56)**:歌单及不喜欢快照区分导入与系统生成来源,支持按来源筛选,避免导入记录参与系统快照自动清理;恢复前先保存当前状态。
+- **Web 播放器歌曲评分**:播放器、歌曲列表、歌单、排行榜及本地音乐支持 1–5 星评分与清除,「我的评分」集中查看、筛选及导出;与 Subsonic 共用评分数据,独立保存到评分目录,不写入歌单。本地歌曲关联在线版后,在线已有评分优先,否则迁移本地评分、来源与历史,并删除本地重复记录。
+ - 
+- **后台评分与不喜欢数据查看**:按用户查看歌曲、歌手、专辑的评分与不喜欢记录,展示封面、音乐平台、添加或评分来源及详细资料;支持组合筛选、排序、分页和导出筛选结果。
+ - 
+- **Subsonic 缓存优先时优先使用 OpenList**:后台新增「缓存优先时优先使用 OpenList」设置,可在缓存优先播放流程中优先尝试该用户可用的 OpenList 播放来源。
+ - 
+- **Web 不喜欢自动补分设置**:在 Web 设置中为当前账号单独开启或关闭「不喜欢自动补分」,默认关闭;将歌曲加入不喜欢时,仅为未评分歌曲补 1 星,已有评分不覆盖。设置保存到服务器,同一账号跨设备生效,后台手动补分独立执行。
+ - 
+- **聚合搜索关键词联想**:并发获取多源联想,快源先展示,合并重复词并优先展示多源共同推荐内容;增加短期缓存、超时与中文输入法处理,切换输入或正式搜索时取消旧联想,支持方向键选词和回车搜索,保留聚合音源选择。
+- **歌曲搜索分批加载与聚合排序**:各音源先请求一页并按歌名相关性及原始排名交错展示,保留按设置数量的本地分页,翻到未加载页面时自动取下一批,也可点击常驻「加载更多」按钮;独立记录各源页码、结束与失败状态,避免深页翻唱因歌手名字较短挤到前列,修复跨源 ID 碰撞,并保留手动追加前的页码、结果顺序与滚动位置。
+- **内置音乐 SDK 接口更新**:同步 QQ 签名搜索、推荐歌单、歌手与歌单详情及 QRC 逐字歌词,更新酷我歌词、网易歌手和咪咕封面接口,补齐酷狗分享歌单解析;保留被替换的旧实现为注释,修复歌词时间精度、分页与取消请求问题。
+- **OpenList 备份与统一云端备份范围 (#488 @pyss56)**:支持 OpenList 数据备份、恢复及独立的永久播放缓存;可分别选择 WebDAV / OpenList 备份歌曲与歌词、用户音源文件、快照、用户数据和系统配置。常规歌曲备份包含 `data/music/` 内的音频、LRC、封面与索引,排除独立的根目录 `music/`、所有 `cache/` 及外部自定义音乐目录;OpenList 播放缓存按用户单独配置上传、下载、双向同步或校验后删除本地副本。
+- **后台统一备份管理**:将系统配置中的备份设置及 WebDAV、OpenList 入口集中到「备份管理」,按概览、本地备份、云端备份、数据恢复、记录与校验组织;补充执行时间、周期、范围和异常状态,未启用的服务收起相关操作。重构记录筛选,支持数据类型、用户、存储类别、状态、本地存在性、日期、关键词与排序,并优化文件详情、用户归属及时间显示。
+ - 
+- **服务器已有歌曲优先播放与本地歌词**:Web 播放器可优先读取当前用户的 `music/` 与 `cache/`,按用户存储设置使用数据目录或运行根目录下的位置;新增「优先使用服务器本地歌词」,支持匹配已有 LRC,本地音乐条目也支持自定义目录中的同名歌词。补充 Web / Subsonic 播放顺序说明,区分已有文件读取、后台缓存写入和浏览器缓存。
+ - 
+- **OpenList 播放来源与内容校验**:开启相关设置后,Web / Subsonic 在本地未命中时可使用该用户已登记的 OpenList 缓存或下载文件,不可用时回退在线解析,无需每次遍历网盘;WebDAV 仍用于备份恢复。普通上传仅检查响应与云端大小,独立的手动内容校验支持预估流量、后台执行、停止及结果筛选;「上传完成」与当前版本的「已校验」分别展示,上传后删除本地缓存仍需完整内容校验。
- **均衡器与音效调节界面重构与紧凑交互**:
- 
- **动态生效配置标签(Tag)与一键移除**:顶部状态栏实时展示已生效的音效配置标签(环境混响、EQ 预设/段数、音调调节、3D 环绕),支持鼠标悬停(桌面端)或轻点(移动端)标签右上角 `✕` 一键关闭单项配置;多项开启时支持一键「全部重置」,并提供全局总开关及纯净原音直通提示。
+- **歌词卡片分享界面现代化**:重绘弹窗样式,加大圆角与悬浮阴影,预览区加入主题色光晕与点阵背景,预览卡片增加细描边和悬停辉光;比例、色彩风格、歌词行数按钮改为半透明渐变卡片,带悬停上浮、点击缩放及选中发光反馈;复选框与滑块改为自绘主题色样式,下载按钮增加渐变、内高光与悬停光泽,复制按钮采用毛玻璃描边。仅调整样式,不影响卡片生成逻辑。
+ - 
### 🐛 缺陷修复与体验优化
-- **备份与恢复流程梳理**:统一 WebDAV 增量、ZIP 和离线导入导出的数据范围,完善 OpenList 上传校验、失败重试及 Subsonic 云端播放处理;WebDAV 启动恢复和云端删除同步默认关闭,恢复按所选类型覆盖同名文件,完成后需重启服务。
+- **PWA 与浏览器下载认证完善 (#496)**:原生下载前检查并恢复登录 Cookie,恢复后再次确认会话有效;Fetch 下载携带同源认证头,登录失效或账号切换时明确提示并停止自动重试,避免页面可操作但下载提示「需要授权」。
+- **自动切歌优先使用已完成缓存 (#490)**:接管下一首预读链接前重新检查服务器已有歌曲,避免后台缓存已完成仍使用在线链接;补查仅检查本地文件,设置 1.2 秒超时,未就绪或失败时继续使用预读链接;预读按平台、标准化歌曲 ID 和目标音质匹配,避免跨平台或修改音质后误用旧结果。关闭缓存优先后不再复用在线歌曲的文件缓存预读,播放失败时清除对应预读记录,保留并发更新的新结果。快速切歌或队列变化后丢弃旧预读结果,关闭预读时立即停止缓冲,避免过期任务覆盖下一首。
+- **无可用音源时停止无效重试**:保留本地与缓存播放,在线解析确认当前用户无可用源后停止自动换源、降音质、跳歌及后台连续预读,统一提示添加或启用音源;仅当前平台无源时跳过同平台重试与降音质。
+- **自定义目录加载与索引优化 (#475)**:重复进入复用列表,按索引版本更新并保留分页与筛选,关闭歌词详情不再重复加载;文件操作后局部更新,合并并发扫描与目录遍历,批量删除仅保存一次索引。索引采用临时文件替换,读取失败保留有效记录,防止异步补数据覆盖新关联;Subsonic 按需自动扫描间隔调整为至少 5 分钟,手动同步仍立即执行。
+- **切歌进度重置 (#494)**:切换歌曲时清零进度显示,避免新歌链接解析失败后自动重试时误用上一首的播放位置,保留同曲故障恢复与刷新续播。
+- **缓存自动清理修复 (#489)**:空间限制仅清理 `cache`,不再误删 `music` 下载文件;异步递归统计并分批清理媒体文件,保护索引、临时文件、下载收尾与活跃备份,按最近访问或修改时间成组清理音频和歌词并同步索引。避免清理替换扫描中的索引实例,并容忍扫描途中删除文件;修复保留天数重启丢失,启动、修改清理设置后及定时检查两处缓存目录。
+- **下载与播放歌词获取流程统一**:复用服务器歌词缓存、原平台接口及内嵌歌词兜底,补齐收藏歌曲和咪咕等平台的歌词参数;下载仅按平台与歌曲 ID 匹配,禁用同名歌曲兜底,并保留翻译、音译与用户隔离。
+- **浏览器单曲分段下载**:支持 Range 的大文件默认使用 4 路分段下载,临时文件拼接后继续写入封面和歌词;加入分段校验、失败重试、单连接回退、取消清理及全局连接限制,连接中断时同步显示失败状态。
+- **单曲下载音质筛选修复**:仅展示已验证可用或服务器已有的音质,过滤错误响应、异常文件大小和返回音质不匹配的选项,修复「64 B」占位大小误显示;筛选后按实际音质提交下载与缓存任务。
+- **下载内嵌歌词修复**:统一处理带音源前缀的歌曲 ID,在线下载优先读取当前用户已有歌词,保留翻译与音译;补充歌词写入回读校验及失败日志,修复网页有歌词但下载文件未嵌入的问题。
+- **浏览器下载进度与状态修复(#485)**:补齐进度查询认证信息,修复文件已下载却提示「浏览器未启动下载」的误报;服务器已有文件直接交给浏览器保存,无法确认进度时提示查看浏览器下载记录,进度查询失败不再计为下载失败。
+- **评分模块归类与迁移边界修复**:评分服务、偏好、快照、校验、身份锁及事务迁移集中到 `src/server/rating/`;修复无评分用户的关联整理、仅评分事务覆盖未保存不喜欢数据的问题,并在不喜欢数据初始化时恢复遗留事务。
+- **评分迁移与恢复一致性修复**:本地关联在线歌曲时同步迁移不喜欢原因中的评分 ID;评分、歌曲资料与相关不喜欢快照通过事务日志提交,写入失败可回滚,中断后可恢复。旧评分快照恢复时直接整理关联身份,保留手动不喜欢原因与历史快照。
+- 修复不喜欢规则括号空格重复、同身份资料补全及跨来源隔离;记录评分与不喜欢来源,手动同步增加候选预览、资料补全统计及未解析失败计数。
+- 修复 Subsonic 评分歌单显示与自动联动开关耦合,补齐评分歌单配置和评分快照备份分类;恢复评分时保留收藏,缓存淘汰后尝试重新解析评分歌曲,并提示评分已保存但联动失败的情况。
+- **酷狗码导入修复 (#483)**:完善酷狗分享歌单的链接解析与歌曲列表获取,修复部分歌单导入失败的问题。
+- **备份记录详情面板优化**:加宽桌面详情弹窗,分开展示标题与文件名,优化表格间距及长路径、SHA-256 和元数据换行,避免内容撑出面板或挤压关闭按钮,并适配移动端宽度。
+- **列表长文本滚动优化**:Web 播放器及后台多类列表中,超出宽度的文件名、歌曲名、歌单名等文字在桌面端悬停时滚动,移开后恢复开头;移动端保持自动滚动,完整显示的文字保持静止。
+ - 
+- **后台滚动条样式统一**:横向、纵向滚动条统一采用深色轨道与细圆角滑块,增加悬停及拖动颜色反馈,优化列表、表格和弹窗等区域的视觉效果。
+- **清空搜索返回热搜页修复**:网络搜索时,点击输入框清空按钮或手动清空关键词后,取消旧搜索并清除搜索缓存,返回热搜页,避免继续停留在搜索结果列表。
+- **搜索进度显示范围修复**:搜索状态条仅在网络搜索页显示,切换收藏分类或其他列表时隐藏,返回搜索后恢复当前状态,避免后台更新导致残留。
+- **暗色模式开关状态修复**:修复开启后的开关底色被暗色灰色样式覆盖的问题,歌曲洗版等开关统一使用主题色区分开启与关闭状态。
+- **AirPlay 原生播放恢复修复 (#511 @pokemonred06-creator)**:关闭混响时保留原生模式,避免混响滑块动画在切回本机后重新启用音效或覆盖后续修改,恢复提示按指定时长显示。
+- **无需刷新恢复原生播放**:关闭音效或点击恢复按钮时,通过新音频元素交接歌曲、进度、暂停状态、音量和倍速,释放旧音频图及 iOS 保活桥;保留队列和音效配置,切歌及快速开关不会套用旧进度。切换可能短暂缓冲,浏览器阻止续播时提示手动播放。
+- **音效原生模式提示与配置预览**:原生播放显示醒目的状态标签,总开关显示关闭,保留全部配置但锁定编辑;明确打开总开关后按当前配置启用本机音效,投播期间锁定本机音效控件。
+- **删除用户时清理音源文件**:单个或批量删除用户并勾选删除数据时,同时清理该用户的自定义音源脚本、顺序及状态文件,保留公共音源和其他用户文件。
+- **歌手与专辑详情切换及返回修复**:切换标签、收藏分类或歌单时清理详情头部与悬浮栏,避免残留到其他列表;修复打开专辑后歌手详情因容器被删除而一直加载的问题;记录详情入口,返回时恢复对应的收藏歌手、收藏专辑或原歌单,避免浏览器返回事件重复处理后跳到「我的喜爱」。
+- **批量选择行高亮修复**:修复收藏、搜索等歌曲列表点击「全选」后仅更新选中数量、未同步行高亮的问题;正确识别当前显示的列表,统一排行榜批量操作的刷新入口,确保全选、清空及退出批量模式时选中状态与高亮同步。
+- **播放队列去重与歌曲定位修复**:统一搜索结果与默认列表的歌曲 ID 匹配规则,修复播放时自动加入默认列表的歌曲仍可重复入队的问题;「加入播放队列」遇到已有歌曲时提示并展开侧边面板,滚动定位、高亮对应歌曲;「下一首播放」将已有条目移至下一首,当前正在播放的歌曲不再重复添加。
+- **搜索页面意外跳回收藏修复 (#482)**:修复恢复上次播放时遗留的歌单跳转覆盖当前搜索页面的问题;收藏歌单后台更新完成后,仅在仍浏览该收藏列表时刷新内容,避免搜索过程中突然切回“我的喜爱”等列表。
+- **管理面板 WebDAV 连接测试修复 (#474)**:改为分别检查已保存配置的同步目录与备份目录,避免根目录列举失败导致测试误判;逐项显示检查结果,并针对认证、权限、路径、网关、网络及证书错误提供排查建议。请求超过 10 秒自动取消,测试期间禁止重复点击;明确说明测试仅验证目录读取,不验证上传、删除权限或完整恢复能力。
+- **备份管理手机端布局优化**:统一设置与恢复页间距、按钮分组和触控尺寸;本地配置备份与快照按卡片显示,下载、恢复、删除等宽排列,长路径和文件名自动换行,优化预览、任务记录、校验结果及详情弹窗的窄屏展示。
+- **后台提示样式统一**:批量重试使用后台确认弹窗,取消不会提交且避免重复操作;移除管理主页连接测试、同步结果及详情异常中的浏览器原生提示框。
+- **OpenList 轻量文件检查**:可按已选、筛选结果或当前连接全部记录手动检查本地与云端文件元信息,显示进度、停止操作及上次检查时间;结果按连接和版本保留,不下载文件正文,也不上传或删除文件。
+- **备份任务与校验状态统一**:记录页按待上传、待下载、传输中、已备份、需处理、失败及暂停展示主状态,内容校验作为独立历史标记;本地变化诊断收进详情和高级筛选,云端变化后可重新校验,记录与校验页统一类型、用户和结果筛选。
+- **备份记录本地状态修复**:查询记录时检查实际文件,修复手动删除后仍显示“本地存在”的问题;打开详情重新获取状态,权限异常显示“无法检查”,不影响云端备份与内容校验记录。
+- **备份与恢复可靠性改进**:统一 WebDAV 增量、ZIP 和离线导入导出的数据范围,支持恢复预览、仅补缺失或覆盖同名文件;下载暂存并校验后再替换,默认保留恢复前副本,提供恢复记录与回滚。完善 OpenList 历史版本、失败重试及 WebDAV 临时上传、成功记录和 ZIP 保留策略;WebDAV 启动恢复与本地删除同步到云端默认关闭,恢复保留本地独有文件,完成后需重启服务。同步更新中英文备份、播放、配置及接口说明。
- **运行环境要求更新**:Node.js 最低版本调整为 `22.13.0`,以支持 OpenList 使用的内置 SQLite 模块。
- **搜索与凭据表单隔离 (#477 @pokemonred06-creator)**:为搜索、登录及远程连接设置独立表单和自动填充属性,降低搜索内容被误识别为登录凭据的风险;登录成功后清空密码输入框,修复手动搜索 Enter 重复触发及列表搜索输入法组合输入时误操作。
- **VU 表盘与指针优化 (#477 @pokemonred06-creator)**:更新各主题表盘设计、刻度映射及指针响应,调整动态参考电平,避免持续高 RMS 音频导致指针顶格。
@@ -196,7 +259,7 @@
- **Media Session 媒体会话控制、封面渲染与播放恢复稳定性提升 (#432 @pokemonred06-creator)**:
- **播放控制与指令幂等执行**: 修复系统媒体控制栏(Media Session)的播放/暂停指令未比对当前播放状态导致的逻辑反转问题,确保系统与锁屏快捷指令严格幂等。
- **意外暂停后的错误恢复守护**: 保留系统意外打断(如来电、系统音频中断)时的错误恢复监听,确保用户在恢复播放时重新挂载错误恢复能力,避免状态丢失。
- - **Media Session 封面与车载渲染规范化**: 通过内建下载代理中转跨域封面,避免 Canvas 污染与跨域访问失败;统一在离屏 Canvas 居中缩放为标准 512×512 JPEG 位图(失败时优雅回退),防止尺寸或格式不符导致蓝牙车机或锁屏黑屏;增加版本锁与内存缓存,杜绝切歌时旧封面异步覆盖新歌曲,并补充专辑名 (`song.meta.albumName`) 读取。
+ - **Media Session 封面与车载渲染规范化**: 通过内建下载代理中转跨域封面,避免 Canvas 污染与跨域访问失败;统一在离屏 Canvas 居中缩放为标准 512×512 JPEG 位图(失败时优雅回退),防止尺寸或格式不符导致蓝牙车机或锁屏黑屏;增加版本锁与内存缓存,杜绝切歌时旧封面异步覆盖新歌曲,并补充专辑名 (`song.meta.albumName`) 读取。
- **iOS 后台保活桥接重连与打断恢复**: 在音频图重建(如调节均衡器/音效)时自动重新连接 MediaStreamDestination,避免桥接断开或静音;优化切回前台(`visibilitychange`)时的 AudioContext 状态恢复;捕获并容忍自动播放限制(`NotAllowedError`),保留锚点以便后续用户手势重试。
- **服务端缓存音频流 Range 请求与连接管理优化**: 严格限制只读取缓存目录内合法普通文件,增强路径遍历安全校验;补齐 HEAD 请求支持;完整支持不区分大小写的 `bytes=` 范围头、后缀范围(如 `bytes=-500`)及单边范围,对超出文件大小的请求规范返回 `416 Range Not Satisfiable`;并在客户端断开或读取出错时及时销毁读取流并释放文件描述符。
diff --git a/docker-compose.yml b/docker-compose.yml
index 53edb212..ceba9e40 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3.8'
-
services:
lx-sync-server:
build: .
@@ -7,13 +5,30 @@ services:
restart: always
ports:
- "9527:9527"
- # Subsonic 独立监听端口:仅当 subsonic.standalone 启用时按需取消注释以暴露到主机
+ # Subsonic 独立服务端口:仅当 subsonic.standalone 启用时取消注释
# - "4050:4050"
volumes:
- # 数据持久化
+ # 数据持久化(配置、快照、日志等均落在此目录)
- ./data:/server/data
environment:
- # 环境变量配置,优先级高于 config.js
- NODE_ENV=production
- # 示例:通过环境变量设置用户
+ # 通过环境变量覆盖配置,优先级高于 config.js
+ # 用户:LX_USER_<用户名>=<密码 或 JSON 配置>
# - LX_USER_myuser=mypassword
+ # 管理员密码:覆盖默认 123456(强烈建议设置为强密码)
+ # - ADMIN_PASSWORD=your-strong-password
+ # Subsonic 独立服务
+ # - SUBSONIC_STANDALONE=true
+ # - SUBSONIC_PORT=4050
+ # WebDAV 备份目标(仅填地址与凭据不会启动同步,需显式开启 WEBDAV_ENABLE)
+ # - WEBDAV_ENABLE=true
+ # - WEBDAV_URL=
+ # - WEBDAV_USERNAME=
+ # - WEBDAV_PASSWORD=
+ # 网页播放器鉴权
+ # - ENABLE_WEBPLAYER_AUTH=true
+ # - WEBPLAYER_PASSWORD=<请设置强密码,勿使用默认值>
+ # 其他常用项
+ # - SYNC_INTERVAL=60
+ # - CONFIG_PATH=/server/data/config.js
+ # - SERVER_NAME=My Sync Server
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index c92f1611..db02af2d 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -50,20 +50,23 @@ export default defineConfig({
text: '核心功能',
items: [
{ text: '同步服务器设置', link: '/guide/sync-server' },
- { text: 'Web 播放器指南', link: '/guide/web-player' }
+ { text: 'Subsonic 本地音乐库', link: '/guide/subsonic-local-library' },
+ { text: '播放器、歌词与解析顺序', link: '/guide/web-player' }
]
},
{
text: '配置指南',
items: [
{ text: '配置文件及环境变量', link: '/guide/configuration' },
- { text: '云端备份与恢复', link: '/guide/backup-recovery' }
+ { text: '备份与恢复', link: '/guide/backup-recovery' }
]
},
{
text: 'API 文档',
items: [
- { text: '服务端 API 参考', link: '/api/reference' }
+ { text: '服务端 API 参考', link: '/api/reference' },
+ { text: 'HTTP 路由索引', link: '/api/routes' },
+ { text: 'Subsonic 协议支持', link: '/api/subsonic' }
]
}
],
@@ -123,20 +126,23 @@ export default defineConfig({
text: 'Core Features',
items: [
{ text: 'Sync Server Settings', link: '/en/guide/sync-server' },
- { text: 'Web Player Guide', link: '/en/guide/web-player' }
+ { text: 'Subsonic Local Library', link: '/en/guide/subsonic-local-library' },
+ { text: 'Playback, Lyrics & Sources', link: '/en/guide/web-player' }
]
},
{
text: 'Config Guide',
items: [
{ text: 'Config & Env Vars', link: '/en/guide/configuration' },
- { text: 'Cloud Backup & Recovery', link: '/en/guide/backup-recovery' }
+ { text: 'Backup & Recovery', link: '/en/guide/backup-recovery' }
]
},
{
text: 'API Docs',
items: [
- { text: 'Server API Ref', link: '/en/api/reference' }
+ { text: 'Server API Ref', link: '/en/api/reference' },
+ { text: 'HTTP Route Index', link: '/en/api/routes' },
+ { text: 'Subsonic Protocol Support', link: '/en/api/subsonic' }
]
}
],
diff --git a/docs/api/reference.md b/docs/api/reference.md
index 73955522..2d4f36f7 100644
--- a/docs/api/reference.md
+++ b/docs/api/reference.md
@@ -336,4 +336,128 @@ GET /api/music/search?name=周杰伦&source=kw&page=2&limit=20
- `POST /api/webdav/backup`、`GET /api/webdav/backups`、`DELETE /api/webdav/backup`、`POST /api/webdav/restore`:远程备份管理。
- `GET /api/webdav/logs`、`GET /api/webdav/progress`:同步日志与 SSE 进度。
-Subsonic 的协议范围与本地库行为另见 [本地音乐库说明](../subsonic-local-library.md) 和 [协议状态](../subsonic-status.md)。
+### 评分与不喜欢联动 {#rating-api}
+
+| 方法 / 路径 | 认证 | 请求与返回要点 |
+| --- | --- | --- |
+| `GET /api/music/ratings` | 用户认证 | 返回 `version,pending,ratings,origins,songs`,只读取当前账号 |
+| `POST /api/music/ratings` | 用户认证 | 默认写入 `song` 与整数 `rating`(0–5,0 清除);返回保存结果,可带 `warning`,警告不代表评分未保存 |
+| `GET /api/music/rating-preferences` | 用户认证 | 返回 `dislikeAutoRating`,默认 false |
+| `POST /api/music/rating-preferences` | 用户认证 | JSON `dislikeAutoRating` 必须是布尔值;保存后返回设置 |
+| `GET /api/data/rating-sync?user=<账号>` | 管理员认证 | 返回该账号的 `pending,version` |
+| `POST /api/data/rating-sync?user=<账号>` | 管理员认证 | 重试该账号待处理的评分同步任务 |
+| `POST /api/subsonic/sync-link` | 管理员认证 | JSON `user,direction,preview,reconcile`,执行或预览评分与不喜欢规则同步 |
+
+`POST /api/music/ratings` 也接受 `operation`:`version` 查询版本,`retryFailed` 重试任务,`exportBackup` 导出评分备份,`resolve` 批量解析最多 100 个 `songs`,可用 `includeDetails=true` 请求详情。各操作的响应结构不同,不统一包装为 `success`。
+
+`sync-link` 的 `direction` 必须为 `ratingToDislike` 或 `dislikeToRating`,前者要求启用并保存 `subsonic.linkRatingToDislike`。建议显式使用 `preview:true` 检查候选,再明确执行:普通同步仅在 `preview===true` 时预览;`ratingToDislike` 且 `reconcile:true` 时默认预览,只有 `preview:false` 执行。该端点的 HTTP 400 错误体可为纯文本,不能假定为 JSON。
+
+### OpenList 连接状态 {#openlist-connection}
+
+`GET /api/openlist/connection` 使用管理员认证,读取已记录的连接状态;`POST` 使用已保存配置主动探测。成功返回 `{ success: true, connection }`。POST 探测在线时还会在后台触发待检查记录核验,不是无副作用的查询。连接状态与历史文件内容校验结果是不同维度;探测失败返回 HTTP 500 与 `error`,其他方法返回 405。
+
+### 仪表盘概览 {#dashboard-overview}
+`GET /api/dashboard/overview` 使用 `x-frontend-auth: <管理员密码>`,无需查询参数,认证失败返回 HTTP 401。成功响应为 JSON,并设置 `Cache-Control: no-store`。
+
+| 响应字段 | 含义 |
+| --- | --- |
+| `success` | 成功时为 `true` |
+| `logs.totals` | 最近 24 小时窗口内的 `info`、`warn`、`error` 计数 |
+| `logs.buckets` | 最近 12 个滚动小时桶,含毫秒时间戳 `t` 与各级别计数 |
+| `logs.recent` | 最多 8 条近期警告/错误,按新到旧返回;含 `time`、`level`、`msg`,消息最多 200 字符 |
+| `disk` | 数据目录所在文件系统的 `total`、`free`,单位字节;无法读取时为 `null` |
+| `heap` | `used` 为进程 RSS,`total` 为系统总内存,`external` 为进程外部内存,均为字节;并非 V8 堆使用量与堆上限 |
+| `services` | WebDAV、OpenList、公共收藏的启用配置,以及恢复维护状态;不是远端连通性检查 |
+| `node`、`pid` | Node.js 版本与进程 ID |
+
+日志只读取 `app.log` 末尾最多 512 KiB 后按时间筛选,不读取轮转文件。高日志量时统计可能不覆盖完整 24 小时;日志不可读取或格式不匹配时,可返回空统计,不能据此判断没有发生错误。小时桶按请求时刻向前滚动,不对齐整点。
+
+Subsonic 的协议范围与本地库行为另见 [本地音乐库说明](../guide/subsonic-local-library.md) 和 [协议支持](./subsonic.md)。
+
+## 备份与恢复接口 {#backup-api}
+
+本节用于脚本集成;后台操作步骤见 [备份与恢复](../guide/backup-recovery.md)。这些管理接口使用 `x-frontend-auth: <管理员密码>`,JSON 请求使用 `Content-Type: application/json`。用户 Token 不能代替管理员密码。不要只按 HTTP 200 判断成功,还要检查 `success`、`errors` 及各接口的 `message`/`error`。
+
+### 本地导入导出与配置历史
+
+| 方法 / 路径 | 请求与返回要点 |
+| --- | --- |
+| `GET /api/backup/download` | 返回 ZIP;使用已保存的 WebDAV 类型范围,无需启用 WebDAV,不是无条件全目录导出 |
+| `POST /api/backup/upload` | multipart 文件字段 `backup`(也兼容 `file`);按当前范围恢复本地 ZIP,不使用下面的 WebDAV 预览 token |
+| `GET /api/config/backups` | `success,list,autoBackupEnabled,retentionDays,intervalHours,scheduleTime,nextBackupAt,lastBackupAt`;时间为毫秒时间戳,0 表示暂无 |
+| `POST /api/config/backup-now` | 立即保存配置副本,返回 `success,filename`,失败可返回 `error` |
+| `GET /api/config/backups/download?file=<文件名>` | 下载指定配置副本 |
+| `POST /api/config/backups/restore` | JSON `fileName`;恢复配置历史,不等同于数据 ZIP 恢复 |
+| `DELETE /api/config/backups/<文件名>` | 删除指定本地配置副本 |
+
+### WebDAV
+
+| 方法 / 路径 | 请求与行为 |
+| --- | --- |
+| `POST /api/webdav/test` | 使用已保存配置,只检查目录读取;返回 `success,message,checks`,不验证写入/删除能力 |
+| `POST /api/webdav/sync` | 上传所选类型的本地文件,不做双向合并 |
+| `POST /api/webdav/backup` | JSON 可选 `force`,默认 `false`;创建并上传 ZIP |
+| `GET /api/webdav/backups` | 返回 `success,backups`;条目含 `filename,basename,size,time,timeStr` |
+| `DELETE /api/webdav/backup` | JSON `filename`,必须位于配置的 ZIP 目录;不是查询参数 `file` |
+| `POST /api/webdav/restore` | 先预览,再使用返回的 token 确认,详见下例 |
+| `GET /api/webdav/restore/tasks` | 返回 `success,tasks`;含状态、下载/写入数量、错误与 `canRollback` |
+| `POST /api/webdav/restore/tasks//rollback` | 回退保留的恢复前副本;拒绝覆盖恢复后的新修改 |
+| `DELETE /api/webdav/restore/tasks/` | 删除本地任务及副本,不删除云端备份;运行中/待故障处理任务拒绝删除 |
+| `GET /api/webdav/logs` | 同步日志 |
+| `GET /api/webdav/progress` | SSE;也支持查询参数 `auth`,避免在可分享链接中携带凭据 |
+
+预览请求:
+
+```json
+{
+ "preview": true,
+ "mode": "files",
+ "strategy": "missing",
+ "keepCopies": true
+}
+```
+
+- `mode`:`files`(增量目录)、`zip`、`auto`(默认,目录无所选文件或不存在时尝试 ZIP)。
+- `targetFilename`:ZIP 模式下可指定备份列表中的文件名,省略选择最新 ZIP。
+- `strategy`:`missing`(仅补缺失)或 `overwrite`(覆盖同名);API 默认 **overwrite**,不要省略后误以为是仅补缺失。
+- `keepCopies`:默认 `true`,保留恢复前副本。
+- 返回包含 `success,token,source,total,added,overwritten,merged,skipped,bytes,warnings,files`。`bytes` 是计划写入文件的字节数;ZIP 预览需要下载完整压缩包,不应将此值当作预览流量。
+
+确认时仅发送 `{"token":"预览返回值"}`;取消发送 `{"cancel":true,"token":"预览返回值"}`。预览有效期 15 分钟,配置或本地文件变化需重新预览。执行返回包含 `success,taskId,restored,backupDir,errors,restartRequired,message`(失败早于创建任务时部分字段不存在)。手动恢复需要维护状态与重启;跨文件提交不是文件系统原子事务。
+
+`POST /api/webdav/sync-file` 是单文件兼容入口,JSON 为 `path,action`,`action` 为 `upload` 或 `download`。它不具备整批恢复的预览、事务及回退记录;常规恢复应使用上述预览流程。
+
+### OpenList
+
+| 方法 / 路径 | 请求与行为 |
+| --- | --- |
+| `POST /api/openlist/test` | 可传 `endpoint,username,password,token,root`,未传使用已保存配置;只读测试,不保存 |
+| `POST /api/openlist/sync` | 可选 `user,folder`;备份并执行已启用用户缓存规则,返回 `success,summary` |
+| `GET /api/openlist/backups` | 返回 `success,list`;常规上传状态不等于完整内容校验结果 |
+| `GET /api/openlist/metadata` | 当前轻量检查任务、当前连接的主文件记录数量 |
+| `POST /api/openlist/metadata/start` | JSON `target` 为 `local`、`remote` 或 `both`;可传非空整数数组 `ids`,省略时检查当前连接全部记录。后台只读检查元信息,不传输正文、不删除文件 |
+| `POST /api/openlist/metadata/cancel` | 停止轻量检查,保留已完成观察结果 |
+| `POST /api/openlist/retry` | JSON `id` |
+| `POST /api/openlist/retry-batch` | JSON `ids` |
+| `POST /api/openlist/delete` | JSON `ids`;显式删除操作可能删除远端文件,返回 `deleted,remoteRemoved,remoteFailed` |
+| `GET /api/openlist/restore/history` | 可恢复的云端历史版本 |
+| `POST /api/openlist/restore` | 扫描预览:`preview:true,mode,types,backupBeforeRestore,snapshotId`;返回 `planToken` 及数量、文件清单、警告 |
+| `POST /api/openlist/restore/jobs/start` | 提交相同恢复参数和 `planToken`,返回 `success,taskId`,任务后台运行 |
+| `GET /api/openlist/restore/jobs` | 恢复任务列表及 `maintenance` 状态 |
+| `POST /api/openlist/restore/jobs//cancel` | 停止任务 |
+| `POST /api/openlist/restore/jobs//resume` | 继续可恢复任务;提交中断需先处理回退 |
+| `POST /api/openlist/restore/jobs//rollback` | 回退本次恢复,返回 `restartRequired` |
+| `DELETE /api/openlist/restore/jobs/` | 删除任务与本地副本 |
+| `POST /api/openlist/integrity/preview` | JSON 可选 `type,user,scope`;scope 为 `changed`(默认)、`all` 或 `problems`,返回 `token,total,bytes,unknown` |
+| `POST /api/openlist/integrity/start` | JSON `token`,启动手动内容校验 |
+| `GET /api/openlist/integrity` | 当前校验任务和结果 |
+| `POST /api/openlist/integrity/cancel` | 停止内容校验 |
+
+OpenList 恢复的 `mode` 是 `missing` 或 `overwrite`(默认),与 WebDAV 的来源 `mode` 含义不同。`types` 可选,使用五类数据标识;未传使用已保存范围。`snapshotId` 省略恢复当前备份,指定时使用历史版本;`backupBeforeRestore` 默认 `true`。WebDAV 的 `token`、OpenList 恢复的 `planToken`、内容校验的 `token` 互不通用。
+
+维护期间可读取状态/配置及访问恢复管理、回退和重启入口;普通业务接口可能返回 503。重启后再继续备份或播放。内容校验可能下载文件正文,`bytes` 为最大正文读取估算,`unknown` 为缺少上传指纹的文件数;校验不会自动修复文件。
+
+
+### 备份概览状态
+
+`GET /api/backup/status` 使用 `x-frontend-auth` 鉴权。返回 `success,maintenance,webdav,openlist`,未启用的服务返回 null。WebDAV 包含 `running,restoring,nextSyncAt,nextZipAt,lastActivity,lastError`;OpenList 包含 `running,checking,nextSyncAt,nextCheckAt,counts,lastRun,lastRecordAt,lastError`。时间为毫秒时间戳,0 表示未安排。下一次时间是调度检查时间,不保证届时一定传输文件。运行结果摘要在进程重启后重新记录,持久化文件记录与整轮执行结果分开呈现。此接口不读取云端文件正文;未启用的服务在界面隐藏详情与执行按钮。
diff --git a/docs/api/routes.md b/docs/api/routes.md
index 22764064..681a8e55 100644
--- a/docs/api/routes.md
+++ b/docs/api/routes.md
@@ -16,6 +16,33 @@
| --- | --- | --- |
| GET | `/api/status` | — |
+## dashboard
+
+| 方法 | 路径 | 直接读取字段(部分) |
+| --- | --- | --- |
+| GET | `/api/dashboard/overview` | — |
+
+## ratings
+
+| 方法 | 路径 | 直接读取字段(部分) |
+| --- | --- | --- |
+| POST | `/api/subsonic/sync-link` | `user`, `direction`, `preview`, `reconcile` |
+| GET / POST | `/api/data/rating-sync` | 查询参数 `user` |
+| GET / POST | `/api/music/rating-preferences` | `dislikeAutoRating` |
+| GET / POST | `/api/music/ratings` | `operation`, `songs`, `includeDetails`, `song`, `rating` |
+
+## openlist/connection
+
+| 方法 | 路径 | 直接读取字段(部分) |
+| --- | --- | --- |
+| GET / POST | `/api/openlist/connection` | — |
+
+## files
+
+| 方法 | 路径 | 直接读取字段(部分) |
+| --- | --- | --- |
+| GET / HEAD | `/api/files` | 查询参数 `path`;委托 VueFinder,详见 API 参考 |
+
## users
| 方法 | 路径 | 直接读取字段(部分) |
@@ -518,7 +545,7 @@
| 方法 | 路径 | 直接读取字段(部分) |
| --- | --- | --- |
-| POST | `/api/webdav/restore` | — |
+| POST | `/api/webdav/restore` | `preview,mode,targetFilename,strategy,keepCopies,token,cancel` |
## webdav/logs
@@ -578,3 +605,36 @@
| POST | `/api/music/remaster/start` | — |
| GET | `/api/music/remaster/status` | — |
| POST | `/api/music/remaster/cancel` | — |
+
+## 备份恢复任务与内容校验
+
+鉴权、完整请求参数及执行顺序见 [备份接口说明](./reference.md#backup-api)。
+
+| 方法 | 路径 | 主要字段 / 用途 |
+| --- | --- | --- |
+| GET | `/api/webdav/restore/tasks` | `tasks` |
+| POST | `/api/webdav/restore/tasks//rollback` | `id` |
+| DELETE | `/api/webdav/restore/tasks/` | `id` |
+| POST | `/api/openlist/test` | `endpoint,username,password,token,root` |
+| POST | `/api/openlist/sync` | `user,folder` |
+| GET | `/api/openlist/backups` | `list` |
+| GET | `/api/openlist/metadata` | `job,total` |
+| POST | `/api/openlist/metadata/start` | `target,ids?` |
+| POST | `/api/openlist/metadata/cancel` | `—` |
+| POST | `/api/openlist/retry` | `id` |
+| POST | `/api/openlist/retry-batch` | `ids` |
+| POST | `/api/openlist/delete` | `ids` |
+| POST | `/api/openlist/restore` | `preview,mode,types,backupBeforeRestore,snapshotId,planToken` |
+| GET | `/api/openlist/restore/history` | `history` |
+| GET | `/api/openlist/restore/jobs` | `tasks,maintenance` |
+| POST | `/api/openlist/restore/jobs/start` | `planToken,mode,types,backupBeforeRestore,snapshotId` |
+| POST | `/api/openlist/restore/jobs//cancel` | `id` |
+| POST | `/api/openlist/restore/jobs//resume` | `id` |
+| POST | `/api/openlist/restore/jobs//rollback` | `id` |
+| DELETE | `/api/openlist/restore/jobs/` | `id` |
+| GET | `/api/openlist/integrity` | `job,results` |
+| POST | `/api/openlist/integrity/preview` | `type,user,scope` |
+| POST | `/api/openlist/integrity/start` | `token` |
+| POST | `/api/openlist/integrity/cancel` | `—` |
+
+| GET | `/api/backup/status` | `success,maintenance,webdav,openlist` |
diff --git a/docs/api/subsonic.md b/docs/api/subsonic.md
new file mode 100644
index 00000000..cc725dd5
--- /dev/null
+++ b/docs/api/subsonic.md
@@ -0,0 +1,109 @@
+# Subsonic 协议支持
+
+本服务实现 Subsonic / OpenSubsonic 接口,用于将 LX 歌单、在线音源和账号本地曲库提供给兼容客户端。接口行为由服务端配置、音源可用性及客户端能力共同决定。本页按功能说明支持范围,不代表所有客户端提供相同界面。
+
+本地文件目录、身份和歌单持久化见 [Subsonic 本地音乐库](../guide/subsonic-local-library.md),连接设置见 [同步服务器设置](../guide/sync-server.md)。
+
+## 接入方式
+
+默认协议路径为 `/rest`,与主服务共用端口;路径可通过 `subsonic.path` 调整。使用独立端口时,必须同时启用 `subsonic.standalone` 并设置有效的 `subsonic.port`,只设置端口不会启用独立监听。
+
+使用用户账号的同步密码认证,不使用管理密码。接口支持 JSON / XML;以下路径中的参数仅示意,正式客户端应提供协议认证和版本等参数。
+
+```text
+GET /rest/ping.view?u=&t=&s=&v=1.16.1&c=&f=json
+```
+
+## 支持的接口
+
+### 系统、浏览与搜索
+
+| 方法 | 行为与限制 |
+| --- | --- |
+| `ping`、`getLicense`、`getUser` | 连接检查、许可信息和当前用户信息 |
+| `getMusicFolders` | 返回服务器设置入口、NAS 本地音乐及在线平台入口 |
+| `getMusicDirectory` | 目录、专辑、歌单和本地音乐入口 |
+| `getIndexes`、`getArtists`、`getArtistList` | 艺术家索引;`getArtistList` 为兼容入口 |
+| `getArtist`、`getArtistInfo`、`getArtistInfo2` | 艺术家详情和信息 |
+| `getAlbum`、`getAlbumInfo`、`getAlbumInfo2` | 专辑歌曲、介绍及封面信息 |
+| `getAlbumList`、`getAlbumList2` | 专辑列表、推荐和最近播放等;指定 NAS 文件夹时使用本地分支 |
+| `getGenres` | 在线或本地曲库的流派信息 |
+| `search`、`search2`、`search3` | 根据在线搜索设置和查询前缀选择数据来源 |
+| `startScan`、`getScanStatus` | 刷新本地索引,返回扫描状态、数量和失败原因;不是固定占位状态 |
+
+在线搜索使用 `subsonic.onlineSearch` 与 `subsonic.onlineSearchMode`:`fallback` 在本地结果不足时补充在线结果,`merge` 合并结果,`local_only` 只查本地。可使用 `local:`、`online:` 或平台前缀限定范围;在线播放仍需要可用的自定义音源。
+
+### 媒体、歌单与收藏
+
+| 方法 | 行为与限制 |
+| --- | --- |
+| `getSong` | 在线或本地歌曲信息 |
+| `stream`、`download` | 音频读取;本地文件支持 Range,在线资源依赖音源解析 |
+| `getCoverArt` | 歌曲、专辑及歌单封面,支持 `size` 参数 |
+| `getLyrics`、`getLyricsBySongId` | 普通及结构化歌词;来源和逐字歌词限制见本地音乐库说明 |
+| `getPlaylists`、`getPlaylist` | 用户、共享及已启用的只读本地歌单 |
+| `createPlaylist`、`updatePlaylist`、`deletePlaylist` | 用户歌单管理;内置和共享歌单有写入限制 |
+| `getStarred`、`getStarred2`、`star`、`unstar` | 查询及设置收藏 |
+| `setRating` | 按用户保存 0–5 分;0 表示清除评分 |
+
+纯本地与混合歌单的同步规则、删除行为和备份文件见 [收藏与歌单同步](../guide/subsonic-local-library.md#收藏与歌单同步)。
+
+### 发现与电台
+
+| 方法 | 行为与限制 |
+| --- | --- |
+| `getRandomSongs`、`getSongsByGenre`、`getSongsByGenre2` | 随机或按流派返回歌曲,支持本地库分支 |
+| `getSimilarSongs`、`getSimilarSongs2`、`getSonicSimilarTracks` | 基于同歌手等条件挑选相似歌曲,不进行声学分析 |
+| `getTopSongs` | 艺术家热门歌曲 |
+| `getRecommendedSongs`、`getDailySongs`、`getSongsByTag` | 服务端推荐入口;后两者为别名,`getSongsByTag` 不执行独立标签检索 |
+| `getInternetRadioStations` | 返回可用的官方电台、用户电台及音源歌单电台 |
+| `createInternetRadioStation`、`updateInternetRadioStation`、`deleteInternetRadioStation` | 用户电台管理;另接受复数形式的删除兼容入口 |
+
+每日推荐从在线推荐专辑获取歌曲并去重,同一天使用稳定的洗牌顺序。上游不可用时可回退到上次成功结果。返回前按账号过滤落入“不喜欢”评分区间的歌曲:`0 < rating <= subsonic.dislikeRating`,默认阈值为 1。
+
+服务端生成的电台流地址使用绝对 URL 和限时票据,不直接携带长期认证凭据;用户自建的外部电台地址按配置返回。反向代理应正确传递协议等信息,且客户端必须能访问这些地址。上游官方电台不可用时会被暂时隐藏。
+
+### 播放活动、队列与书签
+
+| 方法 | 行为与限制 |
+| --- | --- |
+| `scrobble` | 保存请求中的歌曲播放记录,接受播放时间参数 |
+| `reportPlayback` | 更新播放状态和位置;仅 `stopped` 且未设置 `ignoreScrobble=true` 时记录播放历史 |
+| `getNowPlaying` | 返回近期播放活动,可附带位置;并非对所有客户端的实时播放状态监测 |
+| `getPlayQueue`、`savePlayQueue` | 按用户持久化播放队列 |
+| `getPlayQueueByIndex`、`savePlayQueueByIndex` | 按索引读写队列;当前索引越界返回参数错误 |
+| `getBookmarks`、`createBookmark`、`deleteBookmark` | 按用户持久化书签 |
+
+## 转码与媒体限制
+
+| 配置项 | 默认值 | 用途 |
+| --- | --- | --- |
+| `subsonic.transcode.enabled` | `false` | 服务端转码总开关 |
+| `subsonic.transcode.format` | `mp3` | 目标格式,可选 `mp3`、`opus`、`aac` |
+| `subsonic.transcode.maxConcurrent` | `2` | 同时转码任务上限 |
+
+转码需要可用的 ffmpeg。`stream` / `download` 可结合客户端码率要求选择转码;`timeOffset` 使用秒。OpenSubsonic 的 `getTranscodeDecision` / `getTranscodeStream` 提供转码决策和签名参数。
+
+ffmpeg 不可用时在线资源可降级为原始链接,不能保证原始格式符合客户端要求。目标格式、音源可用性和服务器资源均会影响播放结果。
+
+## OpenSubsonic 扩展
+
+`getOpenSubsonicExtensions` 声明以下扩展,版本均为 1:
+
+| 扩展 | 对应能力 |
+| --- | --- |
+| `formPost` | 表单 POST 参数 |
+| `songLyrics` | 结构化歌词 |
+| `playbackReport` | 播放状态与位置上报 |
+| `sonicSimilarity` | 相似曲目入口,使用启发式挑选 |
+| `indexBasedQueue` | 按索引管理播放队列 |
+| `transcodeOffset` | 转码时间偏移 |
+| `transcoding` | 转码决策与转码流 |
+
+声明扩展表示接口存在,客户端是否调用和展示仍取决于其实现。
+
+## 不支持的范围
+
+视频、播客、聊天、分享,以及通过 Subsonic 接口创建或修改服务器账号等功能不在当前实现范围。典型未支持方法包括 `getVideos`、`getPodcasts`、`getChatMessages`、`createShare`、`createUser`、`updateUser`、`deleteUser`、`changePassword` 和 `getAvatar`。
+
+未支持的方法返回 `Method not found`。账号管理应使用后台管理页;客户端出现缺失控件或兼容问题时,应先核对本页能力范围与该客户端实际请求的方法。
diff --git a/docs/chromecast/architecture.md b/docs/chromecast/architecture.md
deleted file mode 100644
index 35611518..00000000
--- a/docs/chromecast/architecture.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Cast architecture
-
-`public/music/js/player/playback.js` adapts the native audio element behind the player's existing media properties, methods and events. Local media events pass through. While casting, the adapter routes controls to the remote output and suppresses native events, preventing duplicate playback and false local recovery. Other audio modules retain the real DOM element.
-
-`chromecast.js` loads Google's official Web Sender SDK in supported secure browser contexts and uses the Default Media Receiver; no custom application registration or server dependency is required. RemotePlayer status drives transport events, position and volume. LOAD commands are serialized and versioned so stale preparation cannot replace a newer track. A seek made during LOAD is applied after it completes; local stall timers cannot recover across a route change. Pending play/pause requests both take effect after LOAD. Cast route changes synchronize lyric speed; local startup restoration yields to resumed remote sessions. Standard/local artwork is resolved through the shared image helper and retained in session metadata. Completion is delivered once. Session resumption adopts existing media without another LOAD. The shared controls remain available on mobile layouts. Equal side columns keep the transport centered, with repeat on the left and speed/wireless controls on the right. Local crossfade is suspended while casting, including an in-progress fade, to preserve receiver volume.
-
-`src/server/integrations/castAccess.ts` issues opaque, random capabilities:
-
-- `POST /api/music/cast/session`: existing player/account authentication, `{url, mime}`; returns a resource-scoped URL and expiry.
-- `GET /api/music/cast/session?id=…`: authenticated issuing owner can recover the normalized source for resumption.
-- `DELETE /api/music/cast/session?id=…`: authenticated owner revokes a capability.
-- `GET/HEAD /api/music/cast/media?id=…`: verifies expiry and the original credentials, then internally routes the immutable target through existing music handlers. Receiver-supplied URLs and authentication headers cannot change its scope.
-
-Capabilities live only in bounded process memory (two hours, sixteen per owner, 1,024 total) and are revoked after track replacement/disconnection. Receiver URLs contain no account credential. Internal music targets are allowlisted; external URLs use the existing public-address validation, pinned DNS connection and redirect validation. Cached/custom media retains its usual ownership/containment rules. Tagging/mutation parameters are excluded from proxy targets. CORS, exposed range headers, no-store responses and HEAD support permit receiver reads and seeking.
-
-Native same-origin audio, artwork and browser downloads use the HttpOnly player session cookie (Secure on HTTPS); the player removes account query tokens from these URLs. Cookie fallback is confined to read-only media handlers and validates the account and session lifetime. Legacy API query-token handling remains for compatibility, but the player does not generate it. API mutations still require their existing header authentication. Local sound-effect changes are saved during wireless playback without building or updating a local graph.
-
-The current combined submission caches first-party modules under `lx-music-web-v51`. Google's SDK stays network-loaded. SDK failure or unsupported browsers leave local playback available.
-
-References: [Google Web Sender setup](https://developers.google.com/cast/docs/web_sender), [integration](https://developers.google.com/cast/docs/web_sender/integrate), [supported media](https://developers.google.com/cast/docs/media).
-
-The combined deployment profile retains its existing custom VU themes and pass-through audio policy: stored crossfade is disabled and direct volume updates do not affect a remote receiver. Transient preload failures remain retryable during foreground playback.
-
-The combined profile, including the user's earlier VU/audio/login customizations, is now included in the upstream submission. `/js/config.js` reads the running package version independently of generated frontend metadata; `scripts/update-build-hash.js` synchronizes the public version and source fingerprint during builds/starts. Player and About share that identity and never infer a release number from unavailable metadata.
-
-Cookie recovery first checks native media access without API headers, then restores the cookie through the existing Token-authenticated verification endpoint. Password renewal is only attempted when a saved password exists; Token-only credentials remain intact. Login supports explicit account and player-password modes, with separate guest credential storage and matching automatic restoration. Once a desktop effects graph captures audio, viewport resizing does not disable its controls; wireless routes still bypass local edits.
-
-Token verification returns the session owner and upgrades guest or other-account cookies to the authenticated account before native media. Existing-Token fast paths also verify this binding. Cast quota ownership uses only a validated, unexpired stored session or a verified account; unknown cookies share the public quota. Playlist card metadata is escaped in both layouts, with JSON arguments escaped for inline click attributes.
-
-Mobile loads remain native until an explicit local effect edit opts into DSP. The panel reports native, local-processed or receiver output. After mobile capture the in-page AirPlay picker directs the user through a native reload, because disconnecting Web Audio does not release a captured media element. That reload preserves queue/position with a one-time session flag without changing the auto-resume preference. Existing wireless/Cast edits remain saved-only.
diff --git a/docs/chromecast/progress.md b/docs/chromecast/progress.md
deleted file mode 100644
index 46609a75..00000000
--- a/docs/chromecast/progress.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Chromecast progress
-
-## Complete customization submission and version identity — 2026-10-20
-
-### Expanded review before deployment
-
-- [x] Final thread audit found three older unresolved findings: unescaped playlist metadata/click arguments, account Tokens retaining a guest media cookie, and Cast quotas using unvalidated cookie IDs. Reproduced nine cases against `6a48885` and corrected them. Existing Token/login paths now bind the matching account cookie before media; account verification promotes guest/other-account sessions; playlist row/grid templates escape remote metadata and safely encode click arguments; Cast ownership accepts only live stored sessions.
-- [x] Follow-up passes all159tests (147CJS+12TS), type/build/JS/diff and53 compiled-server HTTP checks, including actual guest-to-account cookie promotion and private media reads. Follow-up fingerprint `9925f04`, worker51.
-- [x] Deploy follow-up `4ebc5bc` over the verified prior image: 53 isolated NAS/52 safe live HTTP checks, 264 file hashes, 12 session/10 effects/5 login regressions per image, eleven authenticated HTTPS assets and five initialized sources pass. Six existing files updated/258 preserved; configuration/environment/six mounts and all51other containers unchanged. Protected stopped-state rollback evidence retained; candidate/clone removed. Current deployment is worker51/build `9925f04`, superseding the first successful worker50 deployment below. The six corrected review threads are resolved; official merge remains separate.
-
-- [x] Reproduce token-only cookie recovery failure, loss of a valid Token without a saved password, touch-desktop resize loss of DSP control, and missing player-password login/restore behavior.
-- [x] Restore a native media cookie through the existing Token-authenticated verification endpoint before considering password renewal; retain Token-only credentials. Keep an already established desktop effects graph controllable after resize while still yielding to AirPlay/Cast. Restore explicit account/player-password login modes, mode-aware persistence and automatic recovery without a username for guests.
-- [x] Pass all 149 tests (137 CJS + 12 TypeScript), type/build/JS/diff checks and 50 isolated compiled-server HTTP checks. Repin version `v2.1.3`, fingerprint `93301ce`, worker v50; the earlier v49 artifact is superseded before deployment.
-
-- [x] Include all remaining tracked customizations in the official submission while preserving the user's earlier commit history: VU themes/geometry/stereo routing, native/pass-through audio policy, compact login, search compatibility, quality badges, foreground preload retry and the combined conflict fixes.
-- [x] Include the subsequent QQ/Safari MIME normalization, mobile/wireless effects guards, playback-session recovery, credential-log removal and closed-drawer animation optimization.
-- [x] Replace fabricated Player/About/runtime-config v1.0.0 fallbacks. Read the running package version, synchronize generated version metadata at build/start, and show the source fingerprint beside the Player version. About uses the same metadata.
-- [x] Add regressions for stale/missing metadata, reproducible build identities, source/version changes, matching Player/About labels and escaped About substitutions; refresh worker v49.
-- [x] Pass all 141 tests (129 CJS + 12 TypeScript), TypeScript checking, production compilation, JS syntax and diff checks. The compiled server passes 48 isolated HTTP checks using disposable account/data/configuration, including version/fingerprint agreement, login, media ranges and Cast access. No configured external provider or physical receiver was used in this run.
-- [x] Update official PR #451 with the complete profile, original customization history and current results; code checkpoint `f3535de`. The expanded PR contains 59 changed files. Its new automated review is pending and is separate from the previously resolved review threads.
-- [x] Prepare a pinned six-file release overlay plus full before/after manifests, verification scripts and rollback handoff; schedule a quiet 30-minute connectivity retry that stops after verified deployment.
-- [x] Deploy reviewed code `6a48885`, fingerprint `93301ce`, worker v50 after connectivity returned. All 263 pre-deployment hashes matched; the isolated NAS candidate passed 50 HTTP checks, all 264 resulting hashes, 12 session/10 effects/4 login regressions and initialization of all five sources. Back up current data/configuration and recreate only LX; live verification passed 49 safe HTTP checks, the same hashes/regressions, eleven authenticated HTTPS assets and five initialized sources. Configuration bytes, environment, six mounts and all 51 unrelated containers were preserved; the candidate/clone were removed and rollback evidence retained. No new physical receiver, Windows or iPhone test is claimed.
-
-The dated sections below record earlier verification and the historical separation of public/custom profiles. This submission combines them; earlier hardware results are not a fresh physical receiver test of the new build. The user's pre-existing customizations retain their original commits and attribution.
-
-- [x] Review existing playback, authentication and Google Web Sender integration.
-- [x] Implement mobile-accessible Cast controls, remote transport and local handoff.
-- [x] Implement expiring resource capabilities without sharing general login credentials.
-- [x] Preserve native Safari/AirPlay and existing player behavior.
-- [x] Keep transport centered with equal side columns; group speed and wireless controls on the right.
-- [x] Integrate on the latest upstream `dev`, retaining its HTTP cookie, metadata, playback recovery and synchronization fixes.
-- [x] Prevent local crossfade from modifying Cast volume, including fades already running during handoff.
-- [x] Pass 76 automated checks, TypeScript checking and production compilation.
-- [x] Start the compiled server with isolated temporary data; verify HTTP cookie login, authenticated player markup, Cast assets and worker version.
-
-An earlier compatible deployment passed 35 isolated/live runtime checks, 30 existing metadata/artwork checks and release asset verification. Desktop Chrome and an Onkyo TX-RZ50 accepted MP3 and reported advancing playback, pause, seek and disconnect; local audio stayed paused while casting. Responsive controls were checked at desktop and 320px widths with zero center offset and no control overlap. These hardware/layout checks describe that deployed version; the latest upstream integration has automated and isolated-startup coverage, not another physical receiver run.
-
-Live validation also reproduced and fixed playlist-category escaping-helper shadowing and a batch-module export referring to a function defined by a later script. Regression tests cover both startup/rendering failures.
-
-Physical Android, other receiver/codec combinations, independent audible monitoring and iOS lock-screen/DSP transitions remain separate manual checks.
-
-## Combined deployment profile
-
-- [x] Merge the upstream integration with the existing customization branch, preserving the custom stereo VU themes, search response compatibility, quality badges, pass-through volume policy and transient preload retry behavior.
-- [x] Retain lyric-title, composite artist and raster artwork behavior; include the 30 existing metadata regressions in the combined suite.
-- [x] Pass all 82 tests, including saved pass-through settings and transient preload retry regressions; refresh the combined frontend worker to v42.
-- [x] Validate and deploy the combined release: 38 isolated and 38 live runtime checks; 179 release file hashes and 84 retained static asset hashes match; five sources initialize successfully.
-- [x] Verify signed-in HTTPS assets, secure cookies and centered desktop/320px controls; configuration bytes, six mounts and 51 unrelated containers remain unchanged.
-
-The existing customization history includes eleven commits from October 12–13, before the October 19 audit, covering VU themes, audio behavior, search compatibility and badges. These are retained from the user's fork. Commit author identities alone do not distinguish manual work from assistant-generated work.
-
-## Combined-code compatibility audit
-
-- [x] Reproduce and fix nine behavioral conflicts across fixed audio policies, automatic login, sync surfaces, Cast/recovery handoff and custom VU rendering.
-- [x] Pass all 91 tests, type checking and production compilation; retain the existing lyric/artwork, security and playback regressions.
-- [x] Deploy code `6cafc09`: 41 isolated and 41 live runtime checks pass, seven changed and 256 preserved file hashes match, all five sources initialize, and signed-in HTTPS/browser checks pass. Configuration, six mounts and 51 unrelated containers remain unchanged.
-- [x] Port the four shared Cast/recovery/sync corrections to upstream PR #451 (`abd404a`), with all 54 tests and type checks passing.
-
-The compatibility audit (`combined-conflict-audit.md`, not included in this documentation tree) records reproductions, fixes and validation limits. Frontend worker v43 refreshes the corrected assets.
-
-- [x] Follow-up compatibility audit: retain seeks during pending Cast LOAD, reject stale local-recovery timers after route changes, and synchronize unique sync-status views with the correct initiating connection form. Four focused regressions cover these shared fixes.
-
-
-## PR review follow-up
-
-- [x] Verify both automated review batches against the current source; address all twelve distinct findings.
-- [x] Preserve standard/scoped artwork, pending play intent, lyric speed and resumed Cast sessions.
-- [x] Save local EQ/preset/reverb/pitch/panner settings during wireless playback without capturing native audio.
-- [x] Remove generated reusable account tokens from native media/artwork/download URLs; validate read-only account-cookie access and rejection of guest/expired/deleted sessions.
-- [x] Reject empty administrator secrets, revoke credentials before user-space rename/migration, escape playlist statistics/errors, and use a Windows-compatible directory-link fixture.
-- [x] Isolate shared-download cancellation and retry unaffected callers; serialize differing lyric/provenance requirements and use separate temporary paths for retries.
-- [x] Limit proxy timeout to response headers and terminate downstream output on request/response failure or upstream abort.
-- [x] Pass all 76 tests, type checking and production compilation; refresh first-party assets with worker v44.
-- [x] Start the corrected compiled application with disposable data: ten isolated checks pass for login/cookie flags, authenticated player/worker and cookie-only private media authorization. Historical source replay reproduces twenty failures across 59 focused tests; the corrected suite passes.
-
-
-## Reviewed fixes in the customized deployment
-
-- [x] Merge PR review corrections through `2e833cf` with the custom audio/login/VU profile, retaining the previous nine compatibility corrections.
-- [x] Pass all 116 combined tests (104 CJS + 12 TypeScript), type checking and production compilation.
-- [x] Validate the isolated image: 45 runtime checks, all five sources initialized, 11 updated and 252 retained file hashes match.
-- [x] Back up and deploy code `fc98ffc`: 45 live checks and ten authenticated HTTPS asset hashes pass. Configuration bytes, environment values, six mounts and 51 unrelated containers are preserved. Desktop/320px controls are centered without overlap, artwork loads without account query tokens, and playback remains paused at its prior position.
-
-The combined release uses worker v45. The public PR remains a separate profile and is not merged into the official repository by this deployment.
-
-## Safari proxy media compatibility
-
-- [x] Reproduce a CDN response carrying M4A audio with `application/x-www-form-urlencoded`, then normalize generic MIME labels using recognized media extensions on successful proxy responses. Preserve explicit document/error types, failed statuses, byte ranges and upstream address validation.
-- [x] Pass all 116 tests, TypeScript checking and production compilation. The regression fails on the preceding implementation; it covers mislabeled AAC, generic FLAC, explicit JSON and upstream rejection.
-- [x] Deploy the backend correction `e8256ce` on the customized profile: 45 isolated and 45 live runtime checks, one updated and 262 preserved file hashes, ten authenticated HTTPS assets and actual proxied AAC range bytes/MIME pass. Configuration, environment, mounts and unrelated containers remain unchanged; frontend assets/worker v45 are retained.
-
-A provider authorization error is separate from LX account authentication. A successful source response labeled with the requested quality does not establish actual master audio; observed fallbacks included AAC and MP3. This correction repairs media headers and does not grant provider access or establish physical iPhone playback success.
-
-## Effects and native AirPlay playback
-
-- [x] Reproduce five failures involving forced mobile media capture, resets on existing wireless graphs, and pending pitch/reverb/panner work after AirPlay/Cast handoff.
-- [x] Keep mobile audio native even for forced effects initialization; save edits while bypassing local processing on mobile and wireless routes. Guard resets, server-loaded settings and asynchronous DSP work; apply deferred settings to an existing desktop graph on return to local playback. Explain the saved-setting behavior when opening the effects panel.
-- [x] Pass all 124 tests and TypeScript checking. Eight route/effects regressions also pass against the isolated and live image modules; desktop effects remain functional.
-- [x] Deploy `503e2ab` with worker v46: 45 isolated and 45 live runtime checks, two updated/261 retained file hashes and ten authenticated HTTPS asset hashes pass. Prior fixes/customizations and configuration/environment/mounts/unrelated containers are preserved.
-
-Existing pages that already captured the audio element need a full reload to start with native mobile playback. Physical iPhone/AirPlay playback still needs user confirmation; simulated route transitions do not establish receiver behavior.
-
-## Playback authentication recovery
-
-- [x] Reproduce valid API credentials alongside an expired/missing native-media cookie: audio returns 401 while the server-availability endpoint returns HTTP 200 with `valid:false`. The user reported Windows Chrome/all songs affected, then spontaneous recovery before this patch; the exact browser incident remains an inference from the reproduced gap and timing.
-- [x] Renew the API token and media cookie together; retry URL authentication once with refreshed headers. Repair native playback sessions before excluding providers, retain the original track/resume time, reject stale recovery after a track change and show a login prompt when recovery fails. Remove account-password values from startup logging and duplicate-password errors.
-- [x] Pass all 134 tests, type checking and compilation. Each isolated/live module passes ten session regressions and eight retained AirPlay/effects regressions, alongside 45 runtime checks and ten authenticated HTTPS asset hashes.
-- [x] Publish `b3aea76` through atomic file updates with worker v47. Three files updated/260 retained hashes verified; container ID/start/restart count, configuration/environment/mounts and unrelated containers preserved. Compose selects a tested image containing the same files for future recreation; the current process retains its prior base image. Startup-log redaction takes effect on its next start.
-
-QQ search errors were also observed during the reported interval; the investigation does not establish that every provider failed or that the external search service is repaired. Existing tabs load the frontend correction on their next full reload.
-
-## Song-list scrolling profile
-
-- [x] Measure search-list scrolling and isolate animation work with an A/B/A comparison. Closed drawers retained running marquee animations; pausing them reduced style-calculation work by 43–69% across the tested browser states. Total main-thread improvement varied, so these figures are not a guaranteed frame-rate gain.
-- [x] Pause marquee animations only while queue/cache/download drawers are translated closed; preserve their behavior when open. Publish `0521bae` with worker v48 using atomic file updates and retain the current process.
-- [x] Pass all 134 tests, 45 isolated/live runtime checks, ten session/eight effects regressions per image, two updated/261 retained hashes and eleven authenticated HTTPS asset hashes including the stylesheet. Browser checks confirm zero running marquee tracks in the closed queue, normal resumption on open, removed diagnostic overrides and preserved paused position.
-
-The profile used connected macOS Chrome, 20 visible search rows and a 98-track queue; the user reported Windows Chrome search/playlists. Windows hardware/GPU/extensions were not profiled and are not ruled out. NAS load was low at the sampled time. No virtualization or saved page-size setting was changed.
diff --git a/docs/chromecast/requirements.md b/docs/chromecast/requirements.md
deleted file mode 100644
index 364c21f2..00000000
--- a/docs/chromecast/requirements.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Chromecast playback
-
-Use the player in a Cast-enabled desktop or Android Chrome browser over HTTPS. A Cast button appears when the Google sender SDK is ready; opening its picker starts receiver discovery. Safari and iOS keep their existing playback and AirPlay behavior; Google's Web Sender does not support iOS Chrome.
-
-Select a receiver to transfer the current track and position. The normal player controls must operate remote play/pause, seeking, volume, mute, previous/next and automatic queue advancement. Disconnect returns the current position and playback intent to the local player. Connecting must preserve the receiver's existing volume.
-
-Support online music, server cache/downloads and configured custom music directories. Browser-only blob/file URLs cannot be fetched by a receiver and must produce a useful error. Receiver codec support varies; users can select another quality/source if necessary. Casting uses normal speed; local playback speed is restored on disconnect. The receiver fetches the original audio, so browser audio effects and visualizers do not process its output.
-
-Never send account tokens, cookies or administrator credentials to a receiver. Its media capability must permit only one immutable read resource, expire, and stop working after the issuing credentials are revoked. Existing ownership, filesystem and public-upstream restrictions remain mandatory.
-
-Acceptance: automated local/remote handoff and race tests, actual HTTP range/HEAD/CORS and authorization tests, existing AirPlay regressions, type checking, production compilation, isolated release startup and deployed API/asset verification. Report real receiver playback separately from simulations and network advertisements.
-
-Player and About must identify the running package version plus a reproducible source build fingerprint. Custom builds sharing an upstream release number must remain distinguishable. Missing generated config must not fabricate an older release; package metadata remains authoritative when available, otherwise show an unavailable state.
diff --git a/docs/components/MusicLanding.vue b/docs/components/MusicLanding.vue
index 62705f4e..078fa99a 100644
--- a/docs/components/MusicLanding.vue
+++ b/docs/components/MusicLanding.vue
@@ -12,6 +12,7 @@ const doc = (path: string) => {
}
const asset = (name: string) => withBase(`/showcase/${name}.jpg`)
const clipNames: Record = { 'player-library': 'playlists', search: 'search', charts: 'charts', themes: 'themes', 'admin-dashboard': 'console' }
+const mobileShot = (image: string) => image === 'local-library' ? asset('local-library-mobile') : ''
const clipFor = (image: string) => clipNames[image] || ''
const posterFor = (image: string) => asset(clipFor(image) ? `${clipFor(image)}-poster` : image)
const copy = computed(() => english.value ? {
@@ -142,10 +143,10 @@ function chooseClient(index: number, focus = false) {
diff --git a/docs/components/music-landing.css b/docs/components/music-landing.css
index 294df59e..c6f7fd2e 100644
--- a/docs/components/music-landing.css
+++ b/docs/components/music-landing.css
@@ -365,3 +365,39 @@
.is-animated .music-footer-bottom { font-size: 13px; }
.is-animated .music-footer-bottom button { font-size: 13px; }
}
+
+/* ===== Motion layer ===== */
+.music-landing::before, .music-landing::after { content: ''; position: fixed; z-index: -1; width: 60vmax; height: 60vmax; border-radius: 50%; filter: blur(90px); opacity: .2; pointer-events: none; }
+.music-landing::before { top: -25vmax; left: -15vmax; background: radial-gradient(circle, #6fcf8a, transparent 65%); animation: music-drift-a 18s ease-in-out infinite alternate; }
+.music-landing::after { bottom: -30vmax; right: -20vmax; background: radial-gradient(circle, #4a8fe0, transparent 65%); opacity: .13; animation: music-drift-b 22s ease-in-out infinite alternate; }
+@keyframes music-drift-a { to { transform: translate3d(18vmax, 12vmax, 0) scale(1.15); } }
+@keyframes music-drift-b { to { transform: translate3d(-16vmax, -10vmax, 0) scale(1.2); } }
+.music-nav { backdrop-filter: blur(14px) saturate(1.3); -webkit-backdrop-filter: blur(14px) saturate(1.3); animation: music-fade-down .8s cubic-bezier(.2,.8,.2,1) both; }
+.is-animated .music-nav { background: rgba(16,20,17,.62); }
+.is-animated.is-closing .music-nav { background: rgba(180,231,160,.9); }
+@keyframes music-fade-down { from { opacity: 0; transform: translateY(-16px); } }
+.music-nav-action, .music-button { position: relative; overflow: hidden; }
+.music-nav-action::after, .music-button::after { content: ''; position: absolute; inset: 0; background: linear-gradient(110deg, transparent 30%, rgba(255,255,255,.4) 50%, transparent 70%); transform: translateX(-120%); animation: music-shine 4.5s 1.5s ease-in-out infinite; }
+@keyframes music-shine { 40%, 100% { transform: translateX(120%); } }
+.music-product-window { box-shadow: 0 40px 90px -30px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.06); border-radius: 12px; overflow: hidden; }
+.music-install-options article { transition: transform .35s cubic-bezier(.2,.8,.2,1); }
+.music-install-options article:hover { transform: translateY(-6px); }
+.music-signal span { animation: music-pulse 1.4s ease-in-out infinite; }
+.music-signal span:nth-child(2) { animation-delay: .2s; } .music-signal span:nth-child(3) { animation-delay: .4s; }
+@keyframes music-pulse { 50% { transform: scale(2.2); opacity: .4; } }
+
+/* ===== Mobile: portrait clips shown whole ===== */
+@media (max-width: 759px) {
+ .music-landing::before, .music-landing::after { filter: blur(60px); }
+ .music-product-images { aspect-ratio: 9 / 17; background: #0b0e0c; }
+ .music-product-shot picture, .music-product-shot picture > img { display: block; width: 100%; height: 100%; }
+ .music-product-shot > picture > img, .music-product-shot video, .music-hero-media video { object-fit: contain; object-position: center; }
+ .is-animated .music-product-shot:has(video) > picture, .is-animated .music-hero-media:has(video) > img { display: none; }
+ .is-animated .music-product-window { width: min(78%, calc(38svh * .53)); margin: 0 auto; }
+ .is-animated .music-hero-media { left: 21%; width: 58%; top: 55%; border-radius: 14px; overflow: hidden; }
+ .is-animated .music-hero-media video, .is-animated .music-product-shot video { opacity: 1; background: #0b0e0c; }
+ .is-animated .music-hero-media video { position: relative; height: auto; aspect-ratio: 9 / 17; }
+ .music-rest-shot { aspect-ratio: auto; object-fit: contain; max-width: 320px; margin-inline: auto; display: block; }
+ .is-animated .music-collection-heading > p { display: none; }
+ .music-product-window figcaption, .is-animated .music-hero-media figcaption { text-align: center; }
+}
diff --git a/docs/en/api/reference.md b/docs/en/api/reference.md
index 57b574e1..5ac581ed 100644
--- a/docs/en/api/reference.md
+++ b/docs/en/api/reference.md
@@ -289,22 +289,42 @@ Success is an **object**, not an array. Read `data.list`:
- **`GET /api/stats`**: Performance and memory usage statistics.
- **`POST /api/utils/check-dir`**: Validate if a local directory exists and is accessible.
+#### Dashboard overview {#dashboard-overview}
+
+
+`GET /api/dashboard/overview` requires `x-frontend-auth: ` and no query parameters. Invalid authentication returns HTTP 401. Successful responses are JSON with `Cache-Control: no-store`.
+
+| Response field | Meaning |
+| --- | --- |
+| `success` | `true` on success |
+| `logs.totals` | `info`, `warn` and `error` counts within the last 24-hour window |
+| `logs.buckets` | Twelve rolling hourly buckets with millisecond timestamp `t` and per-level counts |
+| `logs.recent` | Up to eight recent warnings/errors, newest first, with `time`, `level` and `msg`; messages are limited to 200 characters |
+| `disk` | `total` and `free` bytes on the data directory's filesystem, or `null` when unavailable |
+| `heap` | `used` is process RSS, `total` is system memory and `external` is process external memory, all in bytes; these are not V8 heap usage and heap limits |
+| `services` | WebDAV, OpenList and public-favorites enablement settings, plus restore-maintenance state; not remote connectivity checks |
+| `node`, `pid` | Node.js version and process ID |
+
+Logs are filtered by time after reading at most the last 512 KiB of `app.log`; rotated files are not read. High log volume may prevent full 24-hour coverage. Unreadable or unrecognized logs can produce empty statistics, which do not establish the absence of errors. Buckets roll back from request time rather than aligning to clock hours.
+
+See [Subsonic local music library](../guide/subsonic-local-library.md) and [protocol support](./subsonic.md) for Subsonic behavior and coverage.
+
### 9.2 Local Config Backups
- **`GET /api/config/backups`**: List configuration snapshots.
- **`POST /api/config/backup-now`**: Create immediate config snapshot.
- **`GET /api/config/backups/download?file=`**: Download backup archive.
- **`DELETE /api/config/backups/`**: Delete config backup.
- **`POST /api/config/backups/restore`**: Restore configuration snapshot.
-- **`GET /api/backup/download`**: Export full user data backup.
+- **`GET /api/backup/download`**: Export ZIP using the saved WebDAV scope.
- **`POST /api/backup/upload`**: Import and restore user data backup.
### 9.3 WebDAV Cloud Sync & Remote Backups
- **`POST /api/webdav/test`**: Test WebDAV connection and credentials.
-- **`POST /api/webdav/sync`**: Trigger full bidirectional WebDAV sync.
+- **`POST /api/webdav/sync`**: Upload local files within the selected scope.
- **`POST /api/webdav/sync-file`**: Sync single file to WebDAV.
- **`POST /api/webdav/backup`**: Create remote WebDAV backup archive.
- **`GET /api/webdav/backups`**: List backups stored on WebDAV.
-- **`DELETE /api/webdav/backup`**: Delete remote backup (`?file=...`).
+- **`DELETE /api/webdav/backup`**: Delete remote backup (JSON `filename`).
- **`POST /api/webdav/restore`**: Restore data from WebDAV backup.
- **`GET /api/webdav/logs`**: Get WebDAV synchronization logs.
- **`GET /api/webdav/progress`**: **SSE streaming endpoint**, real-time sync progress.
@@ -359,3 +379,91 @@ Errors return `{"message":"..."}` with the corresponding HTTP status.
- Auto-link requires user token; success: `{"success":true,"data":...}`; business errors: HTTP 400, `{"success":false,"message":"..."}`.
See the [current route index](./routes.md) for all route entries and directly read fields.
+
+## Backup and recovery API {#backup-api}
+
+For scripting; see [Backup and recovery](../guide/backup-recovery.md) for UI instructions. Admin requests use `x-frontend-auth: ` and JSON requests use `Content-Type: application/json`. User tokens do not replace admin authentication. Check `success`, `errors` and endpoint-specific `message`/`error`, not only HTTP 200.
+
+### Local archives and configuration history
+
+| Method / path | Contract |
+| --- | --- |
+| `GET /api/backup/download` | ZIP using the saved WebDAV scope; no enabled WebDAV required; not an unconditional full-directory export |
+| `POST /api/backup/upload` | Multipart field `backup` (also accepts `file`); restores local ZIP with current scope, without the WebDAV preview token |
+| `GET /api/config/backups` | `success,list,autoBackupEnabled,retentionDays,intervalHours,scheduleTime,nextBackupAt,lastBackupAt`; timestamps are milliseconds, 0 means unavailable |
+| `POST /api/config/backup-now` | Create config copy; `success,filename`, or `error` on failure |
+| `GET /api/config/backups/download?file=` | Download a configuration copy |
+| `POST /api/config/backups/restore` | JSON `fileName`; config history restoration differs from data ZIP restoration |
+| `DELETE /api/config/backups/` | Delete a local configuration copy |
+
+### WebDAV
+
+| Method / path | Contract |
+| --- | --- |
+| `POST /api/webdav/test` | Read-only directory checks with saved settings; returns `success,message,checks`, not proof of write/delete permissions |
+| `POST /api/webdav/sync` | Upload selected local types, not bidirectional merging |
+| `POST /api/webdav/backup` | Optional JSON `force`, default false; create/upload ZIP |
+| `GET /api/webdav/backups` | `success,backups`, entries: `filename,basename,size,time,timeStr` |
+| `DELETE /api/webdav/backup` | JSON `filename` within configured ZIP directory, not query `file` |
+| `POST /api/webdav/restore` | Preview, then confirm with returned token; example below |
+| `GET /api/webdav/restore/tasks` | `success,tasks`: state, downloaded/applied counts, errors and `canRollback` |
+| `POST /api/webdav/restore/tasks//rollback` | Restore retained pre-restore copies; rejects subsequent local changes |
+| `DELETE /api/webdav/restore/tasks/` | Delete local task/copies, not cloud backups; running/unresolved tasks cannot be deleted |
+| `GET /api/webdav/logs` | Sync logs |
+| `GET /api/webdav/progress` | SSE; also accepts query `auth`, which should not be exposed in shared links |
+
+Preview request:
+
+```json
+{
+ "preview": true,
+ "mode": "files",
+ "strategy": "missing",
+ "keepCopies": true
+}
+```
+
+- `mode`: `files` (incremental directory), `zip`, or `auto` (default; ZIP fallback only for missing directory/no selected files).
+- `targetFilename`: optional ZIP filename from the backup list; omitted selects latest ZIP.
+- `strategy`: `missing` or `overwrite`; API default is **overwrite**, not missing-only.
+- `keepCopies`: defaults to true.
+- Response includes `success,token,source,total,added,overwritten,merged,skipped,bytes,warnings,files`. `bytes` counts planned write bytes; ZIP preview downloads the complete compressed archive, so this is not its preview transfer estimate.
+
+Confirm with `{"token":"returned-value"}` or cancel with `{"cancel":true,"token":"returned-value"}`. Plans expire after 15 minutes; configuration or local-file changes require a new preview. Execution returns `success,taskId,restored,backupDir,errors,restartRequired,message`; early failures may omit task fields. Manual restore enters maintenance mode and requires restart. Multi-file commit is not a filesystem atomic transaction.
+
+`POST /api/webdav/sync-file` is a legacy single-file endpoint with JSON `path,action`; action is `upload` or `download`. It does not provide batch preview, transaction or rollback records. Prefer the preview flow for recovery.
+
+### OpenList
+
+| Method / path | Contract |
+| --- | --- |
+| `POST /api/openlist/test` | Optional `endpoint,username,password,token,root`, falling back to saved values; read-only, does not save |
+| `POST /api/openlist/sync` | Optional `user,folder`; backup and enabled user-cache rules, returns `success,summary` |
+| `GET /api/openlist/backups` | `success,list`; upload status is separate from content verification |
+| `GET /api/openlist/metadata` | Current metadata job and count of primary records for this connection |
+| `POST /api/openlist/metadata/start` | JSON `target`: `local`, `remote`, `both`; optional nonempty integer `ids` array, omitted for all current-connection records. Starts a background read-only metadata check |
+| `POST /api/openlist/metadata/cancel` | Stop the metadata check; completed observations remain saved |
+| `POST /api/openlist/retry` | JSON `id` |
+| `POST /api/openlist/retry-batch` | JSON `ids` |
+| `POST /api/openlist/delete` | JSON `ids`; explicit deletion may remove remote files; returns `deleted,remoteRemoved,remoteFailed` |
+| `GET /api/openlist/restore/history` | Available recovery versions |
+| `POST /api/openlist/restore` | Preview with `preview:true,mode,types,backupBeforeRestore,snapshotId`; returns `planToken`, counts, files and warnings |
+| `POST /api/openlist/restore/jobs/start` | Same restore parameters plus `planToken`; returns `success,taskId`, runs in background |
+| `GET /api/openlist/restore/jobs` | Tasks and `maintenance` state |
+| `POST /api/openlist/restore/jobs//cancel` | Stop task |
+| `POST /api/openlist/restore/jobs//resume` | Continue an eligible task; interrupted commit needs rollback handling first |
+| `POST /api/openlist/restore/jobs//rollback` | Roll back, returning `restartRequired` |
+| `DELETE /api/openlist/restore/jobs/` | Delete task/local copies |
+| `POST /api/openlist/integrity/preview` | Optional JSON `type,user,scope`: scope `changed` (default), `all`, `problems`; returns `token,total,bytes,unknown` |
+| `POST /api/openlist/integrity/start` | JSON `token`, start manual content verification |
+| `GET /api/openlist/integrity` | Current job and results |
+| `POST /api/openlist/integrity/cancel` | Stop verification |
+
+OpenList restore `mode` is `missing` or `overwrite` (default), unlike WebDAV's source-mode field. Optional `types` uses the five scope identifiers, defaulting to saved types. Omit `snapshotId` for current backup or supply a historical version. `backupBeforeRestore` defaults to true. WebDAV tokens, OpenList restore `planToken` and integrity tokens are not interchangeable.
+
+Maintenance allows status/config reads, recovery management, rollback and restart; ordinary business APIs may return 503. Resume backup/playback after restart. Verification may read full remote bodies: `bytes` is the maximum body-read estimate and `unknown` counts unavailable upload fingerprints. Verification does not automatically repair files.
+
+
+### Backup overview status
+
+`GET /api/backup/status` requires `x-frontend-auth`. Returns `success,maintenance,webdav,openlist`; disabled providers return null. WebDAV exposes `running,restoring,nextSyncAt,nextZipAt,lastActivity,lastError`. OpenList exposes `running,checking,nextSyncAt,nextCheckAt,counts,lastRun,lastRecordAt,lastError`. Times are milliseconds; 0 means no scheduled run. Next times indicate scheduler checks, not guaranteed transfers. Runtime summaries reset at process restart; persistent file records are separate. No cloud file bodies are read. The UI hides details and execution actions for disabled services.
diff --git a/docs/en/api/routes.md b/docs/en/api/routes.md
index 09be2c61..604f01f9 100644
--- a/docs/en/api/routes.md
+++ b/docs/en/api/routes.md
@@ -16,6 +16,33 @@ See [API reference](./reference.md). File operations are delegated to VueFinder
| --- | --- | --- |
| GET | `/api/status` | — |
+## dashboard
+
+| Method | Path | Direct fields (partial) |
+| --- | --- | --- |
+| GET | `/api/dashboard/overview` | — |
+
+## ratings
+
+| Method | Path | Direct fields (partial) |
+| --- | --- | --- |
+| POST | `/api/subsonic/sync-link` | `user`, `direction`, `preview`, `reconcile` |
+| GET / POST | `/api/data/rating-sync` | Query parameter `user` |
+| GET / POST | `/api/music/rating-preferences` | `dislikeAutoRating` |
+| GET / POST | `/api/music/ratings` | `operation`, `songs`, `includeDetails`, `song`, `rating` |
+
+## openlist/connection
+
+| Method | Path | Direct fields (partial) |
+| --- | --- | --- |
+| GET / POST | `/api/openlist/connection` | — |
+
+## files
+
+| Method | Path | Direct fields (partial) |
+| --- | --- | --- |
+| GET / HEAD | `/api/files` | Query parameter `path`; delegated to VueFinder, see API reference |
+
## users
| Method | Path | Direct fields (partial) |
@@ -518,7 +545,7 @@ See [API reference](./reference.md). File operations are delegated to VueFinder
| Method | Path | Direct fields (partial) |
| --- | --- | --- |
-| POST | `/api/webdav/restore` | — |
+| POST | `/api/webdav/restore` | `preview,mode,targetFilename,strategy,keepCopies,token,cancel` |
## webdav/logs
@@ -578,3 +605,36 @@ See [API reference](./reference.md). File operations are delegated to VueFinder
| POST | `/api/music/remaster/start` | — |
| GET | `/api/music/remaster/status` | — |
| POST | `/api/music/remaster/cancel` | — |
+
+## Backup recovery and verification
+
+[Detailed contracts](./reference.md#backup-api).
+
+| Method | Path | Main fields / purpose |
+| --- | --- | --- |
+| GET | `/api/webdav/restore/tasks` | `tasks` |
+| POST | `/api/webdav/restore/tasks//rollback` | `id` |
+| DELETE | `/api/webdav/restore/tasks/` | `id` |
+| POST | `/api/openlist/test` | `endpoint,username,password,token,root` |
+| POST | `/api/openlist/sync` | `user,folder` |
+| GET | `/api/openlist/backups` | `list` |
+| GET | `/api/openlist/metadata` | `job,total` |
+| POST | `/api/openlist/metadata/start` | `target,ids?` |
+| POST | `/api/openlist/metadata/cancel` | `—` |
+| POST | `/api/openlist/retry` | `id` |
+| POST | `/api/openlist/retry-batch` | `ids` |
+| POST | `/api/openlist/delete` | `ids` |
+| POST | `/api/openlist/restore` | `preview,mode,types,backupBeforeRestore,snapshotId,planToken` |
+| GET | `/api/openlist/restore/history` | `history` |
+| GET | `/api/openlist/restore/jobs` | `tasks,maintenance` |
+| POST | `/api/openlist/restore/jobs/start` | `planToken,mode,types,backupBeforeRestore,snapshotId` |
+| POST | `/api/openlist/restore/jobs//cancel` | `id` |
+| POST | `/api/openlist/restore/jobs//resume` | `id` |
+| POST | `/api/openlist/restore/jobs//rollback` | `id` |
+| DELETE | `/api/openlist/restore/jobs/` | `id` |
+| GET | `/api/openlist/integrity` | `job,results` |
+| POST | `/api/openlist/integrity/preview` | `type,user,scope` |
+| POST | `/api/openlist/integrity/start` | `token` |
+| POST | `/api/openlist/integrity/cancel` | `—` |
+
+| GET | `/api/backup/status` | `success,maintenance,webdav,openlist` |
diff --git a/docs/en/api/subsonic.md b/docs/en/api/subsonic.md
new file mode 100644
index 00000000..c96e5702
--- /dev/null
+++ b/docs/en/api/subsonic.md
@@ -0,0 +1,109 @@
+# Subsonic protocol support
+
+The server implements Subsonic / OpenSubsonic endpoints to expose LX playlists, online sources and account-local music to compatible clients. Behavior depends on server settings, source availability and client capabilities. This page describes functional coverage, not identical interfaces across clients.
+
+See [Subsonic local music library](../guide/subsonic-local-library.md) for directories, identities and playlist persistence, and [sync server settings](../guide/sync-server.md) for connection setup.
+
+## Connection
+
+The default protocol path is `/rest`, sharing the main service port; `subsonic.path` customizes it. Separate listening requires both `subsonic.standalone` and a valid `subsonic.port`. Setting the port alone does not enable a separate listener.
+
+Authenticate with the user account's sync password, not the administrator password. JSON and XML are supported. Example paths are illustrative; clients must supply protocol authentication, version and other required parameters.
+
+```text
+GET /rest/ping.view?u=&t=&s=&v=1.16.1&c=&f=json
+```
+
+## Supported endpoints
+
+### System, browsing and search
+
+| Methods | Behavior and limits |
+| --- | --- |
+| `ping`, `getLicense`, `getUser` | Connection checks, license information and current-user information |
+| `getMusicFolders` | Server-configured entry, NAS local music and online platforms |
+| `getMusicDirectory` | Directories, albums, playlists and local music |
+| `getIndexes`, `getArtists`, `getArtistList` | Artist indexes; `getArtistList` is a compatibility entry |
+| `getArtist`, `getArtistInfo`, `getArtistInfo2` | Artist details and information |
+| `getAlbum`, `getAlbumInfo`, `getAlbumInfo2` | Album tracks, descriptions and artwork |
+| `getAlbumList`, `getAlbumList2` | Album lists, recommendations and recent playback; explicit NAS-folder selection uses the local branch |
+| `getGenres` | Online or local-library genre information |
+| `search`, `search2`, `search3` | Source selection follows online-search settings and query prefixes |
+| `startScan`, `getScanStatus` | Refresh the local index and report scan state, counts and failures, rather than a fixed placeholder |
+
+Online search uses `subsonic.onlineSearch` and `subsonic.onlineSearchMode`: `fallback` supplements insufficient local results, `merge` combines results and `local_only` stays local. Use `local:`, `online:` or platform prefixes to restrict scope. Online playback still requires a working custom source.
+
+### Media, playlists and favorites
+
+| Methods | Behavior and limits |
+| --- | --- |
+| `getSong` | Online or local track details |
+| `stream`, `download` | Audio access; local files support Range, while online resources depend on source resolution |
+| `getCoverArt` | Track, album and playlist artwork, with a `size` parameter |
+| `getLyrics`, `getLyricsBySongId` | Ordinary and structured lyrics; see the local-library guide for sources and word-level limitations |
+| `getPlaylists`, `getPlaylist` | User, shared and enabled read-only local playlists |
+| `createPlaylist`, `updatePlaylist`, `deletePlaylist` | User playlist management; built-in and shared playlists have write restrictions |
+| `getStarred`, `getStarred2`, `star`, `unstar` | Read and update favorites |
+| `setRating` | Store per-user ratings from 0–5; 0 clears the rating |
+
+See [favorites and playlist synchronization](../guide/subsonic-local-library.md#favorites-and-playlist-synchronization) for local/mixed playlist rules, deletion behavior and backup files.
+
+### Discovery and radio
+
+| Methods | Behavior and limits |
+| --- | --- |
+| `getRandomSongs`, `getSongsByGenre`, `getSongsByGenre2` | Random or genre-filtered tracks, including a local-library branch |
+| `getSimilarSongs`, `getSimilarSongs2`, `getSonicSimilarTracks` | Select similar tracks using conditions such as the same artist; no acoustic analysis |
+| `getTopSongs` | Artist top tracks |
+| `getRecommendedSongs`, `getDailySongs`, `getSongsByTag` | Server recommendations; the last two are aliases, and `getSongsByTag` does not perform independent tag search |
+| `getInternetRadioStations` | Available official stations, user stations and source-playlist stations |
+| `createInternetRadioStation`, `updateInternetRadioStation`, `deleteInternetRadioStation` | User station management; a plural delete compatibility entry is also accepted |
+
+Daily recommendations obtain tracks from online recommended albums, deduplicate them and use stable shuffling within a day. Upstream failures can fall back to the last successful results. Per-account filtering excludes tracks in the disliked rating range: `0 < rating <= subsonic.dislikeRating`, whose default threshold is 1.
+
+Server-generated station streams use absolute URLs with expiring tickets rather than long-lived credentials. External user station URLs are returned as configured. Reverse proxies must pass the correct protocol information, and clients must be able to reach these URLs. Unavailable official upstream stations are temporarily hidden.
+
+### Playback activity, queues and bookmarks
+
+| Methods | Behavior and limits |
+| --- | --- |
+| `scrobble` | Store the requested track playback records, accepting playback timestamps |
+| `reportPlayback` | Update state and position; record history only for `stopped` without `ignoreScrobble=true` |
+| `getNowPlaying` | Recent playback activity, optionally with position; not live monitoring of every client |
+| `getPlayQueue`, `savePlayQueue` | Persist a queue per user |
+| `getPlayQueueByIndex`, `savePlayQueueByIndex` | Read/write queues by index; an out-of-range current index returns a parameter error |
+| `getBookmarks`, `createBookmark`, `deleteBookmark` | Persist bookmarks per user |
+
+## Transcoding and media limits
+
+| Setting | Default | Purpose |
+| --- | --- | --- |
+| `subsonic.transcode.enabled` | `false` | Enable server transcoding |
+| `subsonic.transcode.format` | `mp3` | Target format: `mp3`, `opus` or `aac` |
+| `subsonic.transcode.maxConcurrent` | `2` | Maximum concurrent transcodes |
+
+Transcoding requires available ffmpeg. `stream` / `download` can select transcoding based on client bitrate requirements; `timeOffset` is measured in seconds. OpenSubsonic `getTranscodeDecision` / `getTranscodeStream` provide decisions and signed parameters.
+
+Without ffmpeg, online resources can fall back to their original links, which may not match client format requirements. Target format, source availability and server resources affect playback.
+
+## OpenSubsonic extensions
+
+`getOpenSubsonicExtensions` advertises these extensions, all at version 1:
+
+| Extension | Capability |
+| --- | --- |
+| `formPost` | Form POST parameters |
+| `songLyrics` | Structured lyrics |
+| `playbackReport` | Playback state and position reports |
+| `sonicSimilarity` | Similar-track entry using heuristic selection |
+| `indexBasedQueue` | Index-based queue management |
+| `transcodeOffset` | Transcode time offsets |
+| `transcoding` | Transcode decisions and streams |
+
+Advertising an extension means its endpoint exists; client calls and presentation still depend on client implementation.
+
+## Unsupported scope
+
+Video, podcasts, chat, sharing and server-account administration through Subsonic are outside the current implementation. Typical unsupported methods include `getVideos`, `getPodcasts`, `getChatMessages`, `createShare`, `createUser`, `updateUser`, `deleteUser`, `changePassword` and `getAvatar`.
+
+Unsupported methods return `Method not found`. Manage accounts through the console. For missing controls or compatibility issues, compare this coverage with the methods actually requested by the client.
diff --git a/docs/en/guide/backup-recovery.md b/docs/en/guide/backup-recovery.md
index 057a8614..8fd945c4 100644
--- a/docs/en/guide/backup-recovery.md
+++ b/docs/en/guide/backup-recovery.md
@@ -1,60 +1,190 @@
-# Cloud backup and recovery
+# Backup and recovery
-Use **System settings → Cloud backup scope** to select data types independently for WebDAV and OpenList. Selecting both destinations stores two copies; it does not synchronize the destinations with each other. Each provider still needs its own enabled connection.
+Choose what to save and where to keep it before enabling automatic backups. You can keep a local copy, use WebDAV, use OpenList, or enable both cloud destinations. The two providers store independent copies; they do not synchronize with one another.
-## Included files
+## Choose a backup method
-| Type | Scope | Restore destination |
+| What you need | Use |
+| --- | --- |
+| Recover a previous playlist or dislike list | Playlist/dislike history snapshots |
+| Recover server settings | Local configuration history |
+| Download data for safekeeping or migration | Local ZIP export/import in Backup management |
+| Automatically copy data to cloud storage | WebDAV or OpenList data backup |
+| Upload playback cache to OpenList or free local cache space | Separate advanced per-user OpenList cache rules |
+
+Configuration history does not include all user files or audio. Playlist snapshots do not include audio either. To recover a full installation, select the required data types and separately protect external music libraries excluded from the backup.
+
+## Choose what to save
+
+Open **Backup management → Cloud backup → Backup scope**, select types independently for WebDAV and OpenList, and save. Clearing every type disables that destination's data backup without deleting files already uploaded.
+
+| Option | Includes |
+| --- | --- |
+| Songs and lyrics | Audio, LRC, artwork and music indexes under the data directory's `music` folder |
+| User source files | Source scripts, ordering/status files and uploaded user files |
+| Playlist and dislike snapshots | Historical snapshots, including the configured external snapshot location |
+| User data | User settings, playback history, favorites and other user files |
+| System and accounts | Server configuration, account list and system JSON files at the data directory root |
+
+**Ordinary data backup excludes cache, logs, temporary files, local backup archives and external custom music directories. Directory links are not followed.** A separate `music` directory in the program working directory is also excluded. Paths refer to your actual data directory, which may differ from the default `data`.
+
+New backup scans, new OpenList history manifests and full content verification exclude `test-evidence` directories. Existing cloud files are not automatically deleted, and older history remains restorable. Failed manual OpenList deletion preserves the affected records and local files for retry using the same records. Some remote files may already have been removed; retries skip missing files. Backup and full verification are blocked during deletion. The verification table shows all latest results for current versions, independently of the task scope selected above.
+
+Advanced OpenList playback caching can upload cache separately for selected users. It is not the same as ordinary data backup. System/account backups may contain credentials; store them somewhere you control.
+
+
+### Program directory versus data directory
+
+These paths are relative to the program working directory and assume the default `DATA_PATH=data`:
+
+| Location | Ordinary WebDAV / OpenList data backup |
+| --- | --- |
+| `cache/` | Excluded: program-directory cache |
+| `music/` | Excluded: program-directory music separate from the data directory |
+| `data/cache/` | Excluded: data-directory playback cache |
+| `data/users/account/cache/` | Excluded: per-user cache; cache directories at other levels are excluded too |
+| `data/music/` | Included when Songs and lyrics is selected and saved; audio, existing LRC, artwork and indexes |
+| External directory, e.g. `D:/Music/` | Excluded; protect separately |
+
+With a custom `DATA_PATH`, the scope is **music under the actual data directory**. For `D:/LXData`, that is `D:/LXData/music`. If the program working directory itself is configured as the data directory, its `music/` can be selected for backup. A blanket rule excluding every root-level music directory would be incorrect. Cache remains excluded.
+
+Saving a scope does not enable the service. WebDAV needs an enabled, configured connection for cloud backup. Local ZIP export/import also uses the WebDAV-column scope but does not require WebDAV to be enabled. Advanced OpenList playback caching has independent user rules that can handle cache without changing ordinary backup exclusions.
+
+## Configure local backups
+
+Open **Backup management → Local backups** for snapshot counts, local configuration history and storage paths.
+
+- Configuration history is enabled by default, runs every 24 hours, retains 7 days and uses the data directory's `backups` folder.
+- The default anchor time is `00:00` in the server's local time. Leave it empty to count intervals from startup or rescheduling.
+- An empty configuration-backup path uses the default. Relative paths start at the data directory; absolute paths are used directly.
+- An empty snapshot path keeps existing per-user snapshot locations. An additional configured location separates snapshots by user.
+
+Local ZIP export/import in **Backup management** does not require WebDAV. It uses the types saved in the WebDAV scope column, so check that selection before exporting.
+
+## Back up with WebDAV
+
+1. Open **Backup management → Cloud backup → WebDAV**, enter the URL and credentials, and enable it.
+2. Set incremental and ZIP directories; defaults are `/lx-sync` and `/lx-sync-backups`.
+3. Save, then test the connection on the same page. The test reads the saved directories without creating files; it does not prove upload or deletion permissions.
+4. Run file synchronization or a full ZIP backup and inspect the result. Start with a small backup and test recovery in a separate installation.
+
+Incremental backup uploads changed files and checks every 60 minutes by default. ZIP archives cover the same selected data; scheduled checks run every 24 hours and create an archive when data changed. A manual full backup can force a new archive.
+
+### Understand the two optional switches
+
+| Switch | Off: recommended default | On |
| --- | --- | --- |
-| `music` | `data/music/**`, including audio, lyrics, covers and `music_index.json` | The current data directory, retaining relative paths |
-| `userFiles` | `users/source/**`, `source/**`, `uploads/**` | The same relative paths in the current data directory |
-| `snapshots` | Playlist/dislike snapshot bodies and `snapshotInfo.json`, including extensionless bodies | Original relative paths; external playlist snapshots use the current `snapshot.backupPath` |
-| `userData` | Other user files under `users//**` | The same account directories and relative paths |
-| `system` | `config.js` and JSON files in the data directory root, including `users.json` and `serverInfo.json` | Current `CONFIG_PATH` for configuration; current data directory for JSON |
+| Delete the WebDAV incremental copy when its local file is deleted | Keep the cloud copy | Next incremental run deletes the tracked counterpart; historical ZIPs remain |
+| Overwrite selected local data from WebDAV before every startup | Restore manually when needed | Restore before opening the service; cloud files replace matching local files |
+
+Startup restoration is for deployments deliberately using the cloud as their recovery source. It is not limited to an empty first start and can overwrite newer local data. Failure leaves maintenance mode active and does not upload local data to the cloud.
+
+### Retention and transfer usage
+
+ZIP retention defaults to 5 archives and 0 days (no age limit). Successful uploads clean archives exceeding either limit, always keeping the newest. This does not clean the incremental directory.
+
+Uploads check the response and remote size without downloading the whole file again. Safe replacement requires WebDAV `MOVE`; check server/proxy support if MOVE fails. Matching size is not proof of matching contents.
+
+## Back up with OpenList
+
+1. Open **Backup management → Cloud backup → OpenList**, and enter the endpoint and token or account details.
+2. Enter an **existing OpenList mount path** such as `/local` as the root. Do not enter a Windows host path such as `E:\OpenList`; configure that physical path inside OpenList's storage settings.
+3. Enable, save and test, then run the immediate backup/cache-sync action.
+4. Inspect upload results in the corresponding record tab.
+
+Ordinary backups keep relative paths under `_data` within the configured root. For example, a song might be stored at `/local/_data/music/account/song.mp3`. Its physical disk location depends on the OpenList mount.
+
+Data backups retain local files. Deleting a local file does not automatically delete its `_data` backup. Advanced playback caching has separate user rules and locations; only an explicit upload-and-delete rule removes corresponding cache files after strict verification.
+
+### Upload completion versus content verification
+
+“Upload complete” means upload and remote size checks succeeded, not full content equality. Ordinary backup does not download songs for SHA-256 verification. The Backed up task state and the Content verification filter are independent. Verification results describe the current registered backup fingerprint at a past check time; changed versions or observed cloud changes require another check. Legacy internal verified states alone do not qualify. Upload-and-delete and restoration retain their automatic safety checks.
+
+To check contents, open **Backup management → Records and verification → OpenList → Content verification**:
+
+1. Choose the user, type and scope, then preview estimated transfer usage.
+2. The default checks unchecked or changed versions. Choose all current files when investigating later cloud corruption.
+3. Confirm and start; the job runs in the background and can be stopped.
+4. Review matching contents, mismatches, missing files and read errors. Verification does not automatically repair or overwrite cloud files.
+
+If storage supplies a usable SHA-256 it can be compared directly; otherwise verification reads the remote body and consumes download transfer. Legacy records without upload fingerprints require another backup to establish a baseline. Upload-and-delete cache rules still require strict content verification and may read remote audio and attachments.
+
+## Filter OpenList backup records
+
+In Backup management → Records and verification, select OpenList file records. The record panel contains one set of filters: type buttons, user, storage category, local availability and sorting. Click a status card to filter that status; click again to clear it. Counts reflect other active conditions. Active-condition chips can be removed individually.
+
+Space-separated search terms must all match, across filenames, users, paths and available song metadata. More conditions adds path, snapshot subtype and an inclusive update-date range interpreted in browser-local time. Conditions combine; remove individual chips or reset if nothing matches. Filtering/pagination reuse loaded records; Refresh records and re-entering the record view fetch current results. These controls do not change backup scope or files.
+
+## Restore your data
+
+Recovery runs **from the backup to the current server**. It does not upload local files or delete files that exist only locally.
+
+### WebDAV
+
+1. Confirm the current data/configuration locations and external snapshot path, then save the types to restore.
+2. Open **Backup management → Data recovery → WebDAV** and choose the incremental directory or a specific ZIP.
+3. Choose missing-only or overwrite. Keeping pre-restore copies is recommended.
+4. Preview additions, replacements, index merges and skips before confirming. ZIP preview downloads the entire archive once and reuses it on confirmation; cancelling or expiry removes that temporary archive.
+5. Restart the service to reload accounts, playlists, sources and music indexes.
+
+### OpenList
+
+Open **Backup management → Data recovery → OpenList**, select the current backup or an available historical version, choose scope and overwrite behavior, then scan and confirm. Restore records show progress and support continuation, cancellation or rollback where available. A fresh installation can enumerate cloud data without the old backup database.
+
+### Replacement rules and failures
+
+- **Missing-only:** retain existing files and add absent files. Required music/snapshot indexes merge entries; ordinary JSON files are not merged field by field.
+- **Overwrite:** matching files in the selected scope come from the backup. Local-only files remain; the directory is not cleared.
+- **Pre-restore copies:** kept by default for rollback. Rollback refuses files modified again after restoration. Delete records and copies explicitly when no longer needed.
+
+Required files are staged and checked before local replacement. Download failure changes no originals; write failure attempts rollback. Legacy backups without fingerprints receive size checks and a warning. Allow extra disk space for staged files and pre-restore copies.
+
+Recovery enters maintenance mode. Restart after a manual restore; refreshing the browser is insufficient. If a process stops during commit or rollback fails, use restore records to resolve rollback before restarting normal service.
+
+## Common questions
+
+**“Storage not found”?** Check that the OpenList mount exists, is enabled and matches the configured root. Connecting to OpenList alone does not create writable storage.
+
+**Why are LRC files uploaded?** Songs and lyrics includes existing LRC under the data directory's `music` folder. The advanced cache attachment switch only controls separate cache rules. Disabling lyric generation does not delete existing LRC.
+
+**Nothing changed after restore?** Check source, types, account directories and replacement mode. Missing-only skips existing files. Restart after completion before checking results.
+
+**Do WebDAV and OpenList synchronize with each other?** No. Choose one source explicitly when restoring.
+
+See [configuration](./configuration.md) for keys/defaults and the [backup API](../api/reference.md#backup-api) for scripting.
-Cache, runtime data, logs, temporary files, SQLite working databases/WAL and local backup archives are always excluded. Directory links are not followed. External custom music directories are not included. Preserve account directory names during recovery. Music indexes and audio are grouped together. System and user data can contain credentials or device information.
+### Task states and verification results
-## System configuration
+Record states describe pending uploads, pending downloads, active transfers, backed-up files, items needing attention, failures and paused rules. Local size or modification-time changes flag enabled upload rules for the existing scheduled process. Background content comparison refreshes the baseline when only metadata changed. Disabled services or scopes appear paused. Removing local playback cache after upload does not request cloud deletion.
-```js
-module.exports = {
- 'backup.webdav.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
- 'backup.openlist.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
- 'backup.webdav.mirrorDeletes': false,
- 'backup.webdav.restoreOnStartup': false,
-}
-```
+Content verification is a separate historical result, not a real-time guarantee. Old matching badges are hidden while local changes or transfers are pending. Observed cloud changes mark previous results as needing rechecking and include them in the default verification scope. Select all current files to detect later corruption with unchanged size and timestamps. Refreshing records checks local metadata without downloading cloud bodies. Diagnostic states remain in details and advanced filters. User and type filters also apply to verification results.
-An empty type array disables data backup for that destination. The admin UI validates and saves these fields in the active configuration file. Deselecting a type does not remove existing cloud copies.
+### Manual metadata checks and timestamps
-The old `webdav.excludeCache` and `webdav.excludeMusic` interfaces remain for compatibility. Explicit type arrays control the new scope; cache is always excluded. The old `openlist.backupSystemConfig` applies only to the legacy flow without explicit types, avoiding a second system-backup switch in the new UI.
+In records, choose Check files, select local, cloud or both, and selected records, current filtered results or all records for this connection. Checks cover primary record files; use content verification for attachments. Historical objects are excluded. Checks inspect existence, size and modification time without uploading, downloading bodies, deleting files or producing SHA-256 results. Cloud requests run sequentially and can be stopped while preserving completed observations.
-## Provider behavior
+Advanced filters show checked X/Y records and the newest check time for the matching scope; hover for the oldest timestamp. This does not imply every file was checked successfully in one run. Details show per-file timestamps. Listing records refreshes local metadata; manual local check times are retained separately. Cloud observations persist by connection and backup version across restarts. Interrupted jobs must be restarted.
-WebDAV incremental backup stores selected files under `webdav.syncPath`; ZIP backup archives the same scope under `webdav.backupPath`. Separate successful baselines prevent incremental uploads from suppressing ZIP backups. Failed uploads remain eligible for retry.
+### Subsonic ratings and snapshots
-Local offline ZIP export/import uses the saved WebDAV-column scope without requiring an enabled connection or accessing the cloud. The UI states this explicitly so partial exports are not mistaken for whole-server backups. Daily local configuration history remains controlled by `configBackup.*`; it does not replace a data backup.
+The rating-playlist switch controls virtual playlist visibility only. Each link switch controls its own direction; a zero threshold disables linking. Links only fill missing data: raising or clearing a rating preserves dislike rules, removing a dislike preserves ratings, and existing ratings are never overwritten. The one-star playlist also includes disliked tracks while preserving their actual ratings.
-Cloud files are retained after local deletion by default. Enable `backup.webdav.mirrorDeletes` to propagate deletions to scattered files; historical ZIPs remain unchanged. Backup and restore operations do not run concurrently within one WebDAV instance.
+Restoring a rating snapshot replaces ratings only and preserves current artist and album favorites, including when reading legacy snapshots. Rating snapshots and their index belong to the snapshots backup scope; the current subsonic-meta.json belongs to user data. Select both scopes for a complete backup. If ratings are saved but dislike resolution or writing fails, the client receives a partial-success error and can retry or use manual synchronization. The two stores are not a cross-file transaction. Evicted rated tracks are resolved again when possible; unavailable sources retain their ratings but may temporarily prevent playlist display.
-OpenList stores data at `/_data/`. Data backup uploads files and retains local copies. `openlist.syncInterval` controls scheduled runs. Recovery enumerates cloud files directly, so a fresh installation can recover without the previous SQLite database.
+New dislike rules are deduplicated and matched by source; rules without a source explicitly apply to all sources. Duplicate records with matching source and non-conflicting song identity fill missing fields while preserving valid data and display names. Dislikes record provenance and enrichedBy; rating provenance is stored in ratingOrigins. Manual linking previews candidates including the online cache, counts unresolved targets as failures, and reports enrichment separately.
-Advanced permanent playback caching is separate. Per-user cache rules may upload disposable cache files, delete local copies after verification, or provide Subsonic direct/proxy delivery. Selecting `music` in the backup matrix does not enable cache uploading. Song, snapshot and system backup are no longer controlled twice by these per-user cache rules.
+Configuration changes do not reconcile historical data. Raising the threshold allows manual linking to add newly qualifying rules. Lowering it, setting it to zero, disabling linking, or clearing ratings does not remove existing rules. Matching and visibility settings only change subsequent filtering and display. Automatic Web-to-rating linking applies only to individual songs with a source and song ID; artist and album dislikes are not written as song ratings.
-Before removing a local playback-cache copy, SHA-256 verification reads the actual cloud bytes. This adds a remote verification transfer. Files modified during transfer are retained. Normal data backup never removes local files after upload.
+The admin Data view includes a Dislikes tab for the selected user, combining dislike rules and disliked artist/album libraries. Filter by type, creation channel, platform, or keyword; inspect saved covers, rules, ratings, and complete fields. Missing provenance is explicitly shown as unrecorded rather than inferred. The view is read-only and does not change linking or ratings.
-## Recovery procedure
+Rating identity migration and restore use a transaction journal: write failures roll back, and the next rating access completes an interrupted commit. Restore normalizes local/online scores against current file links and updates rating IDs in dislike reasons while preserving manual reasons and older snapshots. The temporary journal is stored in each user’s `rating/transaction.json` and removed after completion. Multiple server processes must not write to the same data directory concurrently.
-1. Configure the destination server's cloud connection, data directory, `CONFIG_PATH` and snapshot mounts.
-2. Select and save the types to restore. Configure `snapshot.backupPath` before restoring external snapshots; cloud absolute paths are never used as destinations.
-3. Restore scattered files or a specified ZIP from WebDAV, or use OpenList's restore action. Only selected types are downloaded. Matching local files are replaced, local-only files remain, and no files are uploaded during restore.
-4. Check results and logs. Downloads use temporary files and rename after completion. A multi-file restore is not an atomic transaction; inspect partial failures and retry.
-5. Restart the service to reload accounts, playlists, sources and music indexes. Refreshing the browser alone does not reload all server-side state.
+### OpenList connection status
-Startup restore is disabled by default. Set `backup.webdav.restoreOnStartup=true` only when unattended disaster recovery is intended; matching files will be overwritten. Automatic recovery tries ZIP only when the scattered directory is missing or has no selected files. Authentication/network errors do not imply an empty cloud. Failed recovery never initializes the cloud by uploading local files.
+The backup records page shows destination health separately, including the latest probe and successful connection times. “探测连接” (Probe connection) checks authentication and the configured backup directory without uploading or downloading files. An unavailable destination preserves historical backup results and marks remote observations as temporarily unavailable. Automatic probes back off from 30 seconds to 5 minutes; recovery triggers a check of previously backed-up files. A successful connection or unchanged metadata does not prove content integrity.
-Old ZIPs are filtered using the current selection, even if they contain caches. Legacy per-user OpenList audio remains accessible through compatible cache flows; the new unified restore reads `_data` and does not guess or move legacy directories.
+Connection failures use Chinese explanations, while record details retain raw diagnostics. Uploads, downloads, and attachment transfers return to pending on destination outages without consuming per-file retry attempts, then continue after recovery. File checks preserve their current position while disconnected, display a waiting state, and retry the current file after recovery. They remain cancellable. A process restart marks unfinished checks as interrupted; start a new check to proceed.
-## Validation limits
+“上次未变” means the last metadata observation was unchanged. The per-record “检查” action only checks local and remote metadata; “重试上传/重试下载” performs a transfer.
-Regression coverage uses real SQLite, a local HTTP OpenList fixture, a WebDAV client substitute, ZIP archives and fresh restore directories. It covers selection, cache exclusion, retry behavior and selected Subsonic delivery paths. It does not establish compatibility with every real cloud mount, WebDAV server or Subsonic client. Verify a small backup in an independent directory before relying on it.
+Before propagating a bidirectional deletion, files and readable linked attachments are copied to `backups/openlist-recycle//` under the data directory. A `manifest.json` records original paths for manual recovery. A failed safety copy prevents deletion. Recycle copies are not automatically removed. More than 20 candidate deletions in one user folder pause that folder's synchronization before any deletion, upload, or download. Check the mount and local directory, then handle confirmed deletions manually in smaller batches. Explicit administrator deletion actions are unchanged.
diff --git a/docs/en/guide/configuration.md b/docs/en/guide/configuration.md
index 400efa95..e5aa3d0a 100644
--- a/docs/en/guide/configuration.md
+++ b/docs/en/guide/configuration.md
@@ -50,7 +50,7 @@ Defaults below refer to code defaults. Bundled configuration, saved values and i
| `ENABLE_LOGIN_USER_CACHE_RESTRICTION` | `user.enableLoginCacheRestriction` | Enable cache settings restriction for logged-in non-admin users | `false` |
| `ENABLE_CACHE_SIZE_LIMIT` | `user.enableCacheSizeLimit` | Enable cache size limit (auto-cleanup via LRU) | `false` |
| `CACHE_SIZE_LIMIT` | `user.cacheSizeLimit` | Cache size limit in MB | `2000` |
-| `CONFIG_BACKUP_ENABLE` | `configBackup.enable` | Enable automatic config.js backup (creates daily backup copy) | `true` |
+| `CONFIG_BACKUP_ENABLE` | `configBackup.enable` | Enable automatic config.js backup (default: every 24 hours) | `true` |
| `CONFIG_BACKUP_RETENTION_DAYS` | `configBackup.retentionDays` | Config backup retention period in days (auto cleans up expired backups) | `7` |
| `CONFIG_BACKUP_DIR` | `configBackup.dir` | Backup directory, relative to data; empty uses data/backups | `empty` |
| `SNAPSHOT_BACKUP_PATH` | `snapshot.backupPath` | Custom storage path for playlist snapshots (absolute or relative to `./data`) | - |
@@ -80,6 +80,9 @@ Defaults below refer to code defaults. Bundled configuration, saved values and i
| `SYSTEM_ALLOW_UNSAFE_VM` | `system.allowUnsafeVM` | Allow VM mode custom source scripts (note security risks) | `false` |
| `LX_USER_` | `users` | Create account using a password string | — |
+Cache cleanup applies only to `cache/` (including subdirectories), never to `music/`. Each user at each location (working directory and data directory) has an independent size quota. Cleanup runs after downloads, on startup, after changing cleanup settings, and every 12 hours. Oldest access/modification time is used; same-name audio and lyrics are grouped, active backups are protected, and indexes/partial files are excluded. Set `cache.maxAgeDays` in config.js or the admin UI (default 14); 0 disables expiration only, not size-based cleanup.
+
+
Boolean values are case-insensitive: `true/1/yes/y/on` and `false/0/no/n/off`.
`LX_USER_` accepts a password string. Use user management for advanced account settings; JSON in that environment variable is not an advanced-account configuration format.
@@ -105,7 +108,7 @@ Keep `subsonic-local-registry.json` and `subsonic-local-state.json` with user da
Playlist snapshots restore playlists; configuration backups restore configuration. Neither substitutes for audio backups. Configuration backups default to a seven-day retention period under the data directory's `backups/` folder.
-WebDAV and OpenList use the data types selected in **System settings → Cloud backup scope**. Startup restore is off by default and requires `backup.webdav.restoreOnStartup=true`. `backup.webdav.types` and `backup.openlist.types` independently select each destination's scope. Music means `data/music`; cache and external libraries are excluded. See [Cloud backup and recovery](./backup-recovery.md) for provider behavior, compatibility and restore steps.
+WebDAV and OpenList use the data types selected in **Backup management → Cloud backup → Backup scope**. Startup restore is off by default and requires `backup.webdav.restoreOnStartup=true`. `backup.webdav.types` and `backup.openlist.types` independently select each destination's scope. Music means `data/music`; cache and external libraries are excluded. See [Cloud backup and recovery](./backup-recovery.md) for provider behavior, compatibility and restore steps.
For container updates:
@@ -175,3 +178,50 @@ Paths belong to the server's environment: host paths for source runs, container
- WebDAV: check remote namespace, exclusions, recovery logs and effective settings.
See [deployment](./getting-started.md) and [operations](./operations.md).
+
+## Backup configuration keys (admin UI or config.js)
+
+Backup settings live in **Backup management**: Local backups holds configuration/snapshot settings, Cloud backup holds connections, scopes, schedules and ZIP retention, and the OpenList playback cache card below its connection settings in Cloud backup holds user cache rules. See [Backup and recovery](./backup-recovery.md) for instructions.
+
+These are configuration keys, **not automatically supported uppercase environment variables**. There are currently no mappings for `OPENLIST_*`, `WEBDAV_RETENTION_*`, `CONFIG_BACKUP_INTERVAL_HOURS` or `CONFIG_BACKUP_SCHEDULE_TIME`. Save in the admin UI or edit the active `config.js`. Existing variables listed above, including `WEBDAV_*`, `CONFIG_BACKUP_ENABLE`, `CONFIG_BACKUP_RETENTION_DAYS` and `CONFIG_BACKUP_DIR`, remain supported.
+
+| Key | Default | Purpose |
+| --- | --- | --- |
+| `maxSnapshotNum` | `10` | Shared maximum snapshot count; users can override it |
+| `configBackup.enable` | `true` | Automatic local configuration history |
+| `configBackup.intervalHours` | `24` | Configuration backup interval, hours |
+| `configBackup.scheduleTime` | `'00:00'` | Server-local anchor; empty starts intervals at startup/rescheduling |
+| `configBackup.retentionDays` | `7` | Configuration history retention, days |
+| `configBackup.dir` | `''` | Empty uses the data directory's `backups` |
+| `snapshot.backupPath` | `''` | Additional per-user snapshot location |
+| `backup.webdav.types`, `backup.openlist.types` | All five types | Independent scopes shown below |
+| `backup.webdav.mirrorDeletes` | `false` | Propagate local deletion to tracked incremental counterparts |
+| `backup.webdav.restoreOnStartup` | `false` | Restore and overwrite selected local data before every startup |
+| `webdav.retentionCount` | `5` | Keep 1–1000 ZIPs, always retaining the newest |
+| `webdav.retentionDays` | `0` | ZIP age limit, 0–3650 days; 0 disables age-based removal |
+| `openlist.enable` | `false` | Enable OpenList backup and user cache rules |
+| `openlist.endpoint` | `''` | OpenList API URL |
+| `openlist.token` | `''` | OpenList API token |
+| `openlist.username`, `openlist.password` | `''` | Login credentials when not using a token |
+| `openlist.root` | `'/lx_music'` | Path within an existing OpenList mount, not a physical disk path |
+| `openlist.externalUrl` | `''` | External address; empty uses endpoint |
+| `openlist.syncInterval` | `60` | Backup/cache-rule check interval, minutes |
+| `openlist.verifyInterval` | `5` | Background status check interval, minutes; not scheduled full-body SHA-256 verification |
+| `openlist.historyRetentionDays` | `7` | Cloud history retention, days |
+| `openlist.userBackups` | `'[]'` | Advanced user cache rules as a JSON-array string; edit through the UI |
+| `openlist.includeNonAudio` | `false` | Include existing cache lyrics/artwork; does not change ordinary music scope |
+| `openlist.playback.enable` | `false` | Allow registered OpenList files as playback fallback; player/Subsonic options also apply |
+
+```js
+module.exports = {
+ // Merge into existing settings; do not replace unrelated configuration.
+ 'backup.webdav.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
+ 'backup.openlist.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
+ 'backup.webdav.mirrorDeletes': false,
+ 'backup.webdav.restoreOnStartup': false,
+ 'webdav.retentionCount': 5,
+ 'webdav.retentionDays': 0,
+}
+```
+
+An empty array selects no data. Explicit types take precedence over legacy `webdav.excludeMusic`. `webdav.excludeCache` remains a compatibility key; ordinary backups always exclude cache. Legacy `openlist.backupSystemConfig` applies only without explicit types; use `backup.openlist.types` for new settings.
diff --git a/docs/en/guide/operations.md b/docs/en/guide/operations.md
index 3722f9d7..7abf520c 100644
--- a/docs/en/guide/operations.md
+++ b/docs/en/guide/operations.md
@@ -48,7 +48,7 @@ Application rollback and data rollback are separate. An older binary may not und
Preserve user state together with audio. Subsonic local registry/state files retain IDs, favorites and playlist relationships. Moving or renaming audio externally can assign it a new identity.
-WebDAV incremental checks default to 60 minutes (`SYNC_INTERVAL`); full backup intervals default to 24 hours (`BACKUP_INTERVAL`). Startup with WebDAV configured attempts remote recovery and then reloads configuration/accounts; recovery is not limited to a first start with empty data.
+WebDAV incremental checks default to 60 minutes (`SYNC_INTERVAL`); full backup intervals default to 24 hours (`BACKUP_INTERVAL`). Configuring WebDAV does not enable automatic recovery. `backup.webdav.restoreOnStartup` defaults to false. When explicitly enabled, every startup restores selected data and reloads configuration/accounts before opening the service, not just the first empty start. Restart after manual restoration. See [Backup and recovery](./backup-recovery.md) for instructions.
## Reverse proxy
diff --git a/docs/en/guide/subsonic-local-library.md b/docs/en/guide/subsonic-local-library.md
new file mode 100644
index 00000000..793bd40f
--- /dev/null
+++ b/docs/en/guide/subsonic-local-library.md
@@ -0,0 +1,96 @@
+# Subsonic local music library
+
+Subsonic clients can access the current account's server music files, cache and custom music directory. This page covers directory scope, playlist settings, media access and persistent data. See [Subsonic protocol support](../api/subsonic.md) for endpoint coverage.
+
+## Setup and directory scope
+
+1. Select the account's persistent storage location in the console. For NAS storage, enable its custom music directory and ensure the server process can read it.
+2. Enable the required local playlist entries under **Subsonic settings** and save.
+3. Select the **NAS local music (storage + custom directory)** music folder in the client to browse local albums, artists, genres and tracks. Directory browsing also provides a NAS entry.
+4. After adding or updating files, use the client's scan action to call `startScan` and refresh the index.
+
+| Account storage setting | Built-in library scope |
+| --- | --- |
+| `data` | The account's `music` and `cache` under the data directory |
+| `root` | The account's `music` and `cache` under the program root |
+| Enabled custom directory | Accessible files in that directory, in addition to built-in storage; the same physical file is included only once |
+
+A temporary directory switch in the web file manager does not change Subsonic's library scope. Changing account storage or disabling the custom directory changes subsequent requests. Saved track IDs cannot bypass the current access scope.
+
+## Local playlist settings
+
+The console's **Subsonic settings → Local playlists** section provides:
+
+| Setting | Default | Behavior |
+| --- | --- | --- |
+| `subsonic.localPlaylist` | `true` | Expose the read-only local-tracks playlist for the account; disabling it hides both local and custom-directory playlists |
+| `subsonic.customPlaylist` | `true` | Expose a separate custom-directory playlist when the parent switch and the account's custom directory are enabled |
+| `subsonic.cachePlaylist` | `false` | Include the selected storage location's `cache` tracks in the local playlist; otherwise include only `music` |
+
+The server places local playlists near the beginning of the list; client-side sorting takes precedence in the client. Enabled entries remain available even when empty. Disabling the parent switch retains the child settings.
+
+Custom-directory tracks are displayed separately from built-in tracks. If the custom directory points to built-in `music` or `cache`, files belong to built-in storage first and are not duplicated in the custom playlist. Cache tracks remain subject to the cache-playlist switch. These switches control playlist presentation, not the complete library used by search, albums and ratings.
+
+## Browsing, search and scans
+
+- Explicit NAS-folder selection uses the local library for album browsing and related endpoints. Default newest/random recommendations still use the online recommendation pool and do not automatically include NAS albums.
+- `local:query` searches local files only. Ordinary searches follow server search settings; explicit online searches use the online branch.
+- The index reads track number, disc number, album artist, year and genre. Albums are grouped by directory, album name and album artist to keep versions in different directories separate.
+- Results are cached for 30 seconds while directory scope remains unchanged. `startScan` forces a refresh; `getScanStatus` returns scan state, track count and failure information.
+
+## Playback, artwork and lyrics
+
+Local tracks support details, streaming, downloads and HTTP Range requests. Track and album durations are returned as integer seconds while precise durations remain internal. File sizes are actual byte counts.
+
+With server transcoding enabled and ffmpeg available, local playback supports format conversion, bitrate limits and time offsets. See [transcoding and media limits](../api/subsonic.md#transcoding-and-media-limits).
+
+### Lyric sources
+
+Lyrics are resolved in this order:
+
+1. A non-empty external LRC file with the same basename.
+2. Embedded audio lyrics.
+3. The web player's server-side lyric cache.
+4. The linked music platform's online lyric endpoint.
+
+Online fallback uses linked platform IDs only; it does not guess another track by title. Requests have timeouts, share concurrent work per account and briefly cache successful results. Structured lyrics can include original, translated and transliterated text; ordinary lyrics follow the translation setting. Multiple timestamps, integer-second timestamps and offsets are supported. Word-timed content is not additionally converted to OpenSubsonic's word-level format.
+
+### Artwork sources
+
+Local tracks use embedded or directory artwork and accept client-added `al-`, `ar-`, `tr-`, `sg-` and `mg-` prefixes. Access remains restricted to the current account's files.
+
+Ordinary user playlist lists and details return a consistent artwork token. Resolution prefers playlist network artwork, then attempts to obtain missing artwork for a linked online playlist, then falls back to the first track's artwork, including local artwork. Empty playlists without artwork return empty content. Resolution is account-scoped.
+
+## File identity and platform links
+
+Each local file has an independent `local_` ID. Linking an online platform does not change that ID or rewrite audio tags. Responses expose local identity and link status through `lxStorage=local`, `lxLinked` and `comment`; presentation depends on the client.
+
+Local and online tracks are not automatically merged by title. Identity is stored by physical path: updating a file at the same path or linking a platform retains its ID; externally moving or renaming it creates a new file identity, without fingerprint tracking. Tracks outside the current scope disappear from results, but playlist references and favorites remain and can recover when the original scope is restored.
+
+## Favorites and playlist synchronization
+
+| Operation | Storage and synchronization |
+| --- | --- |
+| Create a purely local playlist | Stored on the server only; excluded from official LX client sync |
+| Create a mixed playlist | Online tracks enter the native playlist; local members and mixed order are stored separately |
+| Resynchronize an online playlist | Online members follow synchronization changes; local members remain; new online members are appended to the combined list by default |
+| Remove a track by position | Resolve the position against the mixed list visible to the client |
+| Delete a mixed playlist | Remove the native online playlist and local supplemental state, without deleting audio files |
+| Favorite a local track | Use `star` / `unstar`; directly adding local members to the built-in favorites playlist returns guidance to use favorites |
+
+If another synchronization operation deletes the native playlist, local supplemental members can still appear as a server-side playlist. Playback history, queues and bookmarks can resolve local tracks.
+
+## Backup and troubleshooting
+
+Back up these files with the account's user data:
+
+| File | Contents |
+| --- | --- |
+| `subsonic-local-registry.json` | File identities and metadata; not disposable cache |
+| `subsonic-local-state.json` | Local favorites, purely local playlists and mixed-playlist supplemental state |
+
+For a missing library, check account storage selection, custom-directory enablement and server filesystem permissions, then scan and inspect `getScanStatus`. For missing cache tracks in the local playlist, check `subsonic.cachePlaylist`.
+
+Some clients use track `size` to decide whether to load playlist artwork. Online tracks prefer source-provided byte counts, otherwise estimate from bitrate and duration; unknown duration without a known size still returns 0. Estimates are not used for actual audio Content-Length or Range responses.
+
+Large-library scanning depends on directory access and tag-reading performance. Transcoding depends on ffmpeg and server resources. Artwork, lyrics, scan progress and other controls also depend on client implementation.
diff --git a/docs/en/guide/sync-server.md b/docs/en/guide/sync-server.md
index bdbce5c1..592c656e 100644
--- a/docs/en/guide/sync-server.md
+++ b/docs/en/guide/sync-server.md
@@ -24,7 +24,7 @@ Do not use `/admin`, `/rest` or the player access password for LX sync. Review m
### Subsonic clients
-Compatible clients include Yinliu, Feishin and Symfonium. The server implements Subsonic/OpenSubsonic endpoints for library browsing, search, playback and related features; the UI and available features vary by client.
+Compatible clients include Yinliu, Feishin and Symfonium. The server implements Subsonic/OpenSubsonic endpoints for library browsing, search, playback and related features; the UI and available features vary by client. See [Subsonic protocol support](../api/subsonic.md) for coverage and limits.
| Field | Value |
| --- | --- |
@@ -38,7 +38,7 @@ By default Subsonic shares the main port. Separate listening requires `subsonic.
Online search uses `subsonic.onlineSearch` and `subsonic.onlineSearchMode`: `fallback` supplements insufficient local results; `merge` combines local and online results; `local_only` stays local. Query prefixes include `wy:`, `tx:`, `kw:`, `kg:`, `mg:`, `online:` and `local:`. Online playback still needs a working custom source.
-Local library selection follows the account's saved storage location plus its enabled custom directory. A temporary browsing switch in the web UI does not change that library. Read-only local/custom playlists can be exposed separately. Pure local playlists stay on the server; only the online portion of mixed playlists enters LX native sync.
+Local library selection follows the account's saved storage location plus its enabled custom directory. A temporary browsing switch in the web UI does not change that library. Read-only local/custom playlists can be exposed separately. Pure local playlists stay on the server; only the online portion of mixed playlists enters LX native sync. See [Subsonic local music library](./subsonic-local-library.md).
@@ -64,6 +64,7 @@ For scripts, use the personal Token management UI to create, name, expire, disab
| --- | --- |
| Local music folder | Explicit local-folder selection uses the local library; the default recommendation pool is not the entire NAS library |
| `subsonic.localPlaylist` / `subsonic.customPlaylist` | Read-only local and separate custom-directory playlists; custom depends on the local switch and account directory enablement |
+| `subsonic.cachePlaylist` | Include cache-directory tracks in the local playlist, default false; does not shrink the complete library used by other endpoints |
| `subsonic.cacheOnPlay` | Save online audio during playback, default false |
| `subsonic.playCacheFirst` | Prefer an existing server file, default false; server carries the file traffic |
| `subsonic.quality.*` / `subsonic.source.*` | Quality and platform/script candidate selection, not guaranteed availability |
@@ -74,7 +75,7 @@ For scripts, use the personal Token management UI to create, name, expire, disab
Local tracks have independent IDs; associating a platform does not make them online tracks. Updating at the same path generally retains identity, while moving/renaming externally can assign a new ID. Mixed playlists preserve local members/order separately. Deleting a playlist does not delete its audio files.
-Artwork, word lyrics, scan display and folders depend on both protocol and client support. A web setting does not imply an equivalent control exists in every third-party client.
+Artwork, word lyrics, scan display and folders depend on both protocol and client support. A web setting does not imply an equivalent control exists in every third-party client. See [Subsonic local music library](./subsonic-local-library.md) for details.
## Console modules
@@ -84,7 +85,7 @@ Artwork, word lyrics, scan display and folders depend on both protocol and clien
| Users | Create, rename, disable and assign directories | Distinguish removing an account from deleting its data |
| Data/snapshots | Inspect playlists and restore past state | Verify account and timestamp before restoring |
| Configuration | Paths, authentication, proxies, cache and Subsonic | Environment/WebDAV recovery can affect the next start |
-| WebDAV | Test, sync, back up and restore | Incremental/full backups have separate intervals |
+| Backup management | Local backups, WebDAV, OpenList, recovery and verification | Connections/scopes in Cloud backup; preview recovery and restart when requested |
| File manager | Upload, edit, move and archive server files | Administrative filesystem access, not ordinary library permission |
| Logs/maintenance | Inspect errors, reload or restart | Changed ports/paths require a new connection address |
diff --git a/docs/en/guide/web-player.md b/docs/en/guide/web-player.md
index 927c88ca..f040bce6 100644
--- a/docs/en/guide/web-player.md
+++ b/docs/en/guide/web-player.md
@@ -1,7 +1,66 @@
-# Web player and local music
+# Playback, lyrics and local music
The player defaults to `/`, configurable with `PLAYER_PATH`. Player access authentication opens the player; personal login selects playlists, settings and directory permissions. A valid browser session normally avoids repeated access-password prompts.
+
+## Playback sources and resolution order
+
+**Prefer existing server audio** (formerly prefer cached songs) includes both the current user's `music` downloads and `cache` playback files. Matching uses indexed platform, song ID and quality; scan/link manually copied files or play them directly from the local library. Users are isolated, with `_open` for public files.
+
+| Files | Runtime-root mode | DATA_PATH mode |
+| --- | --- | --- |
+| Downloads and adjacent LRC | `/music//` | `/music//` |
+| Playback cache and adjacent LRC | `/cache//` | `/cache//` |
+
+The persistent per-user serverCacheLocation selects the base. A temporary local-library browsing location does not change the persistent playback base. Custom libraries use a separately configured per-user directory.
+
+### Web audio
+
+Local-library entries directly use their file URL for music, cache or custom directories. Online songs resolve in this order:
+
+1. With prefer-existing-server-audio enabled, probe the user's music/cache. Exact-quality downloads precede cache; other qualities may be fallback candidates.
+2. On a local miss, use registered OpenList user cache/download files if the backend playback-source master switch permits it.
+3. Reuse a browser-cached URL when enabled. URLs can expire and are not offline audio copies.
+4. Resolve an online source-script URL and deliver directly or through the configured proxy.
+
+Cache-song-files controls background writes; prefer-existing-server-audio controls existing-file reads. Disabling writes does not disable reads. Background writes pass the only-download-mode setting: enabled writes to music, otherwise cache, under the selected base. Foreground playback does not wait for the full background download.
+
+### Web lyrics
+
+Prefer-server-local-lyrics is independent of save-lyrics-files:
+
+| Setting | Effect |
+| --- | --- |
+| Prefer server-local lyrics | User music/cache LRC, then browser lyrics, then online lyrics |
+| Save lyric files | Write standalone LRC during lyric fetching/background audio saving; keep it adjacent to existing audio |
+| Browser lyric cache | Store/reuse text in this browser |
+| Embed lyrics during download | Write audio lyric tags independently of standalone LRC |
+
+Turning local preference off skips standalone-LRC lookup in both frontend and backend. Unlinked local tracks can still use embedded lyrics. Local entries support adjacent LRC in music, cache and custom directories even without a platform identity. Roots are determined by the authenticated user; arbitrary paths and cross-user traversal are not permitted. Audio and lyrics resolve independently.
+
+### Subsonic
+
+Online IDs such as tx_... probe local music/cache when subsonic.playCacheFirst is on. OpenList follows only when the Subsonic OpenList option and openlist.playback.enable are both on; online sources follow on a miss. Turning cache-first off skips both local and OpenList for online IDs. Delivery is normally a redirect, or server-side transcoding when required and enabled. Online-ID lyrics now try local LRC before the SDK, including indexed custom-library lyrics.
+
+Local IDs such as local_... are handled separately. The catalog includes the selected cache/music base and enabled custom directory regardless of cache-first. Missing local audio is an error, not a silent replacement with another online track. Lyrics try adjacent LRC, embedded lyrics, indexed local lyrics, and an online SDK fallback if linked to a platform.
+
+subsonic.cacheOnPlay controls background cache writes after online playback resolution, not existing-file reads. Switching tracks can cancel that task. The online download branch caches even when cacheOnPlay is off. Subsonic background caching explicitly fetches lyrics independently of Web save settings. Early local/OpenList hits avoid duplicate online caching.
+
+### Cloud sources
+
+In the admin UI, **Backup management → Cloud backup → OpenList playback cache → Allow OpenList as a playback source (local first)** is the shared master switch for Web and Subsonic. Web also requires its server-audio preference; Subsonic requires its own cache-first and OpenList options. Saving upload-and-delete rules no longer silently enables playback settings. Legacy enabled Subsonic settings migrate only when the new field is absent; explicit false is respected.
+
+A local database matches user/song/quality before querying one remote file, without directory scans per play. Concurrent lookups are coalesced, download addresses reused for up to 30 seconds, and metadata requests limited to four seconds each. Failed lookups fall back to online sources. Web uses existing proxy preferences; Subsonic uses direct redirects, proxy delivery or transcoding.
+
+Only registered user cache/download backups with the corresponding per-user directory rule enabled are direct playback candidates. Enabling only the ordinary five backup scopes is insufficient. Ordinary _data backups and historical objects are not automatically a music catalog. The shared downloader can also retrieve an OpenList backup into local storage, gated by the master setting independently of Subsonic-only preferences.
+
+WebDAV is not a live audio/lyrics fallback. Restore files manually or through explicit startup restore, then use local playback. See [Cloud backup and recovery](./backup-recovery.md).
+
+### LRC and backup scope
+
+Writing LRC and uploading LRC are separate. Disabling Web standalone-lyric saving stops implicit LRC creation during audio caching without deleting existing files. Ordinary music backups include existing audio, LRC, covers and indexes under data/music. openlist.includeNonAudio only controls the separate user-cache attachment pipeline. Ordinary backups exclude cache, cwd/music and external custom libraries.
+
+
## Playback, lyrics and appearance
The responsive player includes a queue, quality selection, playback speed, sleep timer, full-screen lyrics, themes and player backgrounds. Lyrics can include translations, romanization and word highlighting when supplied by the source. Lyric cards offer image layouts for sharing selected lines.
@@ -100,3 +159,41 @@ Favorite artists/albums and dislike rules are stored separately. Subsonic can hi
Server downloads, sync-download and matching run in the backend; browser downloads run on the visiting device. Closing the page does not cancel submitted server work. Inspect task status and use its stop/cancel action rather than refreshing to stop it.
See [operations](./operations.md) for diagnosis and [API reference](../api/reference.md) for fields.
+
+### Song ratings
+
+After signing in to a local account, rate songs from 1 to 5 stars or clear ratings using the player, song menus, artist detail, charts, local music, and custom directories. Favorites → My ratings provides a searchable rating collection.
+
+Web and Subsonic share per-user `ratings` and `ratingOrigins` in `subsonic-meta.json`. Rating snapshots live in `rating/snapshot/`; `rating/songs.json` stores essential Web-submitted song descriptions without playback URLs or authentication parameters. Ratings do not modify playlists or audio tags. Current ratings and song descriptions belong to the user-data backup scope; snapshots belong to the snapshots scope.
+
+Online songs use source-specific IDs, preferring QQ song mids. Unlinked local files use stable registry IDs shared by Web and Subsonic. Once linked, the online ID becomes authoritative: reads and writes share the online version and other linked copies. If the online version already has a rating, it wins and the local score record is deleted. Otherwise the local score, origin and history move to the online ID, and the local entry is deleted. Relinking selects the new online ID; titles are never used to merge ratings. Files recognized as new after importing, rebuilding, or moving are not automatically matched to old ratings by name.
+
+Low ratings only add dislike rules when linking and the threshold allow it. Raising or clearing ratings does not delete dislike rules. Direct Web ratings record `web`; Web rating-to-dislike linking records `web-rating-link`; direct Subsonic ratings record `subsonic`. Partial snapshot/link failures are reported after the rating is saved. Lists load ratings in batches and refresh periodically or when returning to the page.
+
+
+### Rating identity, reconciliation and backups
+
+Unlinked local songs use local IDs. Linking keeps an existing online score or migrates the local score when the online version is unrated, then removes the local score record. The dialog shows the target ID and linked copy count; Admin Data View no longer has rating status labels or filters.
+
+The two supplement actions remain additive. Saving settings does not delete historical rules. To apply a changed threshold, select the user and choose Recalculate by threshold, preview the changes, then execute. Only invalid reasons traceable to a specific rating ID are removed. Rules with manual or other valid reasons remain; historical rules without precise provenance are preserved. Disabling linking does not trigger cleanup.
+
+My ratings → Export rating backup includes scores, origins, song descriptions and local link references, without audio files. New rating snapshots also contain descriptions. Importing/restoring a rating backup in the admin interface restores scores and available descriptions while preserving current favorites and file links. Link references are for comparison: backup paths never locate or move files or revert associations. A score-only backup cannot recreate missing covers or titles.
+
+Recommendation exclusion uses the same policy for daily, random and similar tracks: matching dislike rules and scores at or below the threshold are excluded. This is independent of ordinary list hiding and automatic linking.
+
+Admin rating and dislike dashboards support platform, action origin, cover availability, record age, combined keywords and score filters. Platform options show user totals, including zero-count platforms. Click the score distribution to filter, change page size, inspect records or export the current selection. Rating statistics follow platform, origin, age and keyword filters; type and score further narrow the list.
+
+### Per-user dislike auto-rating
+
+Settings → Logic → Dislike auto-rating is saved to the current account on the server and defaults to off. Adding a Web song dislike supplements 1 star only when unrated; existing ratings remain. Other users and the low-score threshold do not control this preference. The global reverse-link switch has been removed. Admin backfill is an independent, previewable operation and works with auto-rating off or threshold zero. Sources are labeled Web manual rating, Subsonic manual rating, Dislike automatic rating, and Admin backfill. The preference is stored in each user’s `rating/preferences.json`; score backup restore does not change this preference.
+
+Playlist detail supports direct rating. Open rating collections refresh periodically, on returning to the page and after local rating/dislike actions, preserving filters and list scroll. Each entity retains up to 30 recent score changes, including clearing, backfill and restore; identical submissions keep origin timestamps. Old history is not fabricated. Failed auto-rating reports partial completion while retaining the dislike. Recoverable restore write errors roll back files; this does not guarantee cross-file atomicity during process termination or power loss.
+
+
+### Metadata repair and synchronization status
+
+Identical scores keep their origin and timestamp while allowing available metadata to be repaired. Direct ratings and automatic/admin backfill share one write service; backfill only supplements missing scores. Resolved Subsonic metadata is stored in the user's `rating/songs.json`, reducing dependence on runtime caches. Favorite operations retain rating origins and history.
+
+My ratings and admin Ratings show pending work with Retry failed items. The user's `rating/sync-status.json` tracks backfill writes, catalog writes, unresolved metadata and snapshot failures. Retries preserve newer scores; metadata resolution still depends on provider availability. Corrupt primary rating files block writes and exports without replacing the original. Corrupt status files request manual inspection instead of being cleared.
+
+Admin previews separate additions, already-rated skips, duplicate linked identities and resolution failures. Saved scores with failed snapshots report partial completion. Version checks avoid unnecessary full refreshes. The Web collection average follows the current selection; the distribution follows the other filters, so users can switch score levels.
diff --git a/docs/guide/backup-recovery.md b/docs/guide/backup-recovery.md
index e02160fc..61d5443a 100644
--- a/docs/guide/backup-recovery.md
+++ b/docs/guide/backup-recovery.md
@@ -1,62 +1,190 @@
-# 云端备份与恢复
+# 备份与恢复
-在后台「系统设置 → 云端备份范围」中,按数据类型分别选择 WebDAV 和 OpenList。两列互相独立:同时勾选表示保存两份副本,不会在两个云端之间同步。服务连接和自动执行仍由各后端的启用开关控制。
+要防止误删歌单、服务器损坏或迁移时丢失数据,先选择备份内容,再选择保存位置。可以只保存在本机,也可以使用 WebDAV、OpenList 保存云端副本。两种云端独立工作,同时启用会保存两份,不会互相同步。
+
+## 先选适合你的方式
+
+| 需要做什么 | 使用什么功能 |
+| --- | --- |
+| 找回以前的歌单或不喜欢列表 | 歌单、不喜欢列表的历史快照 |
+| 找回以前的服务器设置 | 本地配置历史备份 |
+| 下载一份数据留存或迁移 | 备份管理中的本地 ZIP 导出、导入 |
+| 自动把数据复制到网盘 | WebDAV 或 OpenList 数据备份 |
+| 把播放缓存上传到 OpenList,或上传后节省本地空间 | OpenList 的高级用户播放缓存规则,需单独配置 |
+
+配置历史备份只保存配置,不包含全部用户数据和歌曲。歌单快照也不包含音频文件。需要恢复整套数据时,应选择对应的数据备份范围,并单独保管未纳入范围的外部音乐目录。
## 哪些内容会备份
-| 类型 | 文件范围 | 恢复位置 |
+在后台 **备份管理 → 云端备份 → 备份范围** 分别选择 WebDAV 和 OpenList 的内容,保存后生效。每一列独立选择;全部关闭表示该端不备份这些数据,但不会删除已经上传的文件。
+
+| 界面选项 | 包含的内容 |
+| --- | --- |
+| 歌曲与歌词 | 数据目录 `music` 下的音频、LRC 歌词、封面和音乐索引 |
+| 用户音源文件 | 音源脚本、顺序与状态文件,以及上传的用户文件 |
+| 歌单与不喜欢快照 | 歌单和不喜欢列表的历史快照,包括已配置的外部快照目录 |
+| 用户数据 | 用户设置、播放记录、收藏等其他用户文件 |
+| 系统与账户 | 服务器配置、账号列表及数据目录根部的系统 JSON 文件 |
+
+**常规数据备份不包含 `cache`、日志、临时文件、本地备份包和外部自定义音乐目录,也不跟随目录链接。** 程序运行目录下的独立 `music` 目录也不在此范围内。若实际数据目录不是默认 `data`,上述路径以当前数据目录为准。
+
+新备份扫描、OpenList 新历史清单及完整校验排除 `test-evidence` 测试目录。已有云端文件不会自动删除,旧历史版本仍可恢复。OpenList 手动删除失败时保留失败记录及本地文件,可选择原记录重试;部分远端文件可能已删除,重试会跳过不存在的文件。删除期间暂停备份及完整校验。校验结果表显示当前版本的全部最近结果,不受上方任务校验范围限制。
+
+OpenList 的高级播放缓存可以按用户单独上传 `cache`;它与这里的常规备份不同。系统与账户备份可能包含连接凭据,应保存在自己控制的位置。
+
+
+### 程序根目录和数据目录如何区分
+
+以下路径相对于程序运行目录,假设使用默认 `DATA_PATH=data`:
+
+| 位置 | 是否进入常规 WebDAV / OpenList 数据备份 |
+| --- | --- |
+| `cache/` | 不备份,程序根目录缓存 |
+| `music/` | 不备份,程序根目录中独立于数据目录的音乐 |
+| `data/cache/` | 不备份,数据目录播放缓存 |
+| `data/users/用户/cache/` | 不备份,用户目录缓存;其他层级的 cache 同样排除 |
+| `data/music/` | 选中并保存“歌曲与歌词”后备份,包括歌曲、已有 LRC、封面和音乐索引 |
+| 外部目录,如 `D:/音乐/` | 不备份,需单独保管 |
+
+修改 `DATA_PATH` 后,歌曲范围是 **实际数据目录下的 music**。例如数据目录为 `D:/LXData`,范围就是 `D:/LXData/music`。如果直接把程序运行目录设为数据目录,该位置的 `music/` 就可以被选中备份;不能简单地认为所有“根目录 music”都排除。缓存仍固定排除。
+
+范围选择与服务启用是两件事:保存 WebDAV 列的范围后,还需启用并配置 WebDAV 才会执行云端备份。本地 ZIP 导出和导入也使用这列范围,但不需要启用 WebDAV。OpenList 高级播放缓存使用独立用户规则,可单独处理缓存,不改变常规备份的上述排除范围。
+
+## 配置本地备份
+
+在 **备份管理 → 本地备份** 设置快照数量、本地配置备份和存储路径。
+
+- 本地配置备份默认开启,周期为 24 小时,保留 7 天,默认写入数据目录的 `backups`。
+- “锚点时间”默认 `00:00`,表示按服务器本地时间安排周期;留空则从启动或重新设置后按周期计时。
+- 配置备份目录留空使用默认位置。相对路径基于数据目录,绝对路径直接使用。
+- 快照路径留空使用各用户原有快照目录;设置额外路径后,按用户隔离存放。
+
+在 **备份管理** 中可以导出或导入本地 ZIP,无需开启 WebDAV。ZIP 使用 WebDAV 列已经保存的数据范围;导出前先确认没有漏选需要的内容。
+
+## 使用 WebDAV 备份
+
+1. 在 **备份管理 → 云端备份 → WebDAV** 填写地址、用户名和密码,并启用服务。
+2. 设置增量目录和 ZIP 目录,默认分别为 `/lx-sync`、`/lx-sync-backups`。
+3. 保存配置,在当前页测试连接。测试只检查已保存目录是否可读取,不创建文件;测试通过不代表上传、删除权限均已验证。
+4. 执行一次文件同步或全量 ZIP 备份,查看结果。建议先用少量文件,并实际恢复到独立测试实例确认可用。
+
+增量备份只上传发生变化的文件,默认每 60 分钟检查。ZIP 是所选数据的一份归档,默认每 24 小时检查并在数据变化时创建;手动全量备份可强制创建新归档。
+
+### 两个容易混淆的开关
+
+| 开关 | 关闭时(推荐默认值) | 开启时 |
| --- | --- | --- |
-| 歌曲与歌词 `music` | `data/music/**`,包括歌曲、歌词、封面、`music_index.json` | 当前实例的 `data/music`,保留相对目录 |
-| 用户音源文件 `userFiles` | `data/users/source/**`、`data/source/**`、`data/uploads/**` | 当前实例的同名相对目录 |
-| 快照 `snapshots` | 用户歌单和不喜欢列表的快照正文及 `snapshotInfo.json`;包括无扩展名文件 | 原相对目录;外部歌单快照按本机 `snapshot.backupPath` 定位 |
-| 用户数据 `userData` | `data/users/<用户目录>/**` 中除音源、快照及临时文件之外的数据 | 保留用户目录名及相对路径 |
-| 系统与账户 `system` | `config.js`、数据目录根部的 JSON 文件,如 `users.json`、`serverInfo.json` | 配置恢复到当前 `CONFIG_PATH`;JSON 恢复到当前数据目录 |
+| 本地删除文件时,也删除 WebDAV 增量副本 | 本地删除后,云端仍保留副本 | 下次增量备份删除已跟踪的对应云端文件;ZIP 历史不受影响 |
+| 每次启动前,用 WebDAV 备份覆盖本地所选数据 | 需要时手动恢复 | 每次启动先恢复,云端同名文件覆盖本地;恢复完成后才开放服务 |
+
+启动恢复适合明确需要云端作为恢复来源的部署。它不是“只在空目录第一次启动时恢复”,可能覆盖本地较新的数据;失败后进入维护状态,不会自动把本地数据反向上传。
+
+### 历史保留和上传流量
+
+ZIP 默认最多保留 5 份,保留天数默认 0(不限天数)。成功上传后按份数和天数清理,始终保留最新一份;增量目录不受 ZIP 保留策略影响。
+
+上传后检查响应和云端大小,不把整个文件再下载一遍。安全替换需要 WebDAV 服务支持 `MOVE`;若出现 MOVE 错误,应检查服务及代理是否支持该方法。大小一致只表示大小检查通过,不代表内容逐字节相同。
+
+## 使用 OpenList 备份
+
+1. 在 **备份管理 → 云端备份 → OpenList** 填写 OpenList 地址、令牌或账号信息。
+2. 根目录填写 **OpenList 中已经挂载的路径**,例如 `/local`。不要填写 Windows 主机路径 `E:\OpenList`;主机路径应在 OpenList 自己的存储挂载中配置。
+3. 启用、保存并测试连接,再执行“立即备份并同步缓存”。
+4. 在对应记录页查看上传结果。
+
+常规备份保存到根目录的 `_data` 下,并保留相对目录结构。例如根目录为 `/local`,歌曲可能保存在 `/local/_data/music/用户目录/歌曲.mp3`。实际 Windows 磁盘位置由 OpenList 的挂载配置决定。
+
+常规数据备份默认保留本地文件,本地删除不会自动让 `_data` 中的备份跟着删除。高级播放缓存则有独立的按用户规则和存储位置;只有明确选择“上传后删除本地”的规则,才会在严格校验后清理对应缓存。
+
+### “上传完成”和“内容校验”有什么区别
+
+“上传完成”表示上传成功且云端大小检查通过,不表示已比对完整内容。普通备份不会自动下载歌曲验证 SHA-256。记录列表主状态“已备份”与独立的“内容校验”筛选分开;校验筛选匹配当前备份指纹的历史结果。备份版本变化或后台发现云端变化后需重新校验,内部旧 verified 状态不能作为依据。上传后删除和恢复仍会执行各自的自动安全校验。
+
+需要检查内容时,打开 **备份管理 → 记录与校验 → OpenList → 内容校验**:
+
+1. 选择用户、数据类型和范围,先统计预计读取流量。
+2. 默认只检查未校验或版本变化的文件;怀疑网盘文件后来损坏时选择全部当前文件。
+3. 确认后手动启动,可在后台执行并随时停止。
+4. 查看内容一致、不一致、文件缺失或读取失败等结果。校验不会自动覆盖或修复云端文件。
+
+如果存储提供可用的 SHA-256,可直接比较;否则需读取云端正文,会消耗下载流量。旧记录缺少上传指纹时会明确提示,需要重新备份才能建立可比对记录。“上传后删除本地”仍需严格内容校验,因此可能读取云端音频和附件。
+
+## 筛选 OpenList 备份记录
+
+在“备份管理 → 记录与校验”选择 OpenList 文件备份记录。记录区域只有一套筛选:数据类型按钮、用户、存储类别、本地文件状态及排序;点击状态卡筛选对应状态,再次点击取消。状态卡数量随其他条件变化,当前已选条件会显示在面板底部,点击条件标签可单独移除。
+
+搜索支持空格分隔的多个关键词,所有关键词都需命中,可匹配文件名、用户、路径及已有歌曲元数据。“更多条件”提供路径、快照子类型和更新日期范围,日期包含起止当天,按浏览器本地时间解释。多个条件同时生效,没有匹配结果时可逐项移除或重置。筛选和分页复用当前记录;点击“刷新记录”获取最新状态,重新进入记录页也会刷新。不改变备份范围或实际文件。
+
+## 从备份恢复
+
+恢复方向是 **备份 → 当前服务器**,不会上传本地文件,也不会删除本地独有文件。
+
+### WebDAV
+
+1. 在当前实例确认数据目录、配置文件位置和外部快照路径,保存需要恢复的数据类型。
+2. 打开 **备份管理 → 数据恢复 → WebDAV** 的恢复窗口,选择增量目录或指定 ZIP。
+3. 选择“仅补缺失文件”或“覆盖同名文件”,建议保留恢复前副本。
+4. 先预览新增、覆盖、合并索引与跳过数量,再确认恢复。ZIP 预览需下载整个归档,确认后复用,不重复下载;取消或预览过期会清理该临时归档。
+5. 完成后重启服务,重新加载用户、歌单、音源与音乐索引。
+
+### OpenList
+
+在 **备份管理 → 数据恢复 → OpenList** 选择当前备份或可用的历史版本,选择范围和覆盖方式,扫描预览后启动。恢复记录可以查看进度,并对支持的任务继续、停止或回退。新实例无需原来的备份数据库,也能从云端数据备份枚举恢复。
+
+### 覆盖规则与失败处理
+
+- **仅补缺失文件**:保留已有文件,只补本地没有的内容。必要的音乐、快照索引按条目合并;普通 JSON 不做字段级合并。
+- **覆盖同名文件**:选中范围内的同名文件以备份为准,本地独有文件保留,不是清空整个目录。
+- **恢复前副本**:默认保留,可在恢复记录中回退;恢复后又被修改的文件会拒绝自动回退,避免丢失后续改动。确认不需要后可删除记录及副本。
+
+所有需要写入的文件会先下载暂存并校验,再开始替换本地文件。下载失败不会覆盖原文件;写入失败会尝试回滚。旧备份缺少内容指纹时仅检查大小并提示。恢复需要额外磁盘空间存放暂存文件和恢复前副本。
+
+恢复期间进入维护状态;手动恢复完成后需重启,刷新网页不能代替重启。若进程在写入时中断或回滚失败,先在恢复记录中处理回退,再重启恢复正常服务。
+
+## 常见问题
+
+**提示 storage not found?** 检查 OpenList 是否存在且已启用相应挂载,根目录是否与挂载路径一致。连接到 OpenList 不等于已经创建了可写存储。
+
+**为什么 LRC 也上传了?** “歌曲与歌词”包含数据目录 `music` 下已有的 LRC;高级缓存的附件开关只控制独立缓存规则。歌词生成与备份是两个步骤,关闭生成不会删除已有 LRC。
+
+**恢复后看起来没有变化?** 确认恢复来源、数据类型、用户目录和覆盖方式;“仅补缺失”会跳过已有文件。完成后重启服务,再查看结果。
+
+**WebDAV 与 OpenList 会互相同步吗?** 不会。两端各自保存副本,恢复时明确选择一个来源。
-`cache`、`runtime`、日志、临时文件、SQLite 工作库/WAL、已有本地备份包始终排除。不会跟随目录联接或符号链接,不会备份外部自定义音乐目录。歌曲备份不是整个服务器磁盘备份。
+配置键与默认值见 [配置说明](./configuration.md),脚本集成见 [备份接口](../api/reference.md#backup-api)。
-用户目录名称包含账户标识,恢复时需要保留名称。歌曲索引与音频归在同一类,避免只恢复索引而没有对应音频。系统和用户数据可能包含密码、令牌或设备信息,请使用自己控制的云端目录。
+### 记录状态与校验结果
-## 写入 config.js 的设置
+记录页主状态表示任务进度:待上传、待下载、传输中、已备份、需处理、失败或已暂停。启用范围中的本地文件大小或修改时间变化会提示待上传,后台按现有周期核对并处理;仅时间变化且内容相同的文件在后台确认后更新基线。服务或范围停用时显示已暂停。播放缓存按上传后删除规则清理本地,不等于删除云端备份。
-```js
-module.exports = {
- 'backup.webdav.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
- 'backup.openlist.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
- 'backup.webdav.mirrorDeletes': false,
- 'backup.webdav.restoreOnStartup': false,
-}
-```
+内容校验独立显示历史结果,不代表实时一致。本地有变化或传输等待期间不在列表突出旧通过标记。后台观察到云端变化后,旧通过结果显示需重新校验,并纳入默认校验范围;要查找同大小、同修改时间的云端内容损坏,应选择所有当前备份文件。刷新记录只检查本地元信息,不下载云端正文。本地、云端变化诊断在详情和高级筛选查看。校验页用户、类型也会限制结果列表。
-类型数组允许为空,表示该目的地不备份任何数据类型。后台保存时校验类型并写入实际使用的配置文件。取消勾选只改变之后的备份和恢复范围,不会删除云端已有文件。
+### 手动检查文件与上次检查时间
-`webdav.excludeCache`、`webdav.excludeMusic` 保留旧配置兼容接口。新的类型数组是范围选择的依据;缓存现在固定排除,不再提供相互冲突的界面开关。旧 `openlist.backupSystemConfig` 只用于未采用新类型设置的旧流程,不再作为新界面的第二个系统备份开关。
+在记录页点击“检查文件”,选择本地、云端或两者,以及已选记录、当前筛选结果或当前连接的全部记录。范围只包括记录主文件;绑定附件请使用内容校验入口,历史版本对象不在本次范围。检查存在性、大小、修改时间,不上传、不下载正文、不删除文件,不会生成 SHA-256 内容校验结果。云端逐项请求元信息,允许停止;已完成结果保留。
-## 两种云端的执行方式
+高级筛选显示当前匹配范围内“已检查 X/Y 项”和最近检查时间,悬停可看最早检查时间;这不是整轮全部成功的证明。每个文件详情有准确时间。本地时间随刷新记录重新检查,手动检查时间另外保留;云端结果按连接与备份版本保存,服务重启后仍可查看。后台检查不保证所有文件同时检查,进程中断的任务需重新启动。
-WebDAV 增量备份将所选文件按相对路径保存到 `webdav.syncPath`;ZIP 备份将相同范围打包保存到 `webdav.backupPath`。它们分别维护成功基线,增量上传不会让 ZIP 误判为没有变化,失败上传也会在下一轮重试。
+### Subsonic 评分与快照
-本地离线 ZIP 导出和导入复用 WebDAV 列已保存的范围,但无需启用 WebDAV,也不会访问云端。界面会明确说明这个范围,避免把部分导出误认为整个服务器的全量副本。本地配置每日历史副本仍由 `configBackup.*` 管理;它只保存配置,不替代上述数据备份。
+评分歌单开关仅控制虚拟歌单显示,不启用自动联动。两个联动开关独立控制方向,阈值为 0 时停止联动;联动只补缺失数据,提高或清除评分不删除不喜欢规则,取消不喜欢不删除评分,也不覆盖已有评分。1 星歌单并入不喜欢命中歌曲,保留已有真实评分。
-默认保留已上传的散文件,即使本地已经删除。只有显式打开 `backup.webdav.mirrorDeletes`,本地删除才会传播到散文件目录。镜像删除不会更改历史 ZIP。两种定时任务和恢复操作不会在同一个 WebDAV 实例内同时运行。
+评分快照恢复只替换评分,保留当前歌手和专辑收藏,兼容旧快照格式。评分快照及索引属于备份的「快照」范围;当前 subsonic-meta.json 属于「用户数据」,完整备份请同时选择两项。评分成功但不喜欢解析或写入失败会返回部分成功提示,可重试或手动补同步;两个存储不构成跨文件事务。缓存淘汰的已评分歌曲会尝试重新解析,源不可用时保留评分但暂时无法展示歌曲。
-OpenList 数据备份保存在 `/_data/<相对路径>`,默认只上传并保留本地文件。同步间隔由 `openlist.syncInterval` 控制;「立即备份 / 同步缓存」也会执行一次。SQLite 记录用于显示进度及重试,不是恢复的唯一依据:新实例没有旧数据库,也能枚举 `_data` 恢复文件。
+新不喜欢规则按来源去重和匹配;未指定来源时显式作用于全部来源。相同来源、歌曲身份不冲突的重复记录会补齐缺失字段,保留已有有效资料和原始显示名称。新不喜欢记录含 provenance,补全记录含 enrichedBy;评分来源存于 subsonic-meta.json 的 ratingOrigins。手动补同步先预览候选(包含在线缓存),执行时空解析计入失败,资料补全数单独报告。
-OpenList 高级永久播放缓存使用独立的用户规则和目录。它可以上传 `cache`、上传后清本地或用于 Subsonic 直链/代理交付,**不属于上面的数据备份范围**,默认不会因为勾选「歌曲与歌词」而启用。新界面的歌曲、快照、系统备份不再由这些用户缓存规则重复控制。
+配置变化不会重算历史数据。提高低分阈值后,手动补齐可增加符合条件的规则;降低阈值、设为0、关闭联动或清除评分不会撤销旧规则。匹配和显示选项仅改变后续过滤/展示结果。Web反向自动补1星仅限带来源和歌曲ID的单首歌曲,歌手和专辑不喜欢不会被误写成歌曲评分。
-上传后清理播放缓存之前会核对远端真实内容的 SHA-256;这一步需要从远端读取音频,会增加一次校验流量。本地在传输期间发生变化时保留文件。普通数据备份没有上传后删除行为。
+后台「数据查看 → 不喜欢」按所选用户合并不喜欢规则与不喜欢歌手/专辑库。可筛选类型、添加来源和音乐平台,预览已保存封面、查看规则及完整字段。没有来源记录时显示「来源未记录」,不会推断其来自Web或Subsonic;该页只读,不修改联动或评分。
-## 恢复步骤
+评分身份迁移与恢复使用事务日志:写入失败回滚,进程中断后在下次评分访问时完成提交。恢复时按当前文件关联整理本地与在线评分,并同步不喜欢原因中的评分 ID,保留手动原因与旧快照。事务日志临时保存于用户 `rating/transaction.json`,完成后移除;不要让多个服务器进程同时写入同一数据目录。
-1. 在目标实例配置云端连接,确认数据目录、`CONFIG_PATH` 和外部快照挂载位置。
-2. 在系统设置中选择要恢复的类型并保存。外部快照必须先设置本机的 `snapshot.backupPath`;不会直接采用云端提供的绝对路径。
-3. WebDAV 选择散文件或指定 ZIP 恢复;OpenList 使用「恢复所选数据类型」。两者都只下载所选内容,覆盖同名文件,保留本地独有文件,不反向上传。
-4. 检查返回结果和日志。恢复过程使用临时文件后重命名,截断下载不会覆盖原文件。多个文件的恢复不是一个原子事务,部分失败时应检查失败列表后重试。
-5. 重启服务后,重新加载用户、歌单、音源和音乐索引。仅刷新网页不能刷新所有服务端内存状态。
+### OpenList 连接状态
-WebDAV 启动恢复默认关闭。确需无人工干预的灾备启动时可设置 `backup.webdav.restoreOnStartup=true`;这会按已保存范围覆盖同名本地文件。自动模式只在散文件目录不存在或没有所选文件时尝试 ZIP,不会把认证或网络故障解释成“云端为空”,也不会在恢复失败后自动上传本地数据。
+备份记录页独立显示连接状态、最近探测及成功时间。点击「探测连接」只验证认证和配置的备份目录,不重新上传或下载文件。连接异常时保留历史备份结果,云端文件标记为「暂无法检查」;服务自动退避探测(30 秒至 5 分钟),连接恢复后重新检查已备份文件。连接正常和元信息未变均不代表内容校验通过。
-旧 ZIP 中即使存在缓存,恢复仍按当前类型过滤。旧 OpenList 按用户存储的音频可继续由兼容缓存流程访问;新的统一恢复入口读取 `_data`,不猜测或移动旧目录。
+连接异常统一显示中文原因,详情保留原始错误。上传、下载和附件传输遇到连接故障时回到待执行状态,不消耗文件失败重试次数;服务恢复后自动继续。文件检查中途断连时保留当前进度,显示「等待连接恢复」,恢复后继续检查当前文件;可随时停止。进程重启后的未完成检查会显示中断,需要重新发起。
-## 验证边界
+记录中的「上次未变」仅表示上次元信息观察结果。单条「检查」只检查本地与云端元信息;「重试上传/重试下载」会实际传输文件。
-回归测试覆盖真实 SQLite、本地 HTTP OpenList 测试服务、WebDAV 客户端替身、ZIP 打包/恢复、全新目录恢复、类型过滤、缓存排除、上传失败重试及部分音频交付行为。这些检查不代表所有云盘挂载、真实 WebDAV 服务和真实 Subsonic 客户端都已实测。首次使用时应先备份少量文件,并在独立空目录验证恢复结果。
+双向同步传播删除前,将文件和可读取的关联附件保存到数据目录的 `backups/openlist-recycle/<随机编号>/`,附带 `manifest.json` 记录原路径;保存失败则中止删除。可根据清单手动恢复,回收副本不会自动清理。单个用户目录单轮检测到超过 20 项删除时暂停该目录同步,不执行该目录的删除、上传或下载;请核对挂载及本地目录,确认后分批手动处理。此保护不改变管理员明确执行的删除操作。
diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md
index f97ecb2d..5608d684 100644
--- a/docs/guide/configuration.md
+++ b/docs/guide/configuration.md
@@ -80,6 +80,9 @@
| `SYSTEM_ALLOW_UNSAFE_VM` | `system.allowUnsafeVM` | 是否允许运行 VM 模式自定义源脚本 (需注意安全风险) | `false` |
| `LX_USER_<用户名>` | `users` 数组 | 快速添加用户,值为该用户的密码 (如 `LX_USER_test=123`) | - |
+缓存清理仅处理 `cache/`(含子目录),不处理 `music/`。运行根目录和数据目录中,每个用户、每个位置分别计算空间额度;下载完成后检查空间,启动、修改清理设置后及每 12 小时检查两处缓存。按最近访问或修改时间清理,同名音频与歌词成组处理,跳过活跃备份、索引与临时文件。`cache.maxAgeDays` 可在 config.js 或后台设置,默认 14 天;0 仅关闭过期清理,不关闭空间限制。
+
+
布尔值不区分大小写,开启可用 `true/1/yes/y/on`,关闭可用 `false/0/no/n/off`。
`LX_USER_<用户名>` 的值作为该用户密码使用,例如 `LX_USER_user1=your-password`。自定义目录、写入权限和账号禁用等高级设置请通过后台用户管理维护,不要把 JSON 当作该环境变量的高级配置格式。
@@ -122,7 +125,7 @@ Subsonic 用户目录中的 `subsonic-local-registry.json` 和 `subsonic-local-s
配置备份默认开启,保留 7 天;默认位置为数据目录的 `backups/`。可以用 `CONFIG_BACKUP_DIR` 和 `SNAPSHOT_BACKUP_PATH` 自定义配置备份、歌单快照的存储位置。
-WebDAV 和 OpenList 按「系统设置 → 云端备份范围」选择的数据类型备份。启动时恢复默认关闭,可用 `backup.webdav.restoreOnStartup` 显式开启。恢复步骤和两种后端的职责见 [云端备份与恢复](./backup-recovery.md)。
+WebDAV 和 OpenList 按「备份管理 → 云端备份 → 备份范围」选择的数据类型备份。启动时恢复默认关闭,可用 `backup.webdav.restoreOnStartup` 显式开启。恢复步骤和两种后端的职责见 [云端备份与恢复](./backup-recovery.md)。
新的 `backup.webdav.types` 和 `backup.openlist.types` 类型数组分别控制两个目的地。歌曲只包含 `data/music`;`cache` 和外部音乐目录不属于数据备份。旧排除选项不再作为第二套界面开关。
@@ -196,3 +199,50 @@ npm run dev
- WebDAV:核对远端命名空间、排除项、恢复日志和运行时值。
启动方式见 [部署指南](./getting-started.md),备份恢复见 [运行维护](./operations.md)。
+
+## 备份配置项(后台或 config.js)
+
+备份设置统一位于 **备份管理**。本地配置与快照在“本地备份”,连接、周期、范围和 ZIP 保留策略在“云端备份”,用户播放缓存规则也在“云端备份”内,位于 OpenList 连接配置下方的“OpenList 播放缓存”卡片。实际操作见 [备份与恢复](./backup-recovery.md)。
+
+以下是配置文件键,**不代表存在同名大写环境变量**。目前没有 `OPENLIST_*`、`WEBDAV_RETENTION_*`、`CONFIG_BACKUP_INTERVAL_HOURS`、`CONFIG_BACKUP_SCHEDULE_TIME` 的环境变量映射;请在后台保存,或修改生效的 `config.js`。上方表格列出的 `WEBDAV_*`、`CONFIG_BACKUP_ENABLE`、`CONFIG_BACKUP_RETENTION_DAYS`、`CONFIG_BACKUP_DIR` 等环境变量仍然有效。
+
+| 配置键 | 默认值 | 用途 |
+| --- | --- | --- |
+| `maxSnapshotNum` | `10` | 公共最大历史快照数;用户可有独立设置 |
+| `configBackup.enable` | `true` | 自动保存本地配置历史副本 |
+| `configBackup.intervalHours` | `24` | 配置备份周期,小时 |
+| `configBackup.scheduleTime` | `'00:00'` | 服务器本地锚点时间;空字符串从启动/重新设置后计时 |
+| `configBackup.retentionDays` | `7` | 配置历史保留天数 |
+| `configBackup.dir` | `''` | 空值使用数据目录的 `backups` |
+| `snapshot.backupPath` | `''` | 额外快照路径,按用户隔离 |
+| `backup.webdav.types`、`backup.openlist.types` | 五类全部选中 | 两端独立的数据范围,见下方示例 |
+| `backup.webdav.mirrorDeletes` | `false` | 本地删除时删除已跟踪的 WebDAV 增量副本 |
+| `backup.webdav.restoreOnStartup` | `false` | 每次启动服务前用 WebDAV 覆盖所选本地数据 |
+| `webdav.retentionCount` | `5` | ZIP 最多保留 1–1000 份,始终保留最新一份 |
+| `webdav.retentionDays` | `0` | ZIP 保留 0–3650 天,0 不按天数清理 |
+| `openlist.enable` | `false` | 启用 OpenList 备份与用户缓存规则 |
+| `openlist.endpoint` | `''` | OpenList API 地址 |
+| `openlist.token` | `''` | OpenList API 令牌 |
+| `openlist.username`、`openlist.password` | `''` | 未使用令牌时的登录凭据 |
+| `openlist.root` | `'/lx_music'` | 已存在的 OpenList 挂载内路径,不是主机磁盘路径 |
+| `openlist.externalUrl` | `''` | 外网地址,留空使用 endpoint |
+| `openlist.syncInterval` | `60` | 备份与缓存规则的检查间隔,分钟 |
+| `openlist.verifyInterval` | `5` | 后台状态检查间隔,分钟;不是定时下载正文做 SHA-256 校验 |
+| `openlist.historyRetentionDays` | `7` | 云端历史版本保留天数 |
+| `openlist.userBackups` | `'[]'` | 高级按用户缓存规则,JSON 数组字符串;建议通过界面编辑 |
+| `openlist.includeNonAudio` | `false` | 高级缓存规则附带已有歌词与封面,不控制常规 music 备份 |
+| `openlist.playback.enable` | `false` | 允许已登记 OpenList 文件参与播放回退,另需播放器/Subsonic 相应设置 |
+
+```js
+module.exports = {
+ // 合并到现有配置,不要用此片段覆盖其他配置。
+ 'backup.webdav.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
+ 'backup.openlist.types': ['music', 'userFiles', 'snapshots', 'userData', 'system'],
+ 'backup.webdav.mirrorDeletes': false,
+ 'backup.webdav.restoreOnStartup': false,
+ 'webdav.retentionCount': 5,
+ 'webdav.retentionDays': 0,
+}
+```
+
+类型数组留空 `[]` 表示该端不备份数据。显式类型数组优先于旧 `webdav.excludeMusic`;`webdav.excludeCache` 是兼容项,常规备份始终排除 cache。旧 `openlist.backupSystemConfig` 仅用于没有显式类型设置的兼容流程,新配置使用 `backup.openlist.types`。
diff --git a/docs/guide/operations.md b/docs/guide/operations.md
index e90f582d..c8048736 100644
--- a/docs/guide/operations.md
+++ b/docs/guide/operations.md
@@ -50,7 +50,7 @@ pm2 restart ecosystem.config.js --env production --update-env
迁移本地库时同时保留账号状态和音频。Subsonic 的本地 ID 登记文件与本地歌单状态应一起保留;外部移动或重命名歌曲文件可能产生新身份。
-WebDAV 的增量检测间隔默认 60 分钟,全量备份间隔默认 24 小时,分别由 `SYNC_INTERVAL`、`BACKUP_INTERVAL` 控制。已配置 WebDAV 的启动过程会尝试恢复远端数据,随后重新加载配置和账号;它并不限于第一次启动空目录。
+WebDAV 的增量检测间隔默认 60 分钟,全量备份间隔默认 24 小时,分别由 `SYNC_INTERVAL`、`BACKUP_INTERVAL` 控制。仅配置 WebDAV 不会自动恢复。`backup.webdav.restoreOnStartup` 默认关闭;显式开启后,每次启动都会先恢复所选数据并重新加载配置、账号,成功后才开放服务,不限于空目录首次启动。手动恢复完成后需要重启;操作步骤见 [备份与恢复](./backup-recovery.md)。
## 反向代理
diff --git a/docs/guide/subsonic-local-library.md b/docs/guide/subsonic-local-library.md
new file mode 100644
index 00000000..69253db8
--- /dev/null
+++ b/docs/guide/subsonic-local-library.md
@@ -0,0 +1,96 @@
+# Subsonic 本地音乐库
+
+Subsonic 客户端可访问当前账号的服务器音乐文件、缓存及自定义音乐目录。本页说明目录范围、歌单配置、媒体读取和持久化数据;接口范围见 [Subsonic 协议支持](../api/subsonic.md)。
+
+## 接入与目录范围
+
+1. 在后台为账号选择持久化存储位置;使用 NAS 目录时,启用该账号的自定义音乐目录并保证服务进程可读取。
+2. 在后台“Subsonic 协议配置”中启用需要的本地歌单入口并保存。
+3. 在客户端选择“NAS 本地音乐(存储目录+自定义目录)”音乐文件夹,浏览本地专辑、艺术家、流派和歌曲。目录浏览模式也提供 NAS 入口。
+4. 添加或更新文件后,可通过客户端的扫描功能调用 `startScan` 刷新索引。
+
+| 账号存储设置 | 内置曲库范围 |
+| --- | --- |
+| `data` | 数据目录下该账号的 `music`、`cache` |
+| `root` | 程序根目录下该账号的 `music`、`cache` |
+| 已启用的自定义目录 | 在内置曲库之外收录该目录的可访问文件;相同实际文件只收录一次 |
+
+网页文件管理器中的临时目录切换不改变 Subsonic 的曲库范围。切换账号存储位置或关闭自定义目录后,后续请求使用新的目录范围;已保存的歌曲 ID 不能绕过访问范围限制。
+
+## 本地歌单配置
+
+后台“Subsonic 协议配置 → 本地歌曲歌单”提供以下设置:
+
+| 配置项 | 默认值 | 行为 |
+| --- | --- | --- |
+| `subsonic.localPlaylist` | `true` | 返回只读的“本地歌曲(用户名)”歌单;关闭后不返回该歌单及自定义目录歌单 |
+| `subsonic.customPlaylist` | `true` | 父开关开启且账号已启用自定义目录时,单独返回“自定义目录(用户名)”歌单 |
+| `subsonic.cachePlaylist` | `false` | 是否在“本地歌曲”歌单中合并当前存储位置的 `cache` 歌曲;关闭时仅显示 `music` 歌曲 |
+
+本地歌单在服务端返回列表中排在前面,客户端自行排序时以客户端规则为准。已启用的入口即使没有歌曲也会保留。关闭父开关会保留子开关的设置。
+
+自定义目录歌曲与内置目录歌曲分开展示。自定义目录指向内置 `music` 或 `cache` 时,文件优先归属内置目录,不重复加入自定义目录歌单;其中的缓存歌曲仍受缓存目录开关控制。这些开关只控制歌单展示,不缩减搜索、专辑和评分等接口共用的完整曲库。
+
+## 浏览、搜索与扫描
+
+- 指定 NAS 本地音乐文件夹时,专辑等浏览接口使用本地曲库。默认推荐页的“最新专辑”“随机专辑”仍使用在线推荐池,不自动混入 NAS 专辑。
+- `local:关键词` 只搜索本地文件;普通搜索按服务器搜索设置处理,显式在线搜索使用在线分支。
+- 索引读取曲序、碟号、专辑艺术家、年份和流派。专辑按所在目录、专辑名及专辑艺术家分组,避免不同目录中的版本混合。
+- 目录范围不变时,曲库结果缓存 30 秒;`startScan` 可主动刷新。`getScanStatus` 返回扫描状态、歌曲数量及失败原因。
+
+## 播放、封面与歌词
+
+本地歌曲支持详情、播放、下载及 HTTP Range 请求。歌曲和专辑时长按协议返回整数秒,内部保留精确时长;文件大小返回实际字节数。
+
+服务端开启转码并安装可用的 ffmpeg 后,本地播放可进行格式转换、码率限制和时间偏移。相关配置与限制见 [协议支持说明](../api/subsonic.md#转码与媒体限制)。
+
+### 歌词来源
+
+按以下顺序查找可用歌词:
+
+1. 非空的同名外置 LRC 文件。
+2. 音频文件的内嵌歌词。
+3. 网页播放器的服务器歌词缓存。
+4. 已关联音乐平台的在线歌词接口。
+
+在线回退只使用已关联的平台 ID,不按歌名猜测其他歌曲。请求有超时限制,并按账号复用并发请求、短暂缓存成功结果。结构化歌词接口可返回原文、翻译和音译;普通歌词接口按翻译配置合并。支持多时间标签、整数秒标签和 offset,不额外转换为 OpenSubsonic 逐字歌词格式。
+
+### 封面来源
+
+本地歌曲读取音频内嵌或目录封面,并兼容客户端附加的 `al-`、`ar-`、`tr-`、`sg-`、`mg-` 前缀;读取范围仍受当前账号权限约束。
+
+普通用户歌单的列表与详情返回统一封面令牌。解析时优先使用歌单网络封面;关联网络歌单缺少封面时尝试向原平台获取,再回退到第一首歌曲的封面,包括本地封面。空歌单没有可用封面时返回空内容。解析按账号隔离。
+
+## 文件身份与关联
+
+每个本地文件使用独立的 `local_`。关联在线平台不会改变 ID,也不会改写音频标签。响应通过 `lxStorage=local`、`lxLinked` 和 `comment` 表达本地身份与关联状态,是否显示由客户端决定。
+
+本地歌曲不会按歌名与在线歌曲自动合并。身份登记按实际路径保存:在原路径更新文件或关联平台会保留 ID;外部移动、重命名文件会被视为新文件,不进行指纹追踪。不在当前目录范围内的歌曲会停止显示,但原歌单引用和收藏仍保留,切回原目录后可恢复。
+
+## 收藏与歌单同步
+
+| 操作 | 存储及同步行为 |
+| --- | --- |
+| 创建纯本地歌单 | 只保存在服务器,不进入 LX 官方客户端同步 |
+| 创建混合歌单 | 在线歌曲写入原生歌单,本地成员及混排顺序独立保存 |
+| 在线歌单重新同步 | 在线成员随同步增删,本地成员保留;新增在线成员默认追加到合成列表末尾 |
+| 按位置删除歌曲 | 按客户端看到的混合列表解析位置 |
+| 删除混合歌单 | 删除原生在线歌单与本地附加信息,不删除音频文件 |
+| 收藏本地歌曲 | 使用 `star` / `unstar`;向内置“我的喜爱”歌单直接添加本地成员会返回提示 |
+
+原生歌单被其他同步操作删除后,本地附加成员仍可作为服务端歌单查看。播放记录、队列和书签可解析本地歌曲。
+
+## 数据备份与排查
+
+以下文件位于用户目录,应随用户数据一起备份:
+
+| 文件 | 内容 |
+| --- | --- |
+| `subsonic-local-registry.json` | 文件身份和元数据;不是可随意清理的缓存 |
+| `subsonic-local-state.json` | 本地收藏、纯本地歌单及混合歌单附加信息 |
+
+曲库缺失时,先核对账号存储位置、自定义目录开关和服务进程的文件权限,再发起扫描并查看 `getScanStatus`。本地歌单缺少缓存歌曲时,检查 `subsonic.cachePlaylist`。
+
+部分客户端会根据歌曲 `size` 决定是否加载歌单封面。在线歌曲优先返回音源提供的字节数,缺失时按码率和时长估算;未知时长且没有已知大小时仍返回 0。估算值不用于实际音频响应的 Content-Length 或 Range。
+
+大曲库扫描速度取决于目录访问和标签读取性能;转码取决于 ffmpeg 与服务端资源。封面、歌词、扫描进度及其他控件的展示也受客户端实现影响。
diff --git a/docs/guide/sync-server.md b/docs/guide/sync-server.md
index 22987dce..7c4c9dfb 100644
--- a/docs/guide/sync-server.md
+++ b/docs/guide/sync-server.md
@@ -26,7 +26,7 @@
### Subsonic 客户端
-支持使用音流、Feishin、Symfonium 等客户端访问已实现的 Subsonic/OpenSubsonic 接口。各客户端支持的功能不同,接口范围和已知限制见 [Subsonic 状态](../subsonic-status.md)。
+支持使用音流、Feishin、Symfonium 等客户端访问已实现的 Subsonic/OpenSubsonic 接口。各客户端支持的功能不同,接口范围和已知限制见 [Subsonic 协议支持](../api/subsonic.md)。
默认连接信息:
@@ -52,7 +52,7 @@
Subsonic 本地曲库按当前账号的持久化存储设置读取音乐和缓存,并加入该账号已启用的自定义目录。网页中临时切换浏览目录,不会改变 Subsonic 的曲库范围。
-默认返回只读的“本地歌曲”歌单;自定义目录可以单独显示。纯本地歌单保存在服务端,不进入 LX 官方客户端同步;混合歌单中的在线歌曲和本地成员分别保存。详情见 [Subsonic 本地音乐库](../subsonic-local-library.md)。
+默认返回只读的“本地歌曲”歌单;自定义目录可以单独显示。纯本地歌单保存在服务端,不进入 LX 官方客户端同步;混合歌单中的在线歌曲和本地成员分别保存。详情见 [Subsonic 本地音乐库](./subsonic-local-library.md)。
@@ -91,6 +91,7 @@ Subsonic 本地曲库按当前账号的持久化存储设置读取音乐和缓
| --- | --- |
| 本地音乐文件夹 | 明确选择本地文件夹时使用本地库分支;默认推荐池不等同于 NAS 全部专辑 |
| `subsonic.localPlaylist` / `subsonic.customPlaylist` | 分别控制只读本地歌单和独立自定义目录歌单,后者依赖前者及账号目录启用 |
+| `subsonic.cachePlaylist` | 是否在本地歌单中包含缓存目录歌曲,默认 false;不缩减其他接口共用的完整曲库 |
| `subsonic.cacheOnPlay` | 播放在线资源时触发落盘缓存,默认 false |
| `subsonic.playCacheFirst` | 优先通过服务器已有文件播放,默认 false;使用时服务器承担文件传输 |
| `subsonic.quality.*` / `subsonic.source.*` | 控制候选音质和跨平台/脚本优选,不保证每首都有目标音质 |
@@ -101,7 +102,7 @@ Subsonic 本地曲库按当前账号的持久化存储设置读取音乐和缓
本地歌曲使用独立 ID,关联平台不会把它变成在线歌曲。文件在原路径更新通常保留身份,外部移动或重命名文件则可能产生新 ID。混合歌单的本地成员与顺序单独保存;删除歌单不等于删除音频文件。
-客户端封面、逐字歌词、扫描进度与可用的目录展示受协议和客户端能力共同影响。网页版有某个设置不代表第三方客户端会提供同名控件。详细本地库行为见 [Subsonic 本地音乐库](../subsonic-local-library.md)。
+客户端封面、逐字歌词、扫描进度与可用的目录展示受协议和客户端能力共同影响。网页版有某个设置不代表第三方客户端会提供同名控件。详细本地库行为见 [Subsonic 本地音乐库](./subsonic-local-library.md)。
## 后台各模块的用途
@@ -111,7 +112,7 @@ Subsonic 本地曲库按当前账号的持久化存储设置读取音乐和缓
| 用户管理 | 新建、重命名、禁用账号,设置自定义目录 | 删除账号时区分移除账号与删除数据 |
| 数据与快照 | 查看歌单、恢复历史快照 | 恢复会改变同步端后续读取的数据,先确认账号和时间 |
| 系统配置 | 路径、认证、代理、缓存和 Subsonic 设置 | 环境变量和 WebDAV 恢复可能改变下一次启动值 |
-| WebDAV | 测试连接、同步、备份与恢复 | 增量同步和全量备份有独立间隔 |
+| 备份管理 | 本地备份、WebDAV、OpenList、数据恢复及内容校验 | 连接与范围在“云端备份”;恢复前预览,完成后按提示重启 |
| 文件管理 | 查看与编辑服务器文件、上传、压缩和解压 | 这是管理员文件操作,不按普通用户曲库权限授权 |
| 日志与维护 | 查看错误、重新加载配置、重启服务 | 端口或路径变化后需要使用新地址连接 |
diff --git a/docs/guide/web-player.md b/docs/guide/web-player.md
index 23189929..c75871a8 100644
--- a/docs/guide/web-player.md
+++ b/docs/guide/web-player.md
@@ -1,7 +1,75 @@
-# Web 播放器与本地音乐指南
+# 播放器、歌词与本地音乐
默认播放器入口为 `/`,可通过 `PLAYER_PATH` 修改。访问密码用于进入播放器;个人账号用于自己的歌单、设置和目录权限。浏览器会话有效时通常无需重复输入播放器密码。
+
+## 播放来源与解析顺序
+
+“优先播放服务器已有歌曲”(原“优先播放缓存歌曲”)同时包含当前用户的 `music` 下载文件和 `cache` 播放缓存。不是扫描所有用户,也不代表自动下载所有歌曲。必须能通过索引中的平台、歌曲 ID 和音质匹配;手动放入的文件应先扫描和关联,或直接从本地音乐页面播放。
+
+| 来源 | 运行目录模式 | DATA_PATH 模式 |
+| --- | --- | --- |
+| 下载歌曲与同名 LRC | `<运行目录>/music/<用户>/` | `/music/<用户>/` |
+| 播放缓存与同名 LRC | `<运行目录>/cache/<用户>/` | `/cache/<用户>/` |
+
+播放使用用户持久化的 `serverCacheLocation`。本地音乐页面可以临时浏览另一位置;临时浏览不等于改变后续所有播放的根目录。公开用户使用 `_open`。自定义曲库使用用户单独配置的目录,不混入所有在线歌曲的普通缓存扫描。
+
+### Web 音频
+
+从本地曲库点击歌曲时,直接读取该条目的文件 URL,支持 music、cache 和自定义目录,不依赖在线解析。
+
+在线歌曲的正常解析顺序:
+
+1. 开启“优先播放服务器已有歌曲”时,查当前用户 music/cache。同请求音质先 music、后 cache,允许时再回退其他音质。
+2. 本地未命中,且后台 OpenList 来源总开关开启时,查询已登记的该用户云端缓存/下载文件。
+3. 浏览器链接缓存开启时,复用已有 URL;链接可能过期,不等于本地音频。
+4. 调用在线音源脚本获取播放 URL;按代理设置直连或由服务器转发。
+
+“缓存歌曲文件”仅控制在线解析后的后台写入;“优先播放服务器已有歌曲”仅控制读取已有文件。两者独立,关闭写入仍可读取已有 music/cache。后台请求明确传递“仅下载模式”:开启时写 music,关闭时写 cache,根目录按用户位置选择。首次播放与后台保存是不同请求,不等待整首落盘。
+
+### Web 歌词
+
+“优先使用服务器本地歌词”与“缓存歌词文件”是独立设置:
+
+| 设置 | 开启时的行为 |
+| --- | --- |
+| 优先使用服务器本地歌词 | 当前用户 music/cache LRC → 浏览器歌词缓存 → 在线歌词 |
+| 缓存歌词文件 | 在线取词、后台保存音频时保存独立 LRC;已有音频时尽量同目录同名 |
+| 浏览器歌词缓存 | 在当前浏览器保存和复用歌词文本,不写服务器目录 |
+| 下载时嵌入歌词 | 写入音频内嵌歌词标签,独立于是否保存 LRC |
+
+关闭本地歌词优先后,Web 跳过独立 LRC 查找,走浏览器缓存和在线平台;不隐式在后端再查一次本地 LRC。本地未关联歌曲没有在线平台身份时,可用音频内嵌歌词兜底。
+
+从本地音乐条目播放时,支持读取 music/cache/自定义目录中的相邻同名 LRC,即使没有平台关联。所有路径按认证用户确定,不能传任意绝对路径或跨用户目录。音频和歌词分开解析,不能根据歌曲来自哪里推断歌词一定同源。
+
+### Subsonic 音频和歌词
+
+对于 `tx_...` 等在线 ID:
+
+- `subsonic.playCacheFirst` 开启:按请求音质和客户端码率上限查当前用户 music/cache。
+- 本地未命中,且 `subsonic.playCacheFirstUseOpenlist` 与后台 `openlist.playback.enable` 同时开启:查已登记 OpenList 文件;再未命中则在线解析。
+- 关闭缓存优先后,在线 ID 跳过本地及 OpenList。正常在线结果通过 302 交付;需要格式、码率或偏移转换且开启转码时,由服务器拉取并转码输出。
+- 在线 ID 的歌词先查本地 LRC(cache/music 和已登记自定义曲库),再调用平台 SDK,返回文本或结构化时间行。
+
+`local_...` 本地曲库 ID 在另一入口处理:曲库包含当前位置的 cache/music 和启用的自定义目录,不依赖缓存优先开关。音频直接读文件或转码;缺失时报告错误,不悄悄替换为在线曲目。歌词依次读取相邻 LRC、内嵌歌词、已登记本地歌词,有平台关联时再在线获取。
+
+`subsonic.cacheOnPlay` 仅表示在线播放成功后,在后台下载一份到 cache,不决定是否读取已有文件。后台任务随切歌可取消。在线 download 分支即使关闭 cacheOnPlay 也会后台保存。Subsonic 后台缓存显式获取歌词,独立于 Web 的保存设置;本地或 OpenList 提前命中时不会重复进入在线写入分支。
+
+### OpenList 和 WebDAV
+
+后台「备份管理 → 云端备份 → OpenList 播放缓存→ 允许 OpenList 作为播放来源(本地优先)」是 Web/Subsonic 共用总开关;Web 还要开启服务器已有歌曲优先,Subsonic 还要开启其两个相关选项。保存上传后删除规则不会自动开启这些播放设置。旧版本已开启的 Subsonic OpenList 配置会在缺少新字段时迁移,显式关闭新总开关会被尊重。
+
+OpenList 通过本地备份数据库匹配用户、歌曲和音质,再查询单个文件下载地址;不在每次播放时遍历网盘。并发请求合并,地址最多短期复用 30 秒,单次元信息查询限制 4 秒;未命中或不可用则回退在线音源。Web 获得云端地址后使用现有代理设置;Subsonic 由 `subsonic.openlistDelivery` 决定 302 直链或代理,也可能按客户端要求转码。
+
+目前直接播放来源是已登记且相应用户目录同步规则仍启用的缓存/下载备份;仅开启普通五类备份不够。普通 `_data` 和历史对象不会自动当成曲库。统一下载器也可在本地未命中时先从 OpenList 取回到本地,再登记音频及附件;这一能力由新总开关控制,不再依赖 Web 不相关的 Subsonic 设置。
+
+WebDAV 不参与实时歌曲或歌词解析候选。手动恢复或显式启用启动恢复后,文件回到本地,才由正常本地路径读取。详见 [云端备份与恢复](./backup-recovery.md)。
+
+### LRC 与备份范围
+
+生成 LRC 和上传 LRC 是两回事。“缓存歌词文件”关闭时,Web 不因后台保存音频而默认另写 LRC,但保留已有文件。只要常规备份选择 music,`data/music` 下已有的音频、LRC、封面和索引都会被备份。`openlist.includeNonAudio` 只控制独立用户缓存同步的附件,不能用它排除普通 music 类型中的 LRC。常规备份固定排除 cache、运行目录 music 和外部自定义曲库。
+
+
## 播放与界面
播放器提供播放队列、音质切换、倍速、睡眠定时、全屏歌词和主题设置。桌面和手机使用响应式布局;可以按自己的使用习惯选择主题、播放器背景和歌词展示方式。
@@ -69,7 +137,7 @@
Chromecast 支持具备 Cast 能力的桌面/Android Chromium 浏览器,需要安全上下文,通常为 HTTPS。接收设备还必须能访问服务器和媒体资源;浏览器中的 `blob:` 或本地文件链接不能直接交给接收设备读取。投屏时由接收端播放,浏览器音效和倍速不会应用到接收端;iOS 保留原生 AirPlay 路径。
-网络歌单定时检查、同步下载和普通歌单同步分别配置。可以在任务界面检查上次执行、下次执行和结果;启用歌单同步不会自动开启音频下载。更多操作步骤见 [播放器指南](../guide/web-player.md)。
+网络歌单定时检查、同步下载和普通歌单同步分别配置。可以在任务界面检查上次执行、下次执行和结果;启用歌单同步不会自动开启音频下载。更多任务配置见 [运行维护](./operations.md)。
## 账号音源与公开音源
@@ -106,3 +174,41 @@ Chromecast 支持具备 Cast 能力的桌面/Android Chromium 浏览器,需要
服务端下载、同步下载和自动匹配由后台执行;浏览器下载由访问设备执行。关闭页面不会自动取消已经提交的服务端任务。查看任务状态并使用对应的停止/取消入口,不要依靠刷新页面终止操作。
更多诊断步骤见 [运行维护](./operations.md),接口字段见 [API 参考](../api/reference.md)。
+
+### 歌曲评分
+
+登录本地账号后,可在底部播放器、歌曲列表更多菜单、歌手详情、排行榜、本地音乐与自定义目录给歌曲设置1至5星,或清除评分。「我的收藏 → 我的评分」支持搜索和修改评分。
+
+Web 与 Subsonic 共用用户的 `subsonic-meta.json` 中的 `ratings` 和 `ratingOrigins`;`rating/snapshot/` 保存评分快照,`rating/songs.json` 保存 Web 提交的必要歌曲资料。评分不修改实体歌单、不写入音频标签;歌曲资料不持久化播放URL或鉴权参数。当前评分及歌曲资料属于用户数据备份,评分快照属于快照备份。
+
+在线歌曲按来源及平台歌曲ID区分,QQ优先使用歌曲mid。本地歌曲未关联时按用户本地曲库注册ID评分,Web与Subsonic共用。关联在线歌曲后,以在线ID的评分为准,与在线版本及其他关联副本共用读写;在线歌曲已有评分时保留在线评分并删除本地评分记录;仅本地有评分时,将分数、来源与历史迁移到在线ID,并删除本地条目。重新关联时切换到新的在线ID,不按歌名合并。另行导入、重建或移动后被曲库识别为新文件时,不自动猜测和合并历史评分。
+
+低分是否补不喜欢仍受评分联动开关和阈值控制;提高或清除评分不会删除不喜欢规则。Web直接评分来源为 `web`,其不喜欢联动来源为 `web-rating-link`;Subsonic直接评分来源为 `subsonic`。评分已保存但快照或联动失败时会明确提示。列表评分批量读取,其他客户端的修改会在页面恢复可见或周期刷新后更新。
+
+
+### 评分归属、重算与备份
+
+未关联的本地歌曲使用本地ID评分;关联后使用在线ID评分,在线已有评分则优先使用在线评分,否则迁移本地评分,随后删除本地评分记录。评分弹窗可查看目标ID与关联副本数,管理端不再区分评分使用状态。
+
+「低分补不喜欢」「不喜欢补1星」仍只补充数据。配置保存后不会自动删除历史规则;需要按新阈值处理时,选择用户后点击「按阈值重算」,先查看预览再执行。重算仅撤销能追溯到具体评分ID的失效联动原因;同一条规则仍有手动或其他有效原因时保留。缺乏精确来源的历史规则保留,不猜测删除。关闭联动不触发清理。
+
+「我的评分 → 导出评分备份」包含分数、来源、歌曲描述及本地关联参照,不包含音频。新生成的评分快照也包含描述;在管理端按评分备份导入/恢复时,恢复分数和可用描述、保留当前收藏与文件关联。关联参照用于核对,不按备份中的路径寻找、移动文件或恢复旧关联。只有旧分数而没有描述的备份无法凭空补出封面和标题。
+
+推荐排除开关对每日推荐、随机及相似歌曲使用统一策略,同时排除规则命中项与阈值内低分;与普通列表隐藏、自动联动开关独立。
+
+管理端数据查看的评分与不喜欢看板支持按音乐平台、操作来源、封面、记录时间、组合关键词及评分筛选;平台选项显示用户总条数,包含0条平台。可点击星级分布筛选、调整每页条数、查看记录详情并导出当前筛选结果。统计按当前平台、来源、时间及关键词条件汇总;评分类型与星级用于进一步限定列表。
+
+### 用户独立的不喜欢自动补分
+
+「设置 → 逻辑 → 不喜欢自动补分」按当前账号保存到服务器,默认关闭;Web 将单首歌曲加入不喜欢时,仅为未评分歌曲补1星。已有评分不覆盖,取消不喜欢或关闭设置不删除评分。用户之间独立,低分阈值不控制该设置。原全局反向联动开关已移除。「后台补分」是管理员独立操作,先预览再执行,不受用户自动开关或低分阈值为0影响。来源分别显示 Web手动评分、Subsonic手动评分、不喜欢自动补分、后台补分。偏好保存在用户目录的 `rating/preferences.json`;评分备份恢复不更改此偏好。
+
+歌单详情支持直接评分。评分合集打开时随周期刷新、返回页面及本端评分/不喜欢操作更新,并保留筛选和列表滚动位置。每个对象保留最近30条分数变更(含清除、补分和恢复),同分重复提交不刷新来源时间;旧数据不补造历史。自动补分失败会提示部分完成,已保存的不喜欢保留。评分恢复发生可捕获的写盘错误时回滚文件;此机制不保证进程突然退出或断电时的跨文件原子性。
+
+
+### 资料修复与同步状态
+
+同分提交不会改写评分来源和时间,但会补齐可用歌曲资料。直接评分、用户自动补分与后台补分共用评分写入服务;补分只补未评分项。Subsonic 已解析资料保存到用户的 `rating/songs.json`,避免展示长期依赖运行缓存。收藏操作保留评分来源和历史。
+
+「我的评分」及管理端评分区域可查看待处理项并选择“仅重试失败项”。状态保存在用户 `rating/sync-status.json`,包括补分写入、描述保存、资料未解析和快照失败;重试不会覆盖新评分。资料解析仍受平台可用性影响,失败项会保留。主评分文件损坏时停止写入和导出,保留原文件;状态文件损坏会显示人工检查提示,不自动清空。
+
+后台补分预览分别列出待新增、已有评分跳过、重复关联及解析失败;评分已落盘但快照失败时,明确提示部分完成。评分数据提供版本查询,版本不变时避免重复全量刷新;Web 评分合集的平均分按当前筛选结果计算,星级分布按其余筛选条件计算,供切换星级使用。
diff --git a/docs/public/showcase/charts-mobile.mp4 b/docs/public/showcase/charts-mobile.mp4
index d5c8c75c..c2e785f7 100644
Binary files a/docs/public/showcase/charts-mobile.mp4 and b/docs/public/showcase/charts-mobile.mp4 differ
diff --git a/docs/public/showcase/console-mobile.mp4 b/docs/public/showcase/console-mobile.mp4
index 877ddd8d..11a27cf9 100644
Binary files a/docs/public/showcase/console-mobile.mp4 and b/docs/public/showcase/console-mobile.mp4 differ
diff --git a/docs/public/showcase/console.mp4 b/docs/public/showcase/console.mp4
index 335b60a6..e3cd2f0b 100644
Binary files a/docs/public/showcase/console.mp4 and b/docs/public/showcase/console.mp4 differ
diff --git a/docs/public/showcase/listening-mobile.mp4 b/docs/public/showcase/listening-mobile.mp4
index 7dde3a34..0691aea4 100644
Binary files a/docs/public/showcase/listening-mobile.mp4 and b/docs/public/showcase/listening-mobile.mp4 differ
diff --git a/docs/public/showcase/local-library-mobile.jpg b/docs/public/showcase/local-library-mobile.jpg
new file mode 100644
index 00000000..504a5ea8
Binary files /dev/null and b/docs/public/showcase/local-library-mobile.jpg differ
diff --git a/docs/public/showcase/playlists-mobile.mp4 b/docs/public/showcase/playlists-mobile.mp4
index 961c7c2e..bb5900c0 100644
Binary files a/docs/public/showcase/playlists-mobile.mp4 and b/docs/public/showcase/playlists-mobile.mp4 differ
diff --git a/docs/public/showcase/search-mobile.mp4 b/docs/public/showcase/search-mobile.mp4
index b2c63069..325114a2 100644
Binary files a/docs/public/showcase/search-mobile.mp4 and b/docs/public/showcase/search-mobile.mp4 differ
diff --git a/docs/public/showcase/themes-mobile.mp4 b/docs/public/showcase/themes-mobile.mp4
index 81124acc..02903327 100644
Binary files a/docs/public/showcase/themes-mobile.mp4 and b/docs/public/showcase/themes-mobile.mp4 differ
diff --git a/docs/subsonic-local-library.md b/docs/subsonic-local-library.md
deleted file mode 100644
index b6133294..00000000
--- a/docs/subsonic-local-library.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Subsonic 本地音乐库
-
-Subsonic 本地曲库使用当前账号的持久化存储设置,并叠加已启用的自定义音乐目录。
-
-- 存储选择为 `data`:读取数据目录下该账号的 `music` 和 `cache`。
-- 存储选择为 `root`:读取程序根目录下该账号的 `music` 和 `cache`。
-- 自定义目录启用且可访问时,同时收录其中歌曲。与主目录重复的实际文件只收录一次。
-- 网页文件浏览中的临时根目录/数据目录切换不影响 Subsonic。
-- 改变账号的存储设置或关闭自定义目录后,后续请求会重新选择曲库;旧目录歌曲的保存 ID 也不能绕过范围限制。
-
-## 使用
-
-后台“Subsonic 协议配置 → 本地歌曲歌单 → 返回本地歌曲歌单”控制只读的“本地歌曲(用户名)”歌单,默认开启。设置通过现有后台配置接口保存到服务器配置(`subsonic.localPlaylist`),关闭后列表和详情均不返回此歌单。开启时服务端在歌单列表首位返回;客户端自行排序时以客户端设置为准。
-
-此歌单汇总当前账号所选存储目录中的歌曲。父开关开启时显示“返回自定义目录歌曲”子开关(`subsonic.customPlaylist`,默认开启),开启后在本地歌单之后单独返回“自定义目录(用户名)”歌单;账号还需启用自定义音乐目录。关闭父开关时两个歌单均隐藏,子开关的选择保留。两种歌曲分开显示,子开关关闭时不会将自定义歌曲混入本地歌单。
-
-空曲库时保留已启用的入口。切换存储设置后歌单内容同步变化;不会写入 LX 原生歌单或同步到官方客户端。歌曲及专辑时长按协议返回整数秒,内部保留文件的精确时长。
-
-默认推荐页的“最新专辑”“随机专辑”保持在线推荐池及其分页逻辑,不把 NAS 专辑插入推荐池。明确选择 NAS 本地音乐文件夹时,专辑接口才使用本地曲库分支。
-
-客户端选择“NAS 本地音乐(存储目录+自定义目录)”音乐文件夹,可浏览本地专辑、艺术家、流派和随机歌曲。目录模式也有 NAS 本地音乐入口。搜索 `local:关键词` 仅搜索本地文件;普通搜索会包括本地文件,显式在线搜索仍按在线模式处理。
-
-支持扫描和扫描状态、歌曲/专辑/艺术家详情、封面、外置及内嵌歌词、播放、下载、Range 请求、收藏、评分,以及现有播放记录、队列和书签的本地歌曲解析。开启现有服务端转码配置且安装 ffmpeg 后,本地播放可使用格式转换、码率限制和时间偏移。
-
-歌词按非空外置同名 LRC、内嵌歌词、网页端服务器歌词缓存、已关联平台在线接口的顺序获取。在线回退仅使用已关联平台 ID,不按歌名猜测其他歌曲;请求有超时限制,并按账号复用并发请求、短暂缓存成功结果。外置及内嵌歌词复用网页端扩展歌词解码,结构化接口返回原文、翻译和音译;普通歌词接口按现有翻译配置合并。支持多时间标签、整数秒标签及 offset。逐字内容没有额外转换为 OpenSubsonic 逐字格式。
-
-本地封面支持客户端附加的 al-/ar-/tr-/sg-/mg- 前缀,仍按当前账号可访问的本地文件读取内嵌或目录封面。
-
-普通用户歌单的列表和详情返回统一的封面令牌。获取时优先用歌单自身的网络封面;关联网络歌单未保存封面时尝试从原平台补取;没有则用第一首歌曲的封面(含本地文件封面)。空歌单且无网络封面时返回空内容。封面解析按账号隔离,并兼容客户端附加的前缀。
-
-扫描复用现有文件索引,额外读取曲序、碟号、专辑艺术家、年份、流派。目录不变时曲库结果缓存 30 秒;`startScan` 可主动刷新。`getScanStatus` 返回扫描状态、歌曲数量及失败原因。
-
-## 标记与身份
-
-文件获得独立的 `local_`,在线关联不改变此 ID。响应保留原始歌名,使用 `lxStorage=local`、`lxLinked` 和 `comment` 表达本地身份及关联状态,不改写音频标签。第三方客户端是否显示这些说明取决于客户端支持;不会自动出现通用徽章。
-
-本地歌曲不会按歌名与在线歌曲自动合并,以保留原版、重录版和不同文件版本。专辑按所在目录、专辑名及专辑艺术家分组,避免不同目录中的版本混在一起。
-
-目前身份登记按实际文件路径保存:关联平台及在原路径更新文件会保留 ID;外部移动或重命名文件尚不做指纹追踪,会视为新文件。停止显示不在当前目录范围的歌曲时,原歌单引用和收藏仍保留,切回目录后可恢复。
-
-## 歌单同步
-
-纯本地歌单只保存在服务端,不进入 LX 官方客户端同步。混合歌单的在线歌曲写入原生歌单,本地歌曲及混排顺序独立保存。在线歌单重新同步时,删除/新增的在线成员随之更新,本地成员保留;新增在线成员默认追加到合成列表末尾。
-
-Subsonic 的按位置删除针对客户端看到的混合列表解析。删除混合歌单会删除其原生在线歌单和本地附加信息,不删除音频文件。若原生歌单被其他同步操作删除,本地附加歌曲仍可以作为服务端歌单查看。
-
-本地歌曲收藏通过 `star/unstar` 管理。直接向内置“我的喜爱”歌单添加本地成员会提示使用收藏接口。
-
-用户目录中的持久化文件:
-
-- `subsonic-local-registry.json`:文件身份和元数据,不属于可随意清理的缓存。
-- `subsonic-local-state.json`:本地收藏、纯本地歌单及混合歌单附加信息。
-
-## 验证
-
-音流的歌单封面加载受歌曲 `size` 影响:同一歌单、同一网络歌曲,`size=0` 不加载封面,改为正数后加载。网络歌曲优先返回音源提供的字节数;缺失时用码率 × 时长 ÷ 8 估算,估算值不是精确文件大小。未知时长且无已知大小时仍返回 0。本地歌曲返回真实大小;音频响应的 Content-Length / Range 不使用估算值。
-
-`node scripts/subsonic-local.test.cjs` 使用隔离临时文件、模拟索引/标签读取和本机 HTTP 服务,覆盖目录选择、账号隔离、播放范围、JSON/XML、歌词、收藏和歌单同步。不会读取生产账号配置或修改真实音乐文件。
-
-真实 NAS 大曲库扫描性能、原生标签库在部署架构上的表现、ffmpeg 实际转码和音流/箭头/Feishin 客户端显示,仍需在对应环境进行集成验证。
diff --git a/docs/subsonic-status.md b/docs/subsonic-status.md
deleted file mode 100644
index 4ff06fed..00000000
--- a/docs/subsonic-status.md
+++ /dev/null
@@ -1,115 +0,0 @@
-# Subsonic 协议支持状态对照
-
-> 范围:`src/server/integrations/subsonic.ts` 对 Subsonic REST API 的**自定义实现**对照表。
-> 图例:✅ 已实现可用 🟡 占位(返回空 / no-op,兼容客户端握手) ❌ 未实现(返回 `Method not found`)
-> 更新:2026-09-19(协议补全:专辑信息 / 播放上报 / 书签 / 播放队列 / 相似歌曲 / 转码,见第四节)
-
----
-
-## 一、已实现 / 可用接口
-
-| 方法 (method) | 类别 | 状态 | 说明 |
-|---|---|---|---|
-| `ping` | 系统 | ✅ | 握手 / 存活检测 |
-| `getLicense` | 系统 | ✅ | 许可信息 |
-| `getScanStatus` | 系统 | 🟡 | 固定返回 `scanning:false` |
-| `getMusicFolders` | 浏览 | ✅ | 单一音乐库 |
-| `getMusicDirectory` | 浏览 | ✅ | 专辑 / 歌单 / 歌手目录 |
-| `getGenres` | 浏览 | ✅ | 流派(来自 QQ 音乐 discovery) |
-| `getArtists` | 浏览 | ✅ | 按字母索引的歌手 |
-| `getArtist` | 浏览 | ✅ | 歌手详情 |
-| `getArtistInfo` / `getArtistInfo2` | 浏览 | ✅ | 歌手信息 |
-| `getAlbum` | 浏览 | ✅ | 专辑详情(含歌曲列表) |
-| `getAlbumList` / `getAlbumList2` | 浏览 | ✅ | 含 `recommend`/`random`/`newest`/`recent` 推荐专辑逻辑 |
-| `getStarred` / `getStarred2` | 浏览 | ✅ | 收藏 |
-| `getSong` | 媒体 | ✅ | 单曲信息 |
-| `stream` / `download` | 媒体 | ✅ | 在线音频流(按 `source_songmid` 解析) |
-| `getCoverArt` | 媒体 | ✅ | 封面代理 |
-| `getUser` | 用户 | ✅ | 当前用户 |
-| `search` / `search2` / `search3` | 搜索 | ✅ | 本地 + 在线全网搜索 |
-| `getPlaylists` / `getPlaylist` | 歌单 | ✅ | |
-| `createPlaylist` / `updatePlaylist` / `deletePlaylist` | 歌单 | ✅ | |
-| `star` / `unstar` | 收藏 | ✅ | |
-| `setRating` | 评分 | ✅ | 评分按用户持久化(0-5,0=清除);每日推荐会排除评分落入「不喜欢」阈值(`subsonic.dislikeRating`,默认 1)的歌曲 |
-| `scrobble` | 播放 | ✅ | 写入播放历史;按规范仅 `stopped` 且未带 `ignoreScrobble` 时记录 |
-| `getNowPlaying` | 播放 | ✅ | 返回正在播放列表,条目带 `positionMs` |
-| `getLyrics` / `getLyricsBySongId` | 歌词 | ✅ | |
-| `getOpenSubsonicExtensions` | 扩展 | ✅ | |
-| `getRandomSongs` | 发现 | ✅ | 本地库随机 / 流派随机 |
-| `getSongsByGenre` / `getSongsByGenre2` | 发现 | ✅ | 按流派拉取云端歌曲 |
-| `getSimilarSongs` / `getSimilarSongs2` | 发现 | ✅ | 同歌手相似 |
-| `getTopSongs` | 发现 | ✅ | 歌手热门 |
-| `getInternetRadioStations` | 电台 | ✅ | 三类来源:QQ 官方电台 / 用户自建电台(`radioStations.ts` 落盘)/ 音乐源歌单电台 |
-| **`getRecommendedSongs`** | **发现(新增)** | ✅ | **每日推荐歌曲(见第二节)** |
-| **`getDailySongs`** | **发现(新增)** | ✅ | **`getRecommendedSongs` 的别名** |
-| **`getSongsByTag`** | **发现(新增)** | ✅ | **别名,同样映射为每日推荐**(lx-server 未实现独立按标签检索) |
-
-> **网络电台补充说明(2026-09-19)**
->
-> - 自产电台地址会补全为**绝对地址**(尊重反向代理的 `X-Forwarded-Proto`)—— 客户端把 `internetRadioStation.streamUrl` 当作可直接播放的音频地址「原样请求」,相对路径在第三方客户端必然失败。
-> - 这类地址带**服务端短期签名票据**(`u` + `rtexp` + `rtsig`,HMAC-SHA256,12 小时),而不是用户凭据:客户端请求该 URL 时不会附带 Subsonic 凭据,直接透传 `u+t+s` 会把长期令牌写进客户端会展示、可复制的 URL。票据仅对 `stream` / `download` 且 `id=radio_*` 生效,外部(用户自建)电台地址原样返回。
-> - QQ 官方电台的取歌接口(`GetRadioSong`)当前返回 `500003`、旧接口 404,因此对官方电台做可用性探测(结果缓存 10 分钟)并在不可用时暂时从列表隐藏,上游恢复后自动重现。
-
----
-
-## 二、新增:每日推荐歌曲(2026-09-10 实现)
-
-### 背景
-此前代码里**完全没有**歌曲级「每日推荐」接口(`getRecommendedSongs` / `getDailySongs` / `getSongsByTag` / `DailySongs` 在 `subsonic.ts` 中均为空,走 default 分支返回 `Method not found`)。仅 `getAlbumList2` 带有「推荐专辑」逻辑(`cachedRecommend → fetchRecommendedAlbums`,专辑级)。
-
-### 实现
-- 新增数据源 `src/server/utils/recommendSongs.ts`:`fetchRecommendedSongs(size)`
- - 复用已验证的 `fetchRecommendedAlbums('random')` 拿推荐专辑;
- - 逐张调用 `musicSdk.tx.extendDetail.getAlbumSongs(mid)` 取出专辑内歌曲;
- - 按 `tx_` 去重,按「自然日」做种子洗牌(当天稳定、跨天换批),返回可直接播放的在线歌曲。
- - 全程公开接口、无需登录;QQ 抓取失败时回退到上次成功结果,避免每日推荐空白。
- - **评分过滤**:返回前按请求用户过滤——评分落入「不喜欢」区间(`0 < rating <= subsonic.dislikeRating`,默认阈值为 1,可配置)的歌曲不会进入每日推荐(`setRating` 设置)。
-- `subsonic.ts` 分发开关新增 `case 'getRecommendedSongs' / 'getDailySongs' / 'getSongsByTag'`,handler `handleGetRecommendedSongs` 复用 `renderRandomSongs` 输出(与 `getRandomSongs`/`getSongsByGenre` 同一条渲染链路,封面 / 播放均正常)。
-
-### 调用示例
-```
-GET /rest/getRecommendedSongs?u=&p=&f=json&size=20
-GET /rest/getDailySongs?u=&p=&f=json
-```
-返回结构:`{ recommendedSongs: { song: [ ... ] } }`(XML 下为 `recommendedSongs > children > song`)。
-
----
-
-## 三、未实现(官方标准方法,当前返回 `Method not found`)
-
-| 方法 | 类别 |
-|---|---|
-| `getVideos` | 媒体 |
-| `createUser` / `updateUser` / `deleteUser` / `changePassword` / `getAvatar` | 用户管理 |
-| `getPodcasts` / `getNewestPodcasts` / `refreshPodcasts` 等播客系列 | 播客 |
-| `getChatMessages` / `createChatMessage` | 聊天 |
-| `createShare` / `getShares` / `deleteShare` | 分享 |
-| `createInternetRadioStation` / `updateInternetRadioStation` / `deleteInternetRadioStation` | 电台管理 |
-
-> 注:lx-server 定位是「个人音乐库桥接」,未实现多用户管理 / 播客 / 分享 / 聊天等协作类接口属预期范围。
-
----
-
-## 四、2026-09-19 协议补全(本轮新增)
-
-补齐以下官方 / OpenSubsonic 方法(此前均走 default 分支返回 `Method not found`):
-
-| 方法 | 类别 | 说明 |
-|---|---|---|
-| `getIndexes` | 浏览 | 艺术家按拼音首字母索引分组 |
-| `getAlbumInfo` / `getAlbumInfo2` | 浏览 | 专辑介绍(`notes`)+ 封面 URL;构造不出平台直链时回退歌曲自带封面 |
-| `reportPlayback` | 播放 | OpenSubsonic `playbackReport` 扩展;仅 `stopped` 且未带 `ignoreScrobble` 时写入播放历史 |
-| `getPlayQueue` / `savePlayQueue` | 播放队列 | 按用户持久化;`current` 指向的歌曲缺失时按列表兜底 |
-| `getPlayQueueByIndex` / `savePlayQueueByIndex` | 播放队列 | OpenSubsonic `indexBasedQueue` 扩展;`currentIndex` 越界返回错误码 10 |
-| `getBookmarks` / `createBookmark` / `deleteBookmark` | 书签 | 按用户落盘持久化 |
-| `getSonicSimilarTracks` | 发现 | OpenSubsonic `sonicSimilarity` 扩展;复用同歌手相似歌挑选逻辑(排序分,非声学分析) |
-| `getTranscodeDecision` / `getTranscodeStream` | 转码 | OpenSubsonic `transcoding` 扩展;下发签名 `transcodeParams`,服务端 ffmpeg 流式转码 |
-
-配套变更:
-
-- `scrobble` 由 no-op 改为写入播放历史,`getNowPlaying` 返回正在播放列表(条目带 `positionMs`)。
-- `stream` / `download` 支持 OpenSubsonic `transcodeOffset`:`timeOffset`(秒)作为 ffmpeg 快速定位(`-ss` 置于 `-i` 之前)。
-- `getOpenSubsonicExtensions` 改为声明**实际实现**的扩展名:`formPost`、`songLyrics`、`playbackReport`、`sonicSimilarity`、`indexBasedQueue`、`transcodeOffset`、`transcoding`。此前的 `coverArtScaling` / `thumbnails` / `lyrics` 并非官方扩展名,客户端据此永远不会调用 `getLyricsBySongId`;封面缩放仍通过 `getCoverArt` 的 `size` 参数生效。
-- 服务端转码:`ffmpeg` 可用性探测(缺失时降级 302 直链)、并发信号量(`subsonic.transcode.maxConcurrent`)、目标格式(`subsonic.transcode.format`),并在 `handleStream` 中按客户端 `maxBitrate` 决定是否转码。
-- `search3` 补齐在线歌手 / 专辑搜索(此前只查本地库,未收藏时恒为空);歌手寻址改为相似度匹配(失败短缓存 + 诊断日志);跨源同名歌手合并与去重;简繁双向搜索(新增 `src/server/utils/zhConvert.ts`)。
-- 协议根元素、参数截断与封面尺寸修正;共享歌单支持「排行榜 / 歌单 / 都要」三态配置(`subsonic.sharedListMode`、`subsonic.sharedListSort`)。
diff --git a/docs/ui-audit/architecture.md b/docs/ui-audit/architecture.md
deleted file mode 100644
index a06578cb..00000000
--- a/docs/ui-audit/architecture.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# UI repair architecture
-
-Keep the existing vanilla JavaScript player and backend. Extend existing queue, search and collection entry points rather than adding another playback model. Add shared UI focus management for dialogs/drawers and history metadata for main views. Preserve nested lyric/detail navigation.
-
-Player CSS owns responsive layout using the same 1025px desktop boundary as the existing theme. The visualizer retains its artwork and audio behavior; its mobile canvas occupies a dedicated row. Static Tailwind output scans player markup and script-generated classes, using the existing CSS-variable theme and breakpoint configuration.
-
-Local artwork endpoints retain their existing authentication and path validation. An optional bounded thumbnail transform runs only after authorization, with byte-limited caching and constrained decoding/concurrency. Original images remain available for lyrics/Media Session/export. Closed queue content is materialized only when opened; existing queue state remains authoritative.
-
-Deployment uses an immutable overlay on the verified live image, a disposable candidate with isolated data and disabled background jobs, protected backup/rollback and recreation of only the lxserver Compose service. Compare assets, runtime, configuration, mounts and unrelated containers before/after.
diff --git a/docs/ui-audit/backup-verification.md b/docs/ui-audit/backup-verification.md
deleted file mode 100644
index 8b630f95..00000000
--- a/docs/ui-audit/backup-verification.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# 备份设置与恢复验证
-
-本次改动位于 `review/pr-488-openlist`,包含 PR #488 的数据安全修复及统一备份范围。没有提交或推送。
-
-## 界面决定
-
-- 系统设置首先回答“哪些数据会备份到哪个云端”,用五行数据类型和 WebDAV/OpenList 两列勾选展示。
-- 使用 Framed sheet 方向,seed `1449062923`,保留既有管理台字体、主题和页面框架;新范围面板使用平面分隔,不新增图标卡片或统计数字。
-- 普通数据备份与永久播放缓存分开。普通数据备份没有“上传后删本地”的选项;恢复单独执行。
-- 保留本地副本、镜像删除、启动恢复的后果写在控制旁;不把未保存的选择用于恢复。
-
-## 已验证
-
-- `npm test`:50 项通过,其中 OpenList/播放交付 20 项,TypeScript 测试 30 项。
-- TypeScript `--noEmit`、前端及配置 JS 语法、`git diff --check` 通过。
-- 真实 SQLite 和本地 HTTP OpenList 测试服务:校验后删本地、软删除登记恢复、损坏文件保护、上传/下载错误、同大小修改、目录开关、共享附件、无数据库的新实例恢复。
-- WebDAV 客户端替身及真实 ZIP:增量与 ZIP 范围相同、扩展名为空的快照、外部配置/快照映射、按类型恢复、失败重试、镜像删除与默认保留、恢复不反向上传。
-- 使用生产配置读取字段和实际 `saveConfigToFile` 写入函数,验证类型数组、布尔设置及 Subsonic 交付设置保存、重新加载后的往返一致性;写入失败会报告错误并清理临时文件。
-- 在真实 Chrome 中检查隔离设置面板,宽度为 1440、768、390;验证独立勾选和保存反馈。面板复用实际 HTML、CSS 和 App 方法,保存请求为测试替身,没有修改正在运行的服务配置。
-- 新面板源码检查 0 FAIL / 0 WARN;独立无头浏览器检查器启动失败,改用已有 Chrome 连接进行视觉与交互检查,没有将检查器标记为通过。
-
-截图位于本地 `output/playwright/backup-policy/`,包括桌面、平板和手机预览。方向说明也保存在该目录。
-
-## 尚未验证
-
-没有连接真实用户云盘、真实 WebDAV 服务,没有实际恢复或重启用户正在运行的服务,也没有执行 Docker 构建。Subsonic 测试执行生产处理方法并使用本地音频 HTTP 服务,转码调用以替身确认参数,未实跑 ffmpeg 或第三方客户端。
-
-完整文档构建受已有死链接阻断:`docs/chromecast/progress.md` 引用了不存在的 `combined-conflict-audit`。新增中英文备份指南已经接入导航。
-
-恢复是按文件替换,多个文件不是一个原子事务。发生部分失败时需检查失败列表;恢复后重启服务完整加载用户、歌单和音乐索引。普通数据备份不会覆盖外部自定义音乐目录。
diff --git a/docs/ui-audit/progress.md b/docs/ui-audit/progress.md
deleted file mode 100644
index ae8d2233..00000000
--- a/docs/ui-audit/progress.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# UI repair progress
-
-- [x] Port reviewed responsive/accessibility/search/artwork/queue repairs onto current upstream dev.
-- [x] Preserve upstream VU artwork and modern login layout while applying mobile reachability/input fixes.
-- [x] Enable explicit iPhone audio effects and provide a native-playback reload for AirPlay.
-- [x] Add runnable regression coverage and a pull-request test/build workflow.
-- [x] Production compilation and179regression tests pass.
-- [x] Combined browser checks at320/390/1024/1440px and desktop-to-mobile resizing pass without overflow or hidden Play.
-- [x] Actual modern login succeeds with a disposable local account; input text is16px, capitalization is disabled and the username field remains unique. Mode/rate menus fit and effects dialog Escape/focus behavior passes.
-- [x] Submitted [PR #463](https://github.com/XCQ0607/lxserver/pull/463) to upstream dev; GitHub Player validation passes (tests, production build and compiled smoke). Maintainer review/merge remains pending.
-
-The independently deployed repair source remains verified on Synology. Deployment state and private operational evidence are kept outside this upstream contribution. See [verification](verification.md) for test scope and physical-device limits.
diff --git a/docs/ui-audit/requirements.md b/docs/ui-audit/requirements.md
deleted file mode 100644
index 537d4b36..00000000
--- a/docs/ui-audit/requirements.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Web player UI repairs
-
-Repair the audited player without changing saved preferences, native/AirPlay/Cast routing, lyric metadata, provider configuration or library data.
-
-- Keep all player controls and the VU meter visible without overlap at 320px, 390px, tablet and desktop widths, including after resizing.
-- Expose labelled, keyboard-accessible controls with adequate touch targets. Dialogs contain focus, close on Escape where dismissal is allowed, and restore focus; closed drawers cannot receive focus.
-- Allow Play next and Add to queue without replacing the queue or interrupting playback. Include Default List in the collection picker.
-- Restore the prior main view through browser Back/Forward.
-- Cancel obsolete searches, ignore late responses, bound provider waits, show partial results and distinguish provider failure from no results.
-- Defer closed queue rendering/artwork, serve bounded thumbnails, compile utility CSS ahead of time and defer optional features.
-- Pass existing regression tests, type/build checks, isolated browser/HTTP checks and candidate/live asset verification before declaring deployment successful. Report physical-device limitations separately.
-- Verify mobile flows on the newest installed iPhone simulator runtime; keep simulator, browser-fixture and physical-device results distinct. Keep short-screen agreements/detail actions reachable, avoid iOS input zoom and hover-only first taps, and fit player menus within the viewport.
-
-- Make explicit iPhone effect edits change local audio output while native startup and existing AirPlay/Cast routes remain protected. Explain the native reload required to release Safari media capture before AirPlay.
diff --git a/docs/ui-audit/verification.md b/docs/ui-audit/verification.md
deleted file mode 100644
index d8e49886..00000000
--- a/docs/ui-audit/verification.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# UI repair verification
-
-- [x] Responsive footer/VU and controls, including resizing between mobile/tablet/desktop.
-- [x] Queue insertion preserves current playback; unopened queue omits rows and artwork.
-- [x] Default List supports individual and batch collection changes.
-- [x] Search replacement cancels prior requests; provider deadlines cover response-body reads; partial failures remain visible.
-- [x] Dialog focus/inert lifecycle handles nested input/confirmation surfaces.
-- [x] iPhone agreement remains scrollable in landscape; inputs use16px text and account names disable capitalization.
-- [x] Local iPhone effect edits activate processing; wireless/Cast edits remain saved-only; native reload releases processing and preserves saved settings.
-- [x] Regression suite:168CommonJS and11TypeScript tests on current upstream dev, plus production compilation and compiled authorization smoke test.
-
-The three configuration-migration tests from the separately deployed source are omitted here because that deployment-only module is absent from upstream dev. Backend route fixtures use upstream's actual cache-browse request context.
-
-The earlier deployed repair source passed182tests,69isolated/65live HTTP checks,31effects/session/login regressions per image and14public HTTPS asset checks. These deployment results describe that source; this PR is rebased onto upstream v2.1.4 and retains its newer VU/login/library features.
-
-An iPhone18Pro/iOS27.0 simulator, driven through CLI HID touches, verified a16x RMS change between EQ-12/+12dB, a1kHz→1.5kHz pitch shift, a reverb change and reversal of stereo channel dominance. Native reload left no graph/context/bridge and retained saved effects. The PR sound-effects and AirPlay modules match those signal-tested modules exactly. This establishes simulator processing, not physical speaker fidelity or receiver delivery.
-
-Broad simulator checks cover playback, seek/speed/mode, search/pagination, collections, queue removal/clear, playlists/rankings, lyrics/export, settings and artist/album navigation. Queue dragging, PWA/admin, exhaustive secondary settings/import/export mutations and physical receiver/lock-screen playback remain unverified. The original query-specific search500and old WebKit crash have no proven root cause; this change does not claim to fix those incidents.
-
-Combined branch browser verification uses a separate loopback-only server with disposable data/configuration. Account login succeeds; the modern login retains one username field,16px input text and disabled capitalization. At320/390/1024/1440px and after desktop-to-mobile resize, Play stays inside the viewport, the document has no horizontal overflow and the mobile VU starts below song information. Mode/rate menus remain within the phone viewport; the effects modal exposes dialog semantics and Escape closes it.
diff --git a/md/log/2.2.0-10.png b/md/log/2.2.0-10.png
new file mode 100644
index 00000000..8c6f955f
Binary files /dev/null and b/md/log/2.2.0-10.png differ
diff --git a/md/log/2.2.0-11.png b/md/log/2.2.0-11.png
new file mode 100644
index 00000000..34c7d6c4
Binary files /dev/null and b/md/log/2.2.0-11.png differ
diff --git a/md/log/2.2.0-2.png b/md/log/2.2.0-2.png
new file mode 100644
index 00000000..766094a3
Binary files /dev/null and b/md/log/2.2.0-2.png differ
diff --git a/md/log/2.2.0-3.png b/md/log/2.2.0-3.png
new file mode 100644
index 00000000..70e61574
Binary files /dev/null and b/md/log/2.2.0-3.png differ
diff --git a/md/log/2.2.0-4.png b/md/log/2.2.0-4.png
new file mode 100644
index 00000000..ed8d0fba
Binary files /dev/null and b/md/log/2.2.0-4.png differ
diff --git a/md/log/2.2.0-5.png b/md/log/2.2.0-5.png
new file mode 100644
index 00000000..80f7dc55
Binary files /dev/null and b/md/log/2.2.0-5.png differ
diff --git a/md/log/2.2.0-6.png b/md/log/2.2.0-6.png
new file mode 100644
index 00000000..e419eeb4
Binary files /dev/null and b/md/log/2.2.0-6.png differ
diff --git a/md/log/2.2.0-7.png b/md/log/2.2.0-7.png
new file mode 100644
index 00000000..d0552b6a
Binary files /dev/null and b/md/log/2.2.0-7.png differ
diff --git a/md/log/2.2.0-8.png b/md/log/2.2.0-8.png
new file mode 100644
index 00000000..1ff00c73
Binary files /dev/null and b/md/log/2.2.0-8.png differ
diff --git a/md/log/2.2.0-9.png b/md/log/2.2.0-9.png
new file mode 100644
index 00000000..628f8701
Binary files /dev/null and b/md/log/2.2.0-9.png differ
diff --git a/notice/notification.json b/notice/notification.json
index 02342148..c90909be 100644
--- a/notice/notification.json
+++ b/notice/notification.json
@@ -18,25 +18,25 @@
}
},
{
- "comment": "=== 版本更新 v2.1.4 ===",
- "id": "update_v2_1_4",
+ "comment": "=== 版本更新 v2.2.0 ===",
+ "id": "update_v2_2_0",
"status": "active",
"type": "version",
"logic": {
- "target_version": "v2.1.4",
+ "target_version": "v2.2.0",
"operator": "<",
"interval_hours": 24
},
"ui": {
- "title": "发现新版本 v2.1.4",
- "date": "2026-10-25",
- "message": "本次更新带来多项重磅视觉革新、连接监控与系统级体验跃升:\n1. [新增] 播放页动态特效与背景自适应: 背景与 12 种粒子特效彻底解耦组合,明暗主题深度自适应,支持同源引擎轻量化复用与自动休眠。\n2. [新增] 用户在线状态指示与禁用功能: 用户列表前置 WebSocket 实时连接指示圆点(🟢在线/🔴离线),新增「禁用用户」开关并在全链路安全拦截登录。\n3. [新增] 本地音乐智能检测与多平台匹配: 优先提取声学指纹比对,支持多平台智能匹配,新增并发线程配置与本地持久化。\n4. [新增] 侧边栏折叠手柄与 Alt+B 快捷键: 极简半透明折叠控制手柄,支持一键沉浸浏览,全局快捷键与设置开关实时联动。\n5. [新增] 自定义目录侧边面板与树形层级: 沉浸式侧边目录分类树,支持多选快捷开关与歌曲数量角标,弹窗与侧边栏体验统一。\n6. [新增] Subsonic 配置独立管理页: 与主配置完全解耦,支持独立端口监听(4050),统一流媒体播放与下载缓存机制。\n7. [新增] Web 播放器无线投放与 VU 表头: 接入原生 AirPlay 与 Chromecast 投放,新增双通道动圈 VU 表头与真实 Git 构建指纹。\n8. [新增] 自定义音源在线一键自动更新: 提示栏拆分为「更新」与「前往」,支持在当前页一键拉取最新自定义音源并重载。\n9. [重构] 文件管理器全面升级 VueFinder: 彻底淘汰陈旧 jQuery / elFinder,基于 Vue 3 + VueFinder 4.7.6 构建轻量自包含静态资源。",
+ "title": "发现新版本 v2.2.0",
+ "date": "2026-11-11",
+ "message": "本次更新带来备份体系、评分系统与后台界面的全面升级:\n1. [新增] 后台仪表盘与明/暗主题: 仪表盘重构为数据看板,新增日志活动、服务与存储、用户与设备、最近告警,并支持明/暗主题一键切换。\n2. [新增] OpenList 备份与统一云端备份: 支持 OpenList 数据备份与恢复,WebDAV / OpenList 可分别选择备份范围,并新增后台「备份管理」统一入口。\n3. [新增] 歌曲评分与 Subsonic 评分歌单: Web 播放器支持 1–5 星评分,Subsonic 生成按星级汇总的虚拟歌单,后台可查看评分与不喜欢数据。\n4. [新增] 聚合搜索升级: 多源关键词联想、分批加载与聚合排序,内置音乐 SDK 接口同步更新。\n5. [新增] 服务器已有歌曲优先播放: 优先使用服务器 music / cache 中的歌曲与本地歌词,Subsonic 可选返回缓存目录歌曲。\n6. [优化] 下载与缓存: 浏览器单曲 4 路分段下载,自动切歌优先使用已完成缓存,缓存清理不再误删 music 文件。\n7. [修复] PWA 下载认证失效、切歌进度未重置、自定义目录索引等多项问题。",
"confirm_text": "查看日志",
"cancel_text": "稍后提醒"
},
"action": {
"type": "link",
- "url": "https://github.com/XCQ0607/lxserver/releases/tag/v2.1.4"
+ "url": "https://github.com/XCQ0607/lxserver/releases/tag/v2.2.0"
}
}
]
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index da8c831c..ba635010 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "lx-music-sync-server",
- "version": "2.1.4",
+ "version": "2.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -20,7 +20,7 @@
"log4js": "^6.9.1",
"lru-cache": "11.5.3",
"message2call": "0.1.3",
- "module-alias": "^2.3.4",
+ "module-alias": "^2.3.5",
"music-tag-native": "0.2.5",
"needle": "^3.3.1",
"sharp": "0.35.5",
@@ -45,7 +45,7 @@
"@types/unzipper": "^0.10.11",
"@types/ws": "8.18.2",
"changelog-parser": "^4.1.0",
- "electron": "~43.8.3",
+ "electron": "~43.8.6",
"electron-builder": "^26.18.0",
"esbuild": "^0.28.2",
"husky": "^9.1.7",
@@ -6141,9 +6141,9 @@
}
},
"node_modules/electron": {
- "version": "43.8.3",
- "resolved": "https://registry.npmjs.org/electron/-/electron-43.8.3.tgz",
- "integrity": "sha512-KgvSm3bHXz96hSXzqxyRjLeli80rIdszbDQFKlRSfckmGazTPCH/CxUN31Dp9c5rFKTQSNf9gPJRWl1yWNB3pA==",
+ "version": "43.8.6",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-43.8.6.tgz",
+ "integrity": "sha512-2k3n5cjPHZqYTOn8iaJrm4fmb/QsvciomLD7PG9IECd0N/jr8Hxcu6Z/3mvMRrtXWCOp/i8qm5ZAcSnUzAa5Mw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8489,9 +8489,9 @@
}
},
"node_modules/module-alias": {
- "version": "2.3.4",
- "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.3.4.tgz",
- "integrity": "sha512-bOclZt8hkpuGgSSoG07PKmvzTizROilUTvLNyrMqvlC9snhs7y7GzjNWAVbISIOlhCP1T14rH1PDAV9iNyBq/w==",
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.3.5.tgz",
+ "integrity": "sha512-hbei6S1tXkg/P+ZJTyUqqL66LOeg/40WGcZYPFzIeBltTCRm+O4IZ/KRtR9hGanXaJxuvPj9q9ldl/cmbV6Osw==",
"license": "MIT"
},
"node_modules/mri": {
@@ -11970,4 +11970,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 9802e322..398b83e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lx-music-sync-server",
- "version": "2.1.4",
+ "version": "2.2.0",
"private": true,
"scripts": {
"prebuild": "npm run build:css && npm run build:files && node scripts/download-binary.js && node scripts/update-build-hash.js",
@@ -52,7 +52,7 @@
"log4js": "^6.9.1",
"lru-cache": "11.5.3",
"message2call": "0.1.3",
- "module-alias": "^2.3.4",
+ "module-alias": "^2.3.5",
"music-tag-native": "0.2.5",
"needle": "^3.3.1",
"sharp": "0.35.5",
@@ -76,7 +76,7 @@
"@types/unzipper": "^0.10.11",
"@types/ws": "8.18.2",
"changelog-parser": "^4.1.0",
- "electron": "~43.8.3",
+ "electron": "~43.8.6",
"electron-builder": "^26.18.0",
"esbuild": "^0.28.2",
"husky": "^9.1.7",
@@ -103,4 +103,4 @@
],
"exec": "npx tsx ./src/index.ts"
}
-}
+}
\ No newline at end of file
diff --git a/public/app.js b/public/app.js
index 6aac9a58..59bbe6d4 100644
--- a/public/app.js
+++ b/public/app.js
@@ -18,13 +18,12 @@
const API_BASE = '';
function stringToColor(str) {
- if (!str) return 'var(--accent-primary)';
+ if (!str) return 'var(--accent)';
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
- const h = Math.abs(hash) % 360;
- return `hsl(${h}, 70%, 45%)`;
+ return ['var(--avatar-1)', 'var(--avatar-2)', 'var(--avatar-3)', 'var(--avatar-4)'][Math.abs(hash) % 4];
}
class App {
@@ -74,7 +73,7 @@ class App {
const isPassword = pwdInput.type === 'password';
pwdInput.type = isPassword ? 'text' : 'password';
if (icon) {
- icon.className = isPassword ? 'fas fa-eye-slash text-indigo-400' : 'far fa-eye';
+ icon.className = isPassword ? 'fas fa-eye-slash tone-accent' : 'far fa-eye';
}
}
});
@@ -140,7 +139,7 @@ class App {
const statusEl = document.getElementById('user-custom-dir-status');
if (statusEl) {
statusEl.textContent = '目录路径已修改,请点击右侧「检测」验证可用性';
- statusEl.style.color = 'var(--text-secondary, #94a3b8)';
+ statusEl.style.color = 'var(--text-2)';
}
});
@@ -322,7 +321,7 @@ class App {
const sizeClass = ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl'][Math.floor(Math.random() * 6)];
item.className = `fas ${icon} admin-floating-note ${sizeClass}`;
item.style.left = `${Math.random() * 95}%`;
-
+
const duration = 12 + Math.random() * 12;
const delay = Math.random() * duration;
item.style.animationDuration = `${duration}s`;
@@ -351,7 +350,7 @@ class App {
x: Math.random() * width,
y: Math.random() * height,
radius: Math.random() * 2 + 0.8,
- color: Math.random() > 0.5 ? 'rgba(99, 102, 241, ' : 'rgba(6, 182, 212, ',
+ color: Math.random() > 0.5 ? 'accent' : 'info',
alpha: Math.random() * 0.45 + 0.1,
speedY: Math.random() * 0.4 + 0.15,
speedX: (Math.random() - 0.5) * 0.2,
@@ -362,6 +361,7 @@ class App {
const animateParticles = () => {
if (document.getElementById('login-overlay')?.classList.contains('hidden')) return;
ctx.clearRect(0, 0, width, height);
+ const colors = themeColors();
for (const p of particles) {
p.y -= p.speedY;
p.x += p.speedX;
@@ -377,13 +377,16 @@ class App {
const currentAlpha = p.alpha * (0.6 + 0.4 * Math.sin(p.pulse));
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
- ctx.fillStyle = p.color + currentAlpha + ')';
+ ctx.globalAlpha = currentAlpha;
+ ctx.fillStyle = colors[p.color];
ctx.shadowBlur = 8;
- ctx.shadowColor = p.color + '0.5)';
+ ctx.shadowColor = colors[p.color];
ctx.fill();
}
- requestAnimationFrame(animateParticles);
+ ctx.globalAlpha = 1;
+ this._loginParticlesFrame = requestAnimationFrame(animateParticles);
};
+ this._resumeLoginParticles = animateParticles;
animateParticles();
}
@@ -441,7 +444,7 @@ class App {
if (btnText) btnText.textContent = '验证成功,正在进入...';
if (btn) {
btn.classList.remove('admin-btn-submit');
- btn.classList.add('bg-emerald-600');
+ btn.classList.add('tone-fill-success');
}
if (card) {
@@ -477,15 +480,49 @@ class App {
logout() {
localStorage.removeItem('lx_auth');
- location.reload();
+ this.password = null;
+ for (const timer of ['monitorTimer', 'logsRefreshTimer', '_openlistRestoreTimer', '_olbConnectionTimer', '_olIntegrityTimer']) {
+ clearInterval(this[timer]);
+ this[timer] = null;
+ }
+ document.getElementById('app')?.classList.add('hidden');
+ document.getElementById('login-overlay')?.classList.remove('hidden');
+ const card = document.getElementById('admin-login-card');
+ if (card) { card.style.removeProperty('transform'); card.style.removeProperty('opacity'); }
+ const button = document.getElementById('login-btn');
+ if (button) {
+ button.disabled = false;
+ button.classList.remove('tone-fill-success');
+ button.classList.add('admin-btn-submit');
+ const icon = button.querySelector('i');
+ if (icon) icon.className = 'fas fa-right-to-bracket text-sm';
+ }
+ const text = document.getElementById('login-btn-text');
+ if (text) text.textContent = '登录控制台';
+ if (!this._loginEffectsInited) this.initLoginScreenEffects();
+ else if (!this._loginParticlesFrame) this._resumeLoginParticles?.();
}
showApp() {
+ cancelAnimationFrame(this._loginParticlesFrame);
+ this._loginParticlesFrame = null;
document.getElementById('login-overlay').classList.add('hidden');
document.getElementById('app').classList.remove('hidden');
}
async switchView(viewName) {
+ // 兼容旧入口:WebDAV / OpenList / 快照管理已并入「备份管理」,转向对应页签
+ const legacyBackupViews = { webdav: 'cloud', openlist: 'cloud', snapshots: 'local' };
+ if (legacyBackupViews[viewName]) {
+ this._bmPendingTab = legacyBackupViews[viewName];
+ viewName = 'backups';
+ }
+ // 离开备份管理且存在未保存输入时先确认,避免静默丢失
+ if (this.currentView === 'backups' && viewName !== 'backups' && this.hasBackupUnsaved()) {
+ const leave = await showSelect('有未保存的备份设置', `以下内容尚未保存:${this.backupUnsavedLabels().join('、')}。\n离开后这些修改将被丢弃。`, { danger: true, confirmText: '放弃修改并离开', cancelText: '留在此页' });
+ if (!leave) return;
+ this.clearBackupDirty();
+ }
// 更新导航状态
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.toggle('active', item.dataset.view === viewName);
@@ -503,11 +540,9 @@ class App {
data: '数据查看',
config: '系统配置',
logs: '系统日志',
- webdav: 'WebDAV同步',
+ backups: '备份管理',
subsonic: 'Subsonic 配置',
- openlist: 'OpenList 同步',
files: '文件管理',
- snapshots: '快照管理',
about: '关于'
};
document.getElementById('page-title').textContent = titles[viewName] || viewName;
@@ -532,29 +567,17 @@ class App {
this.loadLogs();
this.startLogsAutoRefresh();
break;
- case 'webdav':
- try {
- const status = await this.request('/api/status');
- this.checkWebDAVConfig(status.isWebDAVConfigured);
- this.loadSyncLogs();
- } catch (e) {
- console.error('Failed to check webdav status:', e);
- }
- break;
case 'subsonic':
this.loadSubsonicConfig();
break;
- case 'backups':
- case 'snapshots':
- if (this.currentBackupTab === 'snapshot') {
- this.loadSnapshots();
- } else {
- this.loadConfigBackups();
- }
- break;
- case 'openlist':
- this.loadOpenlistConfig();
+ case 'backups': {
+ const targetTab = this._bmPendingTab || this.currentBackupCenterTab || 'overview';
+ const targetSub = this._bmPendingSub || '';
+ this._bmPendingTab = null;
+ this._bmPendingSub = null;
+ await this.enterBackupCenter(targetTab, targetSub);
break;
+ }
case 'about':
this.loadAbout();
break;
@@ -615,7 +638,7 @@ class App {
}
} catch (e) {
console.error('Failed to load about content:', e);
- container.innerHTML = '