fix: support pinned HTTP Gitea releases

This commit is contained in:
chenpei 2026-08-12 18:25:08 +08:00
parent d4fb3971e9
commit 7c5f42ca40
5 changed files with 60 additions and 19 deletions

View File

@ -264,7 +264,7 @@ npm run prisma:studio
```powershell
.\scripts\git-incremental-deploy.ps1 -DryRun
.\scripts\git-incremental-deploy.ps1
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
```
部署链路是:源码推送到 Git -> 构建机增量拉取并构建镜像 -> 生产机拉取镜像并替换应用容器。旧的 `deploy-remote-build.ps1` 属于本地历史脚本名,只作为识别应急资料使用,不是仓库交付入口,也不再作为默认推荐方式。

View File

@ -25,7 +25,7 @@
默认部署:
- Git 仓库:`https://git.1080p.hk/WuYongJie/gongxiangp2.git`(证书必须有效,禁止跳过 TLS 校验)
- Git 仓库:`http://git.1080p.hk/WuYongJie/gongxiangp2.git`(现场 Gitea 当前仅提供 HTTP;脚本只允许这一条精确地址)
- 分支:`main`
- 构建机:`192.168.40.206`
- 生产机:`192.168.58.20`
@ -45,7 +45,8 @@
- 构建机本地 Registry 端口 `5000` 可用。
- 已通过环境变量或参数提供构建机密码:`BUILD_SERVER_PASSWORD` 或 `-BuildServerPassword`。
- 已分别固定构建机和生产机的 SSH SHA256 主机指纹;OpenSSH 模式还必须使用预置、只读的 `known_hosts` 文件。
- 私有 Git 仓库认证必须预先配置在维护机和构建机的只读 credential helper 中。脚本拒绝 `GIT_DEPLOY_PASSWORD`、`GIT_AUTH_HEADER`、明文 HTTP 和 URL 内嵌凭据。
- 私有 Git 仓库认证必须预先配置在维护机和构建机的只读、最小权限 credential helper 中。脚本拒绝 `GIT_DEPLOY_PASSWORD`、`GIT_AUTH_HEADER` 和 URL 内嵌凭据。
- 由于 Gitea 使用 HTTP,正式构建或发布必须显式传入维护机已核对的 40 位小写 `-Commit`;脚本不允许把 HTTP 返回的分支最新提交直接当作发布依据。
## 安全边界
@ -53,7 +54,7 @@
- 不在生产机上执行 `npm install` 或 `next build`。
- 不改生产机的 `.env` 和 uploads 目录。
- 不把 Git 密码写入仓库或远端 URL。
- Git HTTPS 证书失效、SSH 指纹不匹配或仓库地址偏离权威地址时,发布必须停止;不得使用 `-k` 或关闭主机校验绕过。
- Gitea HTTP 不提供传输加密或服务器证书身份保证,因此部署账号必须只读、最小权限并定期轮换;仓库地址偏离固定值、未给精确提交 SHA、SSH 指纹不匹配、构建镜像 ID/digest 不一致时,发布必须停止。
- 生产容器启动失败时,默认尝试回滚到旧镜像。
- 清理策略只清理无用镜像/容器,默认保留 Docker build cache。
@ -65,6 +66,12 @@
.\scripts\git-incremental-deploy.ps1 -DryRun
```
正式构建或部署必须显式指定完整提交:
```powershell
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
```
DryRun 只验证:
- 构建机可连接

View File

@ -25,7 +25,7 @@
默认参数:
- Git 仓库:`https://git.1080p.hk/WuYongJie/gongxiangp2.git`(只允许有效 TLS,不得降级到 HTTP)
- Git 仓库:`http://git.1080p.hk/WuYongJie/gongxiangp2.git`(现场 Gitea 仅提供 HTTP;脚本只允许这一条精确地址)
- 分支:`main`
- 构建机:`192.168.40.206`
- 生产机:`192.168.58.20`
@ -49,7 +49,7 @@
- 构建机、生产机的固定 SSH SHA256 主机指纹;OpenSSH 模式还要提供受控 `known_hosts` 文件。
- 维护机和构建机已配置只读 Git credential helper。部署脚本禁止把 Git 密码/Header 写入参数、临时脚本或进程命令行。
不要把 Git 密码写进仓库 URL,也不要把临时部署密码写入文档或提交到仓库。HTTPS 证书过期、主机指纹不匹配或权威仓库不可验证时,必须先修基础设施,不能关闭校验强行发布。
不要把 Git 密码写进仓库 URL,也不要把临时部署密码写入文档或提交到仓库。Gitea 的 HTTP 通道不具备传输加密与证书身份保证,因此只能使用只读、最小权限 credential helper;正式构建/部署必须显式指定维护机已核对的完整 40 位小写提交 SHA,并继续核对 SSH 主机指纹、镜像 ID 与 manifest digest。
### 2.2 部署命令
@ -62,7 +62,7 @@
部署正式环境:
```powershell
.\scripts\git-incremental-deploy.ps1
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
```
影子环境(当前禁止通过通用脚本执行;下面命令会明确失败):
@ -74,13 +74,13 @@
部署指定提交:
```powershell
.\scripts\git-incremental-deploy.ps1 -Commit <commit-sha>
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>'
```
必要时彻底重新构建:
```powershell
.\scripts\git-incremental-deploy.ps1 -NoCache
.\scripts\git-incremental-deploy.ps1 -Commit '<40位小写提交SHA>' -NoCache
```
## 3. 参数说明
@ -88,7 +88,7 @@
- `-Target`:当前只允许 `production`;`shadow` 会在任何 Git/远程操作前 fail-closed。
- `-RepoUrl`:Git 仓库地址,默认使用 `WuYongJie/gongxiangp2`。
- `-Branch`:部署分支,默认 `main`。
- `-Commit`:指定提交;为空时部署目标分支最新提交。
- `-Commit`:正式构建/部署必填,必须是维护机已核对的 40 位小写提交 SHA;仅 `-DryRun` 可不填。
- `-DryRun`:只做连通性与依赖预检,不构建、不推镜像、不重启容器。
- `-NoCache`:Docker 构建不使用缓存。
- `-KeepOldImages`:跳过自动 Docker 镜像清理。

