Scout/使用说明.md
2026-08-31 21:19:15 +08:00

22 KiB
Raw Blame History

Scout 轻量化包管理程序

Scout 是一个使用 Go + Vue 构建的轻量化包管理程序,负责包上传、版本管理、客户端更新检查、实时更新通知和账户权限控制。

当前能力

  • 普通用户、开发者、管理员三种角色。
  • 用户注册、登录、退出和会话认证。
  • 密码使用带随机盐的 PBKDF2-HMAC-SHA256 方案保存,不保存明文密码。
  • 管理员可查看用户、授予开发者权限和启停账户。
  • 开发者或管理员可以上传包;旧版开发者令牌仍可兼容脚本调用。
  • 包名、版本号、文件名和空文件校验。
  • 重复版本拒绝覆盖,包文件保存后提供 SHA-256 校验值。
  • 包列表支持 query 搜索、page 分页和 page_size 条数限制。
  • 客户端通过 SSE 接收新包通知,通过下载接口获取包文件。
  • 邮件只用于管理员通知、上传用户操作通知和安全认证,不发送普通用户包更新订阅邮件。
  • 未配置 SMTP 时,邮件会以“待发送邮件”形式写入服务端日志,便于本地测试。

目录结构

Scout/
├── cmd/
│   ├── scout-server/       # Go 服务端
│   └── scout-client/       # Go 客户端命令行工具
├── internal/
│   ├── httpapi/             # HTTP API、认证、权限和通知
│   ├── notify/              # SMTP 邮件发送抽象
│   ├── registry/            # 包存储、元数据和版本比较
│   └── users/                # 用户、角色、密码哈希和会话
├── pkg/client/              # 可复用 Go 客户端模块
└── frontend/                # Vue 3 + Vite 管理界面

启动 Go 服务端

PowerShell 示例:

$env:SCOUT_DEVELOPER_TOKEN = "请替换为实际开发者令牌"
$env:SCOUT_ADMIN_EMAIL = "admin@example.com"
$env:SCOUT_ADMIN_NAME = "系统管理员"
$env:SCOUT_ADMIN_PASSWORD = "请使用长度至少 8 位的管理员密码"
$env:SCOUT_ADDR = ":8080"
$env:SCOUT_DATA_DIR = "data"
go run .\cmd\scout-server

服务端会在 data/users.json 保存用户和密码哈希,包文件仍保存在 data/包名/版本/。不要把 data/ 目录提交到远程仓库。

健康检查:

GET http://127.0.0.1:8080/healthz

SMTP 配置

需要真实发送邮件时配置:

$env:SCOUT_SMTP_HOST = "smtp.example.com"
$env:SCOUT_SMTP_PORT = "587"
$env:SCOUT_SMTP_USERNAME = "账号"
$env:SCOUT_SMTP_PASSWORD = "密码"
$env:SCOUT_SMTP_FROM = "Scout <noreply@example.com>"
$env:SCOUT_SMTP_TLS = "false"

SMTP 密码只通过环境变量或部署平台密钥管理提供,不要写入代码、配置文件或 Git 远程地址。生产环境建议在反向代理后使用 HTTPS、限制跨域来源、配置限流,并使用独立的开发者账户而不是兼容令牌。

启动 Vue 前端

打开第二个终端:

Set-Location .\frontend
npm.cmd install
npm.cmd run dev

浏览器打开 Vite 输出的地址。当前本地测试地址通常为:

http://127.0.0.1:5174/

页面顶部可以修改服务端地址。页面支持注册、登录、密码重置申请、包搜索、包下载和开发者上传。管理员登录后可以管理用户角色和账户状态。

HTTP 接口

账户和安全认证

方法 路径 用途 认证
POST /api/v1/auth/register 注册用户 无
POST /api/v1/auth/login 登录并获取会话令牌 无
POST /api/v1/auth/logout 退出当前会话 Bearer 会话令牌
GET /api/v1/auth/me 获取当前用户 Bearer 会话令牌
POST /api/v1/auth/password/request 申请密码重置邮件 无
POST /api/v1/auth/password/reset 使用一次性令牌重置密码 无
GET /api/v1/admin/users 查看用户列表 管理员
PATCH /api/v1/admin/users/{id} 更新角色或启停账户 管理员

登录请求示例:

{
  "email": "admin@example.com",
  "password": "管理员密码"
}

登录响应中的 session 用于后续请求:

Authorization: Bearer <session>

包管理

方法 路径 用途 认证
GET /healthz 健康检查 无
GET /api/v1/packages?query=demo&page=1&page_size=50 搜索和分页获取最新包 无
GET /api/v1/packages/{name} 获取指定包最新版本 无
GET /api/v1/packages/{name}/versions 获取指定包全部版本 无
GET /api/v1/packages/{name}/{version}/download 下载指定版本 无
GET /api/v1/updates/stream?packages=a,b 监听包更新 SSE 通知 无
POST /api/v1/packages/upload 上传新包 开发者/管理员会话或兼容令牌

上传接口使用 multipart/form-data,字段如下:

  • name:包名,只允许字母、数字、点、下划线和短横线。
  • version:版本号,只允许字母、数字、点、加号、下划线和短横线。
  • file:非空包文件,文件名必须安全且长度不超过 128。
  • description、platform、architecture:可选元数据。

兼容令牌请求头:

Authorization: Bearer <SCOUT_DEVELOPER_TOKEN>

邮件通知范围

  • 新用户注册:通知注册用户,并通知管理员。
  • 用户登录:向当前用户发送安全登录提醒。
  • 密码重置:向用户发送一次性重置令牌。
  • 密码修改和账户权限变更:通知相关用户。
  • 包上传成功或失败:通知执行上传的开发者/管理员。
  • 普通用户不会因为包版本更新收到邮件,仍通过 SSE 或客户端主动检查获取更新。

多项目发布中心

Scout 服务端是独立发布中心,不需要修改 SSL、Smail 或后续接入项目的源代码。每个项目使用独立的包目录、版本索引和 SSE 通知流;项目之间可以存在同名包而不会互相覆盖。

服务端默认保留 default 兼容项目,对应原有 /api/v1/packages 接口。新增项目使用项目级接口,例如项目标识为 ssl 时:

方法 路径 用途 认证
GET /api/v1/projects 查看项目列表 无
POST /api/v1/projects 创建项目 管理员
GET /api/v1/projects/{project} 查看项目配置 无
PATCH /api/v1/projects/{project} 修改项目名称、说明或状态 管理员
GET /api/v1/projects/{project}/packages 查看项目包列表 项目查看者令牌或成员会话;未启用项目权限时公开
POST /api/v1/projects/{project}/packages/upload 上传项目包 项目开发者令牌或成员会话
GET /api/v1/projects/{project}/packages/{name}/{version}/download 下载项目包 项目查看者令牌或成员会话;未启用项目权限时公开
GET /api/v1/projects/{project}/updates/stream 监听项目更新 项目查看者令牌或成员会话;未启用项目权限时公开
GET /api/v1/projects/{project}/members 查看项目成员 项目管理员成员会话或全局管理员会话
POST /api/v1/projects/{project}/members 添加项目成员 项目管理员成员会话或全局管理员会话
PATCH /api/v1/projects/{project}/members/{userID} 修改项目成员角色 项目管理员成员会话或全局管理员会话
DELETE /api/v1/projects/{project}/members/{userID} 删除项目成员 项目管理员成员会话或全局管理员会话
GET /api/v1/projects/{project}/tokens 查看项目令牌元数据 项目管理员成员会话或全局管理员会话
POST /api/v1/projects/{project}/tokens 创建项目令牌 项目管理员成员会话或全局管理员会话
POST /api/v1/projects/{project}/tokens/{tokenID}/revoke 吊销项目令牌 项目管理员成员会话或全局管理员会话

项目状态为 disabled 时,项目配置仍可查询,但包列表、上传、下载和更新通知会被拒绝。项目目录位于:

data/projects/{project}/

项目权限数据位于 data/project-access.json。项目令牌创建后只返回一次 secret 明文,服务端只保存哈希;调用包列表、下载和更新流时可使用 X-Project-Token 请求头,也可使用 Authorization: Bearer <项目令牌>。项目令牌只能访问创建时绑定的项目。

项目角色如下:

  • project_viewer:查看包、下载包、监听更新。
  • project_developer:包含查看权限,并允许上传包。
  • project_admin:管理项目成员和项目令牌;项目令牌本身不能授予此角色。

当项目尚未添加成员或令牌时,读取接口保持兼容公开访问;首次添加成员或令牌后,该项目的包读取、下载和更新流会启用认证。这样可以在不改造 SSL、Smail 的情况下逐项目迁移到安全模式。

接入 SSL、Smail 或后续项目时,只需在发布中心创建对应项目,再让宿主应用使用项目级客户端:

api, err := client.NewProject(
    "https://packages.example.com",
    "ssl",
    client.WithSessionToken(session),
)
if err != nil {
    return err
}
latest, available, err := api.CheckUpdate(ctx, "scert", currentVersion)

客户端不会修改接入项目;项目只通过 Scout API 和 pkg/client、pkg/updater 模块通信。

Go 客户端命令

编译命令行客户端:

go build -o .\scout-client.exe .\cmd\scout-client

检查更新:

.\scout-client.exe check --server http://127.0.0.1:8080 --project ssl --package demo-core --current 1.0.0

下载最新版本:

.\scout-client.exe download --server http://127.0.0.1:8080 --project ssl --package demo-core --output .\demo-core.pkg

监听更新通知:

.\scout-client.exe watch --server http://127.0.0.1:8080 --project ssl --packages demo-core

更新生命周期命令:

# 当前程序继续运行时下载并准备新版本
.\scout-client.exe prepare --server http://127.0.0.1:8080 --package demo-core --root .\.scout-update --current 1.0.0 --entrypoint demo-core
# 由用户决定后执行平台安装处理和版本切换
.\scout-client.exe install --server http://127.0.0.1:8080 --package demo-core --root .\.scout-update --current 1.0.0 --entrypoint demo-core
.\scout-client.exe activate --server http://127.0.0.1:8080 --package demo-core --root .\.scout-update --current 1.0.0 --entrypoint demo-core
# 新进程启动并确认健康后,Scout 才清理旧版本
.\scout-client.exe healthy --server http://127.0.0.1:8080 --package demo-core --root .\.scout-update --entrypoint demo-core

使用兼容令牌上传:

.\scout-client.exe upload `
  --server http://127.0.0.1:8080 `
  --token $env:SCOUT_DEVELOPER_TOKEN `
  --package demo-core `
  --version 1.2.0 `
  --file .\demo-core.pkg `
  --platform windows `
  --architecture amd64

Go 客户端模块

如果客户端本身也是 Go 程序,可以直接导入 scout/pkg/client。

登录账户:

api, err := client.New("http://127.0.0.1:8080")
if err != nil {
    return err
}
login, err := api.Login(ctx, "dev@example.com", "开发者密码")
if err != nil {
    return err
}
fmt.Println("登录用户:", login.User.Email)

登录后检查更新、下载和监听:

latest, available, err := api.CheckUpdate(ctx, "demo-core", "1.0.0")
if err != nil {
    return err
}
if available {
    _, err = api.Download(ctx, "demo-core", latest.Version, "./demo-core.pkg")
}
updates, err := api.WatchUpdates(ctx, "demo-core")
if err != nil {
    return err
}
for update := range updates {
    fmt.Println("收到更新:", update.Package.Name, update.Package.Version)
}

pkg/client 提供的方法包括:

方法 用途
New 创建客户端
WithDeveloperToken 配置兼容开发者令牌
WithSessionToken 配置登录会话令牌
WithProjectToken 配置绑定当前项目的访问令牌
Register 注册用户
Login 登录并自动保存会话令牌
Me 获取当前登录用户
Logout 退出并清理会话令牌
RequestPasswordReset 申请密码重置邮件
ResetPassword 使用令牌重置密码
ListPackages 获取包列表
GetLatest 获取指定包最新版本
ListVersions 获取指定包全部版本
CheckUpdate 比较当前版本并判断是否有更新
Download 下载并校验 SHA-256
WatchUpdates 监听 SSE 更新通知
Upload 使用兼容令牌或会话上传包
ListProjectMembers 查看当前项目成员
AddProjectMember 添加当前项目成员
UpdateProjectMember 修改当前项目成员角色
DeleteProjectMember 删除当前项目成员
ListProjectTokens 查看当前项目令牌元数据
CreateProjectToken 创建项目令牌并获取一次性明文
RevokeProjectToken 吊销当前项目令牌

跨平台客户端集成

pkg/client 负责服务端协议,pkg/updater 负责下载、校验、暂存、激活、健康确认、回滚和旧版本清理。业务程序不需要依赖 internal 目录,只需注入 client.Client 创建 updater.Manager。

推荐由宿主程序按以下流程调用:

  1. 启动时创建 Manager,调用 RecoverStaleActivation 检查上一次更新是否超时。
  2. 后台调用 Check 或 Watch,收到对应平台和架构的更新通知。
  3. 在业务进程仍运行时调用 Prepare,完成下载、SHA-256 校验和暂存。
  4. 由用户决定立即重启还是稍后重启;立即重启前可调用 Install,再调用 Activate。
  5. 宿主程序退出并重新启动后调用 ConfirmHealthy,成功后才会清理旧版本。
  6. 启动失败或健康确认超时,调用 Rollback 恢复上一版本。

可运行的最小集成示例位于:

examples/client-integration/main.go

平台适配边界

updater.New 默认选择平台适配器,也可以通过 updater.Config.Adapter 注入宿主自己的实现:

  • Linux、Windows、macOS:默认适配器只启动已校验的版本入口,不执行系统级安装器,不停止当前进程。
  • Android:默认适配器只负责准备文件,Install 会返回 updater.ErrPlatformInstallRequired。宿主 Android 应用必须使用系统 PackageInstaller 或企业设备管理能力完成安装,并由系统授权流程决定是否允许安装。
  • macOS 应将 Entrypoint 指向 .app/Contents/MacOS/ 内的真实可执行文件,不能直接把 .app 目录当作入口。
  • Windows、Linux 和 macOS 的旧版本清理由健康确认后的 Cleanup 完成;清理前会保护当前版本和待激活版本。

Android 普通应用不能承诺无需用户确认的静默 APK 安装。Scout 不绕过系统授权,也不会通过提升权限实现静默安装;需要无人值守时必须由受控设备管理方案提供能力。

一个 Scout 包包含多个平台目标

.scout 文件本身是一个包含 scout-package.json 的 ZIP。开发者可以在同一个包中声明多个 targets,例如:

{
  "schema_version": 1,
  "name": "demo-core",
  "version": "1.2.0",
  "channel": "stable",
  "package_type": "full",
  "targets": [
    {
      "platform": "windows",
      "architecture": "amd64",
      "path": "targets/windows/amd64",
      "entrypoint": "targets/windows/amd64/demo-core.exe"
    },
    {
      "platform": "linux",
      "architecture": "amd64",
      "path": "targets/linux/amd64",
      "entrypoint": "targets/linux/amd64/demo-core"
    },
    {
      "platform": "macos",
      "architecture": "arm64",
      "path": "targets/macos/arm64",
      "entrypoint": "targets/macos/arm64/demo-core"
    },
    {
      "platform": "android",
      "architecture": "arm64",
      "path": "targets/android/arm64",
      "entrypoint": "targets/android/arm64/demo.apk"
    }
  ]
}

服务端上传时会解析每个目标并分类保存;客户端查询时只会收到匹配自身平台、架构、通道和当前版本的目标,其他客户端不会收到该更新。

跨平台构建示例

Go 客户端和服务端需要分别构建对应操作系统的二进制,不能把一个 Go 可执行文件直接运行在所有系统上;可以把这些二进制放进同一个 .scout 包,达到“一个包,多平台目标”的效果:

$env:CGO_ENABLED = "0"
$env:GOOS = "windows"; $env:GOARCH = "amd64"; go build -trimpath -o .\targets\windows\amd64\demo-core.exe .\cmd\scout-client
$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -trimpath -o .\targets\linux\amd64\demo-core .\cmd\scout-client
$env:GOOS = "darwin"; $env:GOARCH = "arm64"; go build -trimpath -o .\targets\macos\arm64\demo-core .\cmd\scout-client
Remove-Item Env:GOOS
Remove-Item Env:GOARCH
Remove-Item Env:CGO_ENABLED

Android APK 通常由 Android 工程和签名流水线生成,再放入 targets/android/<architecture>/;Scout 只负责分发和校验,不代替 Android 签名、系统安装授权或应用商店审核。

测试

go test ./...
go test -race ./...
Set-Location .\frontend
npm.cmd run build

服务器部署与反向代理

Scout 的服务端可以部署在 Linux 服务器上。推荐让 Scout 只监听内网地址,由现有的 Nginx、Caddy、云负载均衡或其他网关负责公网入口、域名、SSL 证书和 DNS。Scout 不会申请、续期或修改 SSL、DNS,也不会保存域名服务商凭据。

生产环境配置

复制示例配置并写入服务器的密钥管理位置:

cp deploy/scout.env.example /etc/scout/scout.env
chmod 600 /etc/scout/scout.env

必须替换以下占位内容:

  • SCOUT_PUBLIC_URL:客户端实际访问的完整地址,例如 https://packages.example.com。
  • SCOUT_ALLOWED_ORIGINS:允许访问 API 的前端来源,多个来源使用英文逗号分隔。
  • SCOUT_ALLOWED_HOSTS:反向代理转发到 Scout 的 Host,多个主机名使用英文逗号分隔。
  • SCOUT_ADMIN_EMAIL、SCOUT_ADMIN_PASSWORD:生产管理员邮箱和随机强密码。
  • SCOUT_SMTP_*:管理员、上传用户和安全认证通知使用的 SMTP 配置。

生产环境会拒绝默认测试管理员密码、缺少允许来源、缺少允许主机和缺少 SMTP 发件配置。SCOUT_ALLOW_LEGACY_TOKEN 默认关闭;推荐使用开发者账户会话上传,不要在公网长期保留兼容令牌。

原生 systemd 部署

建议在 Linux 服务器上直接运行 Go 编译后的二进制,并由 systemd 管理服务。

先在构建机编译服务端和前端:

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o scout-server ./cmd/scout-server
cd frontend
npm ci
npm run build
cd ..

将文件复制到服务器:

if ! id scout >/dev/null 2>&1; then
  sudo useradd --system --home /var/lib/scout --shell /usr/sbin/nologin scout
fi
sudo install -d -o scout -g scout /opt/scout /opt/scout/frontend /var/lib/scout /etc/scout
sudo install -m 0755 scout-server /opt/scout/scout-server
sudo cp -r frontend/dist /opt/scout/frontend/
sudo install -m 0600 deploy/scout.env.example /etc/scout/scout.env

编辑 /etc/scout/scout.env,替换所有生产占位符,然后设置权限:

sudo chown -R scout:scout /opt/scout /var/lib/scout
sudo chmod 600 /etc/scout/scout.env
sudo install -m 0644 deploy/scout.service /etc/systemd/system/scout.service
sudo systemctl daemon-reload
sudo systemctl enable --now scout.service
sudo systemctl status scout.service

deploy/scout.service 已配置自动重启、最小权限、只读系统保护和独立数据目录。停止或升级时使用:

sudo systemctl restart scout.service
sudo journalctl -u scout.service -f

Nginx 转发要求

deploy/nginx-native.conf 是 Nginx 参考配置,默认从 /opt/scout/frontend/dist 提供前端文件,并转发到本机 127.0.0.1:8080。部署时请将 server_name _; 按外部网关实际要求调整。

外部网关需要满足以下要求:

  • /api/ 转发到 Scout API。
  • /api/v1/updates/stream 关闭代理缓冲,并将读取超时设置为至少 1 小时。
  • 上传接口允许的请求体大小不小于 SCOUT_MAX_UPLOAD_BYTES。
  • 转发 Host、X-Real-IP、X-Forwarded-For 和 X-Forwarded-Proto。
  • 如果 SCOUT_TRUST_PROXY=true,只允许可信反向代理访问 Scout,不能让公网客户端直接伪造这些请求头。
  • SSL 证书、证书续期、DNS 解析和域名绑定由外部网关或云服务负责。

外部网关终止 HTTPS 后,必须将 X-Forwarded-Proto: https 传给 Scout;否则生产配置中的 SCOUT_FORCE_HTTPS=true 会拒绝请求。这是协议边界检查,不是 Scout 的证书管理功能。

deploy/Caddyfile 是 Caddy 反向代理参考配置,其中的示例主机名需要替换为实际访问地址。它直接代理本机 127.0.0.1:8080,并从 /opt/scout/frontend/dist 提供前端文件。它不会申请或配置 SSL 证书,也不会执行 DNS 解析、域名绑定或服务商 API 操作;如果 SSL 和 DNS 已由其他服务处理,请继续让该服务负责 TLS 终止,并把请求转发到 Scout 的入口。

健康检查、备份和升级

健康检查接口:

GET /healthz

返回 {"status":"ok"} 只代表进程可以响应。生产监控还应检查反向代理、磁盘空间、SMTP 可用性和实际包下载。

备份至少包括整个 SCOUT_DATA_DIR,其中包含用户数据、密码哈希、包元数据和包文件:

SCOUT_DATA_DIR=/var/lib/scout SCOUT_BACKUP_DIR=/var/backups/scout sh deploy/backup.sh

升级建议流程:

  1. 先执行备份并确认备份文件可读取。
  2. 在测试环境执行 go test ./... 和 npm run build。
  3. 编译并替换新的二进制文件和前端静态文件。
  4. 重启服务并检查 /healthz、用户登录、包列表、上传和下载。
  5. 观察日志和磁盘空间后再删除旧版本。
  6. 如升级异常,停止新版本并恢复旧二进制或旧前端文件,再恢复数据备份。

当前会话保存在内存中,服务端重启后已有登录会话会失效,客户端需要重新登录;用户和包数据不会因此丢失。