# 内部服务与上游边界

[文档首页](./README.md)

本页用于服务端维护，不是公网客户端接入说明。内部主机名只在部署网络中解析；不要暴露端口8770/8780内部路径，或向第三方发送内部密钥。

## 1. 管理内部统计

基地址 `http://watch-music-admin:8780`。均为POST，头 `X-Admin-Internal: <INTERNAL_SECRET>`；JSON对象，Content-Length明确，最多65536字节。非POST或密钥不匹配返回404，不向未授权请求暴露内部功能。

### 1.1 `POST /internal/events`

body `{events: Event[]}`，最多128条；网关正常批次最多64条。

Event字段：`ip`有效IPv4/IPv6、`device`可选32位小写hex实例ID、`version`最多保留32字符、`variant`最多保留16字符。不提供关键词、播放内容、媒体URL或硬件标识。

200 `{ok:true}`。无效事件可跳过；events不是数组或超128条为400。此接口信任内部网关已验证设备令牌，不让客户端直接上报任意设备ID。

无设备的IP以私有HMAC派生活动ID，kind=legacy_ip；有设备kind=device。撤回实例单向哈希标记存在时过滤该事件。活动最多50000行并保留30天，审计最多10000行并清理30天前内容。

### 1.2 `POST /internal/register`

body `{ip:string,consent:true,version?:string,variant?:string}`。

200 `{token,device_id,online_window_seconds:300,retention_days:30}`，与公开register结果相同。400IP/consent错误；403IP被禁；429每IP每小时5次。服务器生成随机实例与HMAC令牌。

### 1.3 `POST /internal/forget`

body `{token:string}`。200 `{ok:true}`；400凭证无效。删除实例统计并写30天单向哈希撤回标记，不解封设备/IP。

网关调用超时2秒；事件队列失败允许丢失，不无界重试。注册/撤回错误会转给公开调用者。内部密钥与管理员Cookie、CSRF、SSH密码都不是同一概念。

## 2. LX运行时

基地址 `http://watch-music-lx:8770`。当前协议本身没有公网认证；安全依赖私有网络、不暴露端口、网关只允许已知注册脚本及服务器歌曲。

### 2.1 `GET /plugins`

精确路径，无query合同。200：`{plugins:Plugin[],runtime:"quickjs-isolated-process",notice:string}`。

注册表仅列启用项，校验schema/id/完整哈希；条目含id/name/version/sha256/imported和进程内最近status/detail/sources/checked_at。此内部目录没有网关追加的默认别名；网关再过滤启用策略并投影最近取流状态。

其他路径404。目录生成异常不保证统一JSON错误，应由网关处理为503。

### 2.2 `POST /run`

| body字段 | 类型 | 要求 |
| --- | --- | --- |
| `plugin` | string | 当前注册且启用的真实插件ID，不是默认别名 |
| `action` | string | probe/musicUrl/lyric/pic |
| `source` | string | 非probe通常需要wy/tx/kg/kw/mg，必须在脚本初始化声明中 |
| `quality` | string | 默认128k；musicUrl要求在声明qualitys中 |
| `musicInfo` | object | 服务器构造的歌曲元数据；probe无需 |

示例：

```json
{"plugin":"0123456789abcdef","action":"musicUrl","source":"wy","quality":"128k","musicInfo":{"name":"示例歌曲","singer":"示例歌手","id":"123456","songmid":"123456","albumName":"示例专辑","interval":"240"}}
```

网关基础musicInfo包括name/singer/songmid/id/albumName/interval；kg加hash、mg加copyrightId，并仅复制内部extra中的albumId/albumMid/strMediaMid/albumAudioId/_types/types/hash/songmid/copyrightId等允许字段。公开客户端不应构造这些字段绕过歌曲索引。

200正常业务结果：

```json
{"ok":true,"value":"https://public-audio.example.invalid/example.mp3"}
```

这里URL只是说明；内部可能返回value对象，实际不可假定总是字符串。probe成功为`{ok:true,state:"initialized",sources,detail}`。

**内部HTTP200可能业务失败**：`{ok:false,detail,error?}`，例如执行失败、超时或资源超限。网关probe转422并保留结果；resolve/lyric转422 `{error:detail}`。body长度非法/插件或动作无效400；双槽忙429；未预期异常500。非/run路径404。

运行时虽接受pic，当前公开网关没有plugin_pic接口；不能把内部动作枚举当作公网可调用接口。

### 2.3 隔离限制

每次创建工作进程，超时28秒，CPU8秒、地址空间180MiB、输出文件1MiB、文件描述符32；QuickJS上下文内存40MiB、栈1MiB。脚本最多14个外部请求，支持GET/POST/HEAD，body小于128KiB，响应最多2MiB，单请求超时1..8秒。

外部请求通过safenet公共网络校验；不允许任意内网访问。运行时结果不能证明完整音频有效，音频字节由网关在真正取流时验证。脚本初始化/质量声明也不是授权凭证。

## 3. go-music-dl上游

默认前缀 `/music`，端口8080。它的HTML片段路由、配置、本地文件、登录及收藏功能是上游应用能力，**并未等价公开为Wristhyme接口**。

### 3.1 网关实际使用的路由

| 方法/上游路径 | 参数 | 典型响应/用途 |
| --- | --- | --- |
| GET `/music/search` | q/sources/type/page_size/page | song/playlist的HTML片段，网关提取data-* |
| GET `/music/playlist` | source/id/page_size/page | 歌曲HTML及当前页/总页数标记 |
| GET `/music/recommend` | sources | 公开推荐HTML，轻量网关去重切页 |
| GET `/music/inspect` | id/source/duration/extra等服务器歌曲字段 | JSON valid/url/size/bitrate，网关移除url |
| GET `/music/lyric` | 服务器歌曲字段、format=lrc | 原始歌词文本 |
| GET `/music/download` | 服务器歌曲字段、stream=1 | 音频；可转发Range |

netease_catalog通过专用Go helper执行官方元数据搜索，不要求通用上游HTML支持相同search_signals；hot_charts访问固定官方主机，不能据此增加任意外部API代理能力。

### 3.2 上游源码路由索引

下表仅为本地vendor路由盘点，便于维护者定位源码，**不是宣称生产上游已部署同版或已对公网开放**。上游配置/登录/文件接口的完整细节应在单独维护上游项目时按其当前版本核对，不复用Wristhyme的Cookie/CSRF/设备头。

| 文件（vendor/go-music-dl/internal/web/） | 已注册相对路径和方法 |
| --- | --- |
| music.go | GET `/`、`/recommend`、`/user_playlists`、`/playlist_categories`、`/category_playlists`、`/search`、`/playlist`、`/album`、`/album_jump`、`/inspect`、`/switch_source`、`/cover_proxy`、`/lyric` |
| music.go | GET/POST `/download`、`/download_lrc`、`/download_cover`；GET/DELETE `/api/downloads/records`；POST `/api/downloads/precheck` |
| local_music.go | GET `/local_music_page`、`/local_music`、`/local_music/duplicates`；GET/POST `/local_music/cover`；POST `/local_music/upload`、`/local_music/batch_match`、`/local_music/auto_cache`、`/local_music/reindex`；DELETE `/local_music` |
| collection.go | GET `/my_collections`、`/collection`；收藏分组`/collections`下GET/POST空路径、POST `/import`、PUT/DELETE `/:id`、GET/POST/DELETE `/:id/songs`、POST `/:id/songs/batch` |
| local_music.go | 收藏分组下POST `/:id/local_music`、`/:id/local_music/batch` |
| auth.go | GET/POST `/setup`、`/login`；POST `/logout` |
| qr_login.go | GET/POST `/qr_login/:source` |
| update.go | GET `/app_update/check`、`/github_proxy/test` |
| server.go | GET `/healthz`、`/render`、`/cookies`、`/settings`及静态资源；POST `/cookies`、`/settings` |

拼接前缀后例如`/music/api/downloads/records`，不是`/api/downloads/records`。不要把vendor登录、Cookies读写、本地音乐上传/删除代理成公开接口；这些有不同的安全边界。

## 4. 反向代理与部署

已找到的Caddy配置：`/admin`及子路径代理8780；`/downloads/*`静态服务；`/`和`/LICENSE`静态服务；其他路径代理8766。代理覆盖X-Real-IP并移除外来X-Admin-Internal。

后台body代理限制1500KB，其他网关body4KB；后台响应头等待15秒，音乐55秒。这些是配置证据，不是当前线上已重新核验的数值。发布feed的缓存处理也可能有后续改动，以生产Caddy文件为准。

只有公开文档中的路径可作为客户端合同；运行时内部/run、管理/internal、go-music-dl配置等不应因代理兜底或源码存在被当作已开放API。