View File

@ -403,10 +403,14 @@ describe('production video transcode drain gate', () => {
expect(dockerfile).toContain('chmod 0444 /app/DEPLOYMENT_COMMIT')
})
test('Git 与 SSH 发布通道禁止明文凭据、盲信主机和宽泛临时脚本权限', () => {
expect(script).toContain("[string]$RepoUrl = 'https://git.1080p.hk/WuYongJie/gongxiangp2.git'")
test('HTTP Gitea 只允许固定仓库和精确提交,SSH 通道禁止明文凭据与盲信主机', () => {
expect(script).toContain("[string]$RepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git'")
expect(script).toContain('Inline Git credentials are forbidden')
expect(script).toContain("$trustedRepoUrl = 'https://git.1080p.hk/WuYongJie/gongxiangp2.git'")
expect(script).toContain("$trustedRepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git'")
expect(script).toContain("if (-not $DryRun -and $Commit -cnotmatch '^[0-9a-f]{40}$')")
expect(script).toContain('HTTP Git transport requires -Commit with the exact 40-character lowercase SHA')
expect(script).toContain("[ \"$REPO_URL\" = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git' ]")
expect(script).not.toContain('https://git.1080p.hk')
expect(script).toContain('StrictHostKeyChecking=yes')
expect(script).toContain('-hostkey $BuildServerHostKey')
expect(script).toContain('Production host key fingerprint does not match the pinned value')
@ -490,6 +494,32 @@ describe('production video transcode drain gate', () => {
35_000,
)
;(process.platform === 'win32' ? test : test.skip)(
'HTTP Gitea 正式发布未提供完整提交 SHA 时在任何远程操作前 fail-closed',
() => {
const deployScript = path.join(process.cwd(), 'scripts', 'git-incremental-deploy.ps1')
const powershell = path.join(
process.env.SystemRoot || 'C:\\Windows',
'System32',
'WindowsPowerShell',
'v1.0',
'powershell.exe',
)
const deploy = spawnSync(powershell, [
'-NoProfile',
'-ExecutionPolicy', 'Bypass',
'-File', deployScript,
], { cwd: process.cwd(), encoding: 'utf8', timeout: 30_000 })
const output = `${deploy.stdout}\n${deploy.stderr}`
expect(deploy.status).not.toBe(0)
expect(output).toContain('HTTP Git transport requires -Commit with the exact 40-character lowercase SHA')
expect(output).not.toContain('Resolving and scanning the exact Git tree')
expect(output).not.toContain('Connecting to build server')
},
35_000,
)
test('旧镜像不能证明协议能力时 fail-closed,候选容器不会启动', () => {
expect(script).toContain("grep -Fqx -- 'VIDEO_TRANSCODE_DRAIN_PROTOCOL_V1'")
expect(script).toContain('-e "VIDEO_TRANSCODE_INSTANCE_ID=$transcode_instance_id"')

View File

@ -17,7 +17,7 @@ param(
[ValidateSet('production', 'shadow')]
[string]$Target = 'production',
[string]$RepoUrl = 'https://git.1080p.hk/WuYongJie/gongxiangp2.git',
[string]$RepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git',
[string]$Branch = 'main',
[string]$Commit = '',
@ -71,6 +71,10 @@ if (-not $SkipDeploy -and ($SkipBuild -or $SkipPush)) {
throw 'Production deployment requires a freshly built and pushed exact-SHA image; -SkipBuild and -SkipPush are only allowed together with -SkipDeploy.'
}
if (-not $DryRun -and $Commit -cnotmatch '^[0-9a-f]{40}$') {
throw 'HTTP Git transport requires -Commit with the exact 40-character lowercase SHA selected on the trusted workstation.'
}
if (-not $DryRun -and -not $SkipDeploy) {
if ($MigrationAuditFile -cnotmatch '^/root/gongxiangp2-migration-audit/[A-Za-z0-9._-]+/applied-[0-9]{8}T[0-9]{6}Z\.json$') {
throw 'Production deployment requires the immutable apply audit file under /root/gongxiangp2-migration-audit/<batch>/applied-<UTC>.json.'
@ -93,13 +97,13 @@ if (
throw 'Inline Git credentials are forbidden. Configure a read-only credential helper on the workstation and build server instead.'
}
$trustedRepoUrl = 'https://git.1080p.hk/WuYongJie/gongxiangp2.git'
$trustedRepoUrl = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git'
$localDryRunRepository = $false
if ($DryRun -and [System.IO.Path]::IsPathRooted($RepoUrl)) {
$localDryRunRepository = Test-Path -LiteralPath $RepoUrl -PathType Container
}
if (-not $localDryRunRepository -and $RepoUrl -cne $trustedRepoUrl) {
throw "Production Git repository must be the pinned HTTPS repository $trustedRepoUrl. Plain HTTP, inline credentials, and arbitrary repositories are forbidden."
throw "Production Git repository must be the exact pinned HTTP repository $trustedRepoUrl. URL credentials and arbitrary repositories are forbidden."
}
# Production evidence from 2026-08-02 identifies the dedicated ZFS dataset as
@ -254,8 +258,8 @@ function Invoke-LegacyStorageBoundaryPreflight {
$targetCommit = $branchCommit
if (-not [string]::IsNullOrWhiteSpace($RequestedCommit)) {
if ($RequestedCommit -notmatch '^[0-9a-fA-F]{7,40}$') {
throw 'Commit override must be a 7-40 character hexadecimal Git commit ID.'
if ($RequestedCommit -cnotmatch '^[0-9a-f]{40}$') {
throw 'Commit override must be the exact 40-character lowercase Git commit SHA.'
}
$targetCommit = Invoke-PreflightGit -GitExecutable $gitCommand.Source -CaptureOutput -Arguments @(
"--git-dir=$bareRepository", 'rev-parse', '--verify', "${RequestedCommit}^{commit}"
@ -697,7 +701,7 @@ require_cmd curl
require_cmd node
require_cmd ssh
require_cmd ssh-keygen
[ "$REPO_URL" = 'https://git.1080p.hk/WuYongJie/gongxiangp2.git' ] || fail "Build server received an untrusted Git repository URL"
[ "$REPO_URL" = 'http://git.1080p.hk/WuYongJie/gongxiangp2.git' ] || fail "Build server received an untrusted Git repository URL"
[ "$REMOTE_BUILD_DIR" = '/root/youyidian-3d-git-build' ] || fail "Build directory is not the pinned dedicated worktree"
test -f "$PROD_KNOWN_HOSTS_FILE" || fail "Pinned production known_hosts file is missing on build server"
test ! -L "$PROD_KNOWN_HOSTS_FILE" || fail "Pinned production known_hosts file must not be a symlink"