Path A: strip admin authcode generator from public boot repo

Remove gen-authcode.js, admin-authcode-gui.ps1, admin-authcode.ico (authcode-generation source + admin GUI tooling) from the public boot repo. Authcode minting is now server-side only (issuer container /app/admin/gen-authcode.js). build.ps1 -Admin packaging path removed accordingly; Setup.exe path unchanged. HEAD-only removal (git history retained; stripped content carries no plaintext secret, authorization protected by server-side pepper).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
bookworm-admin 2026-06-30 00:57:28 +08:00
parent 0e5a0d9921
commit 5b2dcecaa8
4 changed files with 7 additions and 705 deletions

View File

@ -1,482 +0,0 @@
<#
.SYNOPSIS
Bookworm 授权码生成器 (管理员 GUI 工具)
.DESCRIPTION
内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码
打包命令: build.ps1 -Admin
#>
# 全局错误捕获 (PS2EXE -NoOutput 会吞掉所有错误, 这里确保弹窗显示)
try {
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# ─── 路径 ─────────────────────────────────────────────
$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot }
elseif ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName -match '\.exe$') {
Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)
} elseif ($MyInvocation.MyCommand.Path) {
Split-Path -Parent $MyInvocation.MyCommand.Path
} else { $PWD.Path }
# gen-authcode.js / secrets.txt 查找: 当前目录 → 父目录 (dist/ 内运行时)
$GenScript = Join-Path $ScriptDir "gen-authcode.js"
if (-not (Test-Path $GenScript)) {
$parentDir = Split-Path $ScriptDir -Parent
$GenScript = Join-Path $parentDir "gen-authcode.js"
if (Test-Path $GenScript) { $ScriptDir = $parentDir } # 切到父目录
}
$SecretsTxt = Join-Path $ScriptDir "secrets.txt"
# ─── 品牌色 ───────────────────────────────────────────
$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242)
$brandDark = [System.Drawing.Color]::FromArgb(24, 25, 38)
$brandLight = [System.Drawing.Color]::FromArgb(245, 246, 250)
$cardBg = [System.Drawing.Color]::FromArgb(248, 249, 253)
$successGreen = [System.Drawing.Color]::FromArgb(35, 134, 54)
$warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29)
$textPrimary = [System.Drawing.Color]::FromArgb(36, 41, 47)
$textSecondary = [System.Drawing.Color]::FromArgb(110, 119, 129)
$inputBorder = [System.Drawing.Color]::FromArgb(208, 215, 222)
# ─── 主窗口 ───────────────────────────────────────────
$form = New-Object System.Windows.Forms.Form
$form.Text = "Bookworm 授权码生成器"
$form.ClientSize = New-Object System.Drawing.Size(540, 594)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedSingle"
$form.MaximizeBox = $false
$form.BackColor = [System.Drawing.Color]::White
$form.Font = New-Object System.Drawing.Font("Segoe UI", 9)
# ─── 标题栏 (深色渐变 + 副标题) ──────────────────────
$header = New-Object System.Windows.Forms.Panel
$header.Dock = "Top"
$header.Size = New-Object System.Drawing.Size(540, 70)
$header.BackColor = $brandDark
$form.Controls.Add($header)
$titleLabel = New-Object System.Windows.Forms.Label
$titleLabel.Location = New-Object System.Drawing.Point(24, 12)
$titleLabel.Size = New-Object System.Drawing.Size(500, 28)
$titleLabel.Text = "Bookworm 授权码生成器"
$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 15, [System.Drawing.FontStyle]::Bold)
$titleLabel.ForeColor = [System.Drawing.Color]::White
$header.Controls.Add($titleLabel)
$subtitleLabel = New-Object System.Windows.Forms.Label
$subtitleLabel.Location = New-Object System.Drawing.Point(24, 42)
$subtitleLabel.Size = New-Object System.Drawing.Size(500, 18)
$subtitleLabel.Text = "管理员专用 · 为每位用户生成独立授权码与加密凭证"
$subtitleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$subtitleLabel.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200)
$header.Controls.Add($subtitleLabel)
# ═══ 输入卡片 (Panel 模拟卡片) ═══════════════════════
$inputCard = New-Object System.Windows.Forms.Panel
$inputCard.Location = New-Object System.Drawing.Point(20, 84)
$inputCard.Size = New-Object System.Drawing.Size(500, 186)
$inputCard.BackColor = $cardBg
$form.Controls.Add($inputCard)
# ── 卡片标题
$inputTitle = New-Object System.Windows.Forms.Label
$inputTitle.Location = New-Object System.Drawing.Point(16, 10)
$inputTitle.Size = New-Object System.Drawing.Size(200, 20)
$inputTitle.Text = "用户信息"
$inputTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
$inputTitle.ForeColor = $brandBlue
$inputCard.Controls.Add($inputTitle)
# ── 用户名
$lblUser = New-Object System.Windows.Forms.Label
$lblUser.Location = New-Object System.Drawing.Point(16, 40)
$lblUser.Size = New-Object System.Drawing.Size(90, 22)
$lblUser.Text = "用户标识"
$lblUser.ForeColor = $textPrimary
$lblUser.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblUser)
$txtUser = New-Object System.Windows.Forms.TextBox
$txtUser.Location = New-Object System.Drawing.Point(114, 38)
$txtUser.Size = New-Object System.Drawing.Size(370, 26)
$txtUser.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$txtUser.BorderStyle = "FixedSingle"
$inputCard.Controls.Add($txtUser)
# ── Relay Key
$lblKey = New-Object System.Windows.Forms.Label
$lblKey.Location = New-Object System.Drawing.Point(16, 74)
$lblKey.Size = New-Object System.Drawing.Size(90, 22)
$lblKey.Text = "Relay Key"
$lblKey.ForeColor = $textPrimary
$lblKey.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblKey)
$txtKey = New-Object System.Windows.Forms.TextBox
$txtKey.Location = New-Object System.Drawing.Point(114, 72)
$txtKey.Size = New-Object System.Drawing.Size(370, 26)
$txtKey.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$txtKey.PasswordChar = '*'
$txtKey.BorderStyle = "FixedSingle"
$inputCard.Controls.Add($txtKey)
$chkShowKey = New-Object System.Windows.Forms.CheckBox
$chkShowKey.Location = New-Object System.Drawing.Point(114, 102)
$chkShowKey.Size = New-Object System.Drawing.Size(100, 20)
$chkShowKey.Text = "显示 Key"
$chkShowKey.ForeColor = $textSecondary
$chkShowKey.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } })
$inputCard.Controls.Add($chkShowKey)
# ── 有效期
$lblDays = New-Object System.Windows.Forms.Label
$lblDays.Location = New-Object System.Drawing.Point(16, 132)
$lblDays.Size = New-Object System.Drawing.Size(90, 22)
$lblDays.Text = "有效期"
$lblDays.ForeColor = $textPrimary
$lblDays.TextAlign = [System.Drawing.ContentAlignment]::MiddleRight
$inputCard.Controls.Add($lblDays)
$cmbDays = New-Object System.Windows.Forms.ComboBox
$cmbDays.Location = New-Object System.Drawing.Point(114, 130)
$cmbDays.Size = New-Object System.Drawing.Size(80, 26)
$cmbDays.DropDownStyle = "DropDownList"
$cmbDays.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$null = $cmbDays.Items.AddRange(@(7, 14, 30, 60, 90, 180, 365))
$cmbDays.SelectedIndex = 2
$inputCard.Controls.Add($cmbDays)
$lblDaysUnit = New-Object System.Windows.Forms.Label
$lblDaysUnit.Location = New-Object System.Drawing.Point(198, 132)
$lblDaysUnit.Size = New-Object System.Drawing.Size(30, 22)
$lblDaysUnit.Text = ""
$lblDaysUnit.ForeColor = $textSecondary
$inputCard.Controls.Add($lblDaysUnit)
$lblDaysHint = New-Object System.Windows.Forms.Label
$lblDaysHint.Location = New-Object System.Drawing.Point(240, 132)
$lblDaysHint.Size = New-Object System.Drawing.Size(240, 22)
$lblDaysHint.Text = ""
$lblDaysHint.ForeColor = $textSecondary
$lblDaysHint.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$inputCard.Controls.Add($lblDaysHint)
$cmbDays.Add_SelectedIndexChanged({
$d = [int]$cmbDays.SelectedItem
$lblDaysHint.Text = "-> 到期: " + (Get-Date).AddDays($d).ToString("yyyy-MM-dd")
})
$cmbDays.SelectedIndex = 2
# ── 环境状态行 ──
$nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue)
$secretsOK = Test-Path $SecretsTxt
$lblStatus1 = New-Object System.Windows.Forms.Label
$lblStatus1.Location = New-Object System.Drawing.Point(16, 160)
$lblStatus1.Size = New-Object System.Drawing.Size(230, 18)
$lblStatus1.Font = New-Object System.Drawing.Font("Segoe UI", 8)
if ($secretsOK) {
$lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count
$lblStatus1.Text = "secrets.txt: $lineCount 个凭证"
$lblStatus1.ForeColor = $successGreen
} else {
$lblStatus1.Text = "secrets.txt: 未找到"
$lblStatus1.ForeColor = [System.Drawing.Color]::Red
}
$inputCard.Controls.Add($lblStatus1)
$lblStatus2 = New-Object System.Windows.Forms.Label
$lblStatus2.Location = New-Object System.Drawing.Point(250, 160)
$lblStatus2.Size = New-Object System.Drawing.Size(230, 18)
$lblStatus2.Font = New-Object System.Drawing.Font("Segoe UI", 8)
if ($nodeOK) {
$nodeVer = try { (& node --version 2>$null) } catch { "" }
$lblStatus2.Text = "Node.js: $nodeVer"
$lblStatus2.ForeColor = $successGreen
} else {
$lblStatus2.Text = "Node.js: 未安装"
$lblStatus2.ForeColor = [System.Drawing.Color]::Red
}
$inputCard.Controls.Add($lblStatus2)
# ═══ 操作按钮 ═════════════════════════════════════════
$btnGenerate = New-Object System.Windows.Forms.Button
$btnGenerate.Location = New-Object System.Drawing.Point(138, 282)
$btnGenerate.Size = New-Object System.Drawing.Size(180, 42)
$btnGenerate.Text = " 生成授权码"
$btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
$btnGenerate.FlatStyle = "Flat"
$btnGenerate.BackColor = $brandBlue
$btnGenerate.ForeColor = [System.Drawing.Color]::White
$btnGenerate.FlatAppearance.BorderSize = 0
$btnGenerate.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnGenerate.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$form.Controls.Add($btnGenerate)
$btnClear = New-Object System.Windows.Forms.Button
$btnClear.Location = New-Object System.Drawing.Point(330, 282)
$btnClear.Size = New-Object System.Drawing.Size(72, 42)
$btnClear.Text = "清空"
$btnClear.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$btnClear.FlatStyle = "Flat"
$btnClear.BackColor = [System.Drawing.Color]::White
$btnClear.ForeColor = $textSecondary
$btnClear.FlatAppearance.BorderColor = $inputBorder
$btnClear.FlatAppearance.BorderSize = 1
$form.Controls.Add($btnClear)
# ═══ 结果卡片 ═════════════════════════════════════════
$resultCard = New-Object System.Windows.Forms.Panel
$resultCard.Location = New-Object System.Drawing.Point(20, 338)
$resultCard.Size = New-Object System.Drawing.Size(500, 220)
$resultCard.BackColor = $cardBg
$form.Controls.Add($resultCard)
$lblResultTitle = New-Object System.Windows.Forms.Label
$lblResultTitle.Location = New-Object System.Drawing.Point(16, 10)
$lblResultTitle.Size = New-Object System.Drawing.Size(200, 20)
$lblResultTitle.Text = "生成结果"
$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
$lblResultTitle.ForeColor = $brandBlue
$resultCard.Controls.Add($lblResultTitle)
$txtAuthCode = New-Object System.Windows.Forms.TextBox
$txtAuthCode.Location = New-Object System.Drawing.Point(16, 38)
$txtAuthCode.Size = New-Object System.Drawing.Size(380, 34)
$txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold)
$txtAuthCode.ReadOnly = $true
$txtAuthCode.BackColor = [System.Drawing.Color]::White
$txtAuthCode.ForeColor = $brandDark
$txtAuthCode.BorderStyle = "FixedSingle"
$txtAuthCode.Text = ""
$resultCard.Controls.Add($txtAuthCode)
$btnCopy = New-Object System.Windows.Forms.Button
$btnCopy.Location = New-Object System.Drawing.Point(404, 38)
$btnCopy.Size = New-Object System.Drawing.Size(80, 34)
$btnCopy.Text = "复制"
$btnCopy.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$btnCopy.FlatStyle = "Flat"
$btnCopy.BackColor = $brandBlue
$btnCopy.ForeColor = [System.Drawing.Color]::White
$btnCopy.FlatAppearance.BorderSize = 0
$btnCopy.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnCopy.Enabled = $false
$resultCard.Controls.Add($btnCopy)
$lblDetails = New-Object System.Windows.Forms.Label
$lblDetails.Location = New-Object System.Drawing.Point(16, 82)
$lblDetails.Size = New-Object System.Drawing.Size(468, 66)
$lblDetails.Text = "点击「生成授权码」后,结果将显示在此处。"
$lblDetails.ForeColor = $textSecondary
$lblDetails.Font = New-Object System.Drawing.Font("Segoe UI", 8.5)
$resultCard.Controls.Add($lblDetails)
# 推送到 Gitea 按钮
$btnPush = New-Object System.Windows.Forms.Button
$btnPush.Location = New-Object System.Drawing.Point(16, 156)
$btnPush.Size = New-Object System.Drawing.Size(468, 38)
$btnPush.Text = "推送到 Gitea (git add + commit + push)"
$btnPush.Font = New-Object System.Drawing.Font("Segoe UI", 10)
$btnPush.FlatStyle = "Flat"
$btnPush.BackColor = $successGreen
$btnPush.ForeColor = [System.Drawing.Color]::White
$btnPush.FlatAppearance.BorderSize = 0
$btnPush.Cursor = [System.Windows.Forms.Cursors]::Hand
$btnPush.Enabled = $false
$resultCard.Controls.Add($btnPush)
# ═══ 状态栏 ═══════════════════════════════════════════
$statusBar = New-Object System.Windows.Forms.Label
$statusBar.Location = New-Object System.Drawing.Point(0, 568)
$statusBar.Size = New-Object System.Drawing.Size(540, 26)
$statusBar.BackColor = $brandDark
$statusBar.ForeColor = [System.Drawing.Color]::FromArgb(160, 170, 200)
$statusBar.Text = " 就绪 | $ScriptDir"
$statusBar.Font = New-Object System.Drawing.Font("Segoe UI", 8)
$statusBar.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft
$form.Controls.Add($statusBar)
# ─── 事件处理 ─────────────────────────────────────────
$btnCopy.Add_Click({
if ($txtAuthCode.Text) {
[System.Windows.Forms.Clipboard]::SetText($txtAuthCode.Text)
$statusBar.Text = " 已复制到剪贴板"
$statusBar.ForeColor = $successGreen
}
})
$btnPush.Add_Click({
$btnPush.Enabled = $false
$btnPush.Text = "推送中..."
$statusBar.Text = " git add + commit + push ..."
$statusBar.ForeColor = $warningOrange
[System.Windows.Forms.Application]::DoEvents()
try {
$gitExe = (Get-Command git -ErrorAction Stop).Source
$userName = if ($global:lastGenUser) { $global:lastGenUser } else { "user" }
# git add secrets-*.enc
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $gitExe
$psi.Arguments = "add secrets-*.enc"
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$psi.CreateNoWindow = $true
$psi.WorkingDirectory = $ScriptDir
$p = [System.Diagnostics.Process]::Start($psi)
$p.WaitForExit(15000)
# git commit
$psi.Arguments = "commit -m `"add user $userName`""
$p = [System.Diagnostics.Process]::Start($psi)
$p.StandardOutput.ReadToEnd() | Out-Null
$p.WaitForExit(15000)
# git push
$statusBar.Text = " git push 中 (可能需要几秒)..."
[System.Windows.Forms.Application]::DoEvents()
$psi.Arguments = "push"
$p = [System.Diagnostics.Process]::Start($psi)
$pushOut = $p.StandardError.ReadToEnd() # git push 输出在 stderr
$p.WaitForExit(30000)
if ($p.ExitCode -eq 0) {
$btnPush.Text = "已推送"
$btnPush.BackColor = [System.Drawing.Color]::FromArgb(200, 220, 200)
$btnPush.ForeColor = $successGreen
$statusBar.Text = " 推送成功 — 现在可以把授权码发给 $userName"
$statusBar.ForeColor = $successGreen
} else {
throw "git push 失败: $pushOut"
}
} catch {
$btnPush.Text = "推送失败 (点击重试)"
$btnPush.BackColor = [System.Drawing.Color]::FromArgb(220, 200, 200)
$btnPush.ForeColor = [System.Drawing.Color]::Red
$btnPush.Enabled = $true
$statusBar.Text = " 推送失败: $_"
$statusBar.ForeColor = [System.Drawing.Color]::Red
[System.Windows.Forms.MessageBox]::Show("推送失败:`n$_`n`n请检查 Git 配置和网络。", "Git 错误", "OK", "Error")
}
})
$btnClear.Add_Click({
$txtUser.Text = ""
$txtKey.Text = ""
$txtAuthCode.Text = ""
$lblDetails.Text = ""
$btnCopy.Enabled = $false
$statusBar.Text = " 已清空"
$statusBar.ForeColor = [System.Drawing.Color]::Gray
})
$btnGenerate.Add_Click({
# 校验
$user = $txtUser.Text.Trim()
$key = $txtKey.Text.Trim()
$days = $cmbDays.SelectedItem.ToString()
if (-not $user) {
[System.Windows.Forms.MessageBox]::Show("请输入用户标识", "缺少用户名", "OK", "Warning")
$txtUser.Focus(); return
}
if (-not $key) {
[System.Windows.Forms.MessageBox]::Show("请输入 Relay Sub-Key`n(从中转站后台为用户创建)", "缺少 Sub-Key", "OK", "Warning")
$txtKey.Focus(); return
}
if (-not (Test-Path $SecretsTxt)) {
[System.Windows.Forms.MessageBox]::Show("secrets.txt 不存在:`n$SecretsTxt`n`n请先创建 secrets.txt (每行 KEY=VALUE)", "缺少凭证文件", "OK", "Error")
return
}
if (-not $nodeOK) {
[System.Windows.Forms.MessageBox]::Show("Node.js 未安装。`n请先安装: https://nodejs.org", "缺少 Node.js", "OK", "Error")
return
}
$statusBar.Text = " 生成中..."
$statusBar.ForeColor = $warningOrange
$btnGenerate.Enabled = $false
[System.Windows.Forms.Application]::DoEvents()
try {
# 用 Process + UTF8 编码读取 (PS2EXE 默认 GBK 会乱码)
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = (Get-Command node -ErrorAction Stop).Source
$psi.Arguments = "`"$GenScript`" $days -k `"$key`" -u `"$user`""
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$psi.CreateNoWindow = $true
$psi.WorkingDirectory = $ScriptDir
$proc = [System.Diagnostics.Process]::Start($psi)
$stdout = $proc.StandardOutput.ReadToEnd()
$stderr = $proc.StandardError.ReadToEnd()
$proc.WaitForExit()
if ($proc.ExitCode -ne 0) {
throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr"
}
# 解析输出 (编码无关: PS2EXE 下中文可能乱码, 只匹配 ASCII 格式)
$authCode = if ($stdout -match '(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" }
$fileId = if ($stdout -match '(secrets-[a-f0-9]{8}\.enc)') { $Matches[1] } else { "" }
$expiry = if ($stdout -match '(\d{4}-\d{2}-\d{2})') { $Matches[1] } else { "" }
if ($authCode) {
$txtAuthCode.Text = $authCode
$lblDetails.Text = "用户: $user`n加密文件: $fileId`n有效期: $days 天 (至 $expiry)`n路径: $ScriptDir\$fileId"
$btnCopy.Enabled = $true
$btnPush.Enabled = $true
$global:lastGenUser = $user
$global:lastGenFile = $fileId
$statusBar.Text = " 生成成功 — 点击「推送到 Gitea」完成部署"
$statusBar.ForeColor = $successGreen
# 追加历史记录 (仅本机, 已在 .gitignore)
$historyFile = Join-Path $ScriptDir "authcode-history.log"
$logLine = "$(Get-Date -Format 'yyyy-MM-dd HH:mm') $($user.PadRight(12)) $fileId $($days)天 至$expiry $authCode"
try { Add-Content -Path $historyFile -Value $logLine -Encoding utf8 } catch {}
} else {
throw "无法解析授权码输出:`n$stdout"
}
} catch {
$txtAuthCode.Text = ""
$lblDetails.Text = "错误: $_"
$btnCopy.Enabled = $false
$statusBar.Text = " 生成失败"
$statusBar.ForeColor = [System.Drawing.Color]::Red
[System.Windows.Forms.MessageBox]::Show("生成失败:`n$_", "错误", "OK", "Error")
} finally {
$btnGenerate.Enabled = $true
}
})
# ─── 启动 ─────────────────────────────────────────────
$form.Add_Shown({ $txtUser.Focus() })
[System.Windows.Forms.Application]::Run($form)
} catch {
# 全局错误弹窗 (PS2EXE 下唯一的错误可见方式)
$errMsg = "Bookworm AuthGen 启动失败:`n`n" +
"错误: $($_.Exception.Message)`n" +
"行号: $($_.InvocationInfo.ScriptLineNumber)`n" +
"代码: $($_.InvocationInfo.Line.Trim())`n`n" +
"ScriptDir: $ScriptDir`n" +
"GenScript: $GenScript`n" +
"SecretsTxt: $SecretsTxt"
try {
[System.Windows.Forms.MessageBox]::Show($errMsg, "AuthGen 错误", "OK", "Error")
} catch {
# 如果连 MsgBox 都失败 (WinForms 未加载), 写文件
$errMsg | Out-File "$env:TEMP\bookworm-authgen-error.txt" -Encoding utf8
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -3,25 +3,23 @@
Bookworm Portable 打包工具 (管理员使用)
.DESCRIPTION
auto-setup.ps1 打包为 Bookworm-Setup.exe (PS2EXE)
gen-authcode.js 打包为 gen-authcode.exe (pkg)
输出到 dist\ 目录
.USAGE
.\build.ps1 # 打包两个
.\build.ps1 -Setup # 只打包用户安装器
.\build.ps1 -Admin # 只打包管理员工具
.\build.ps1 # 打包用户安装器 Bookworm-Setup.exe
.NOTES
[Path-A 2026-06-30] admin 授权码工具 (gen-authcode.js / admin-authcode-gui.ps1) 已从公开 boot 仓剥离;
授权码生成改由服务器端 issuer 容器 /app/admin/gen-authcode.js 负责, -Admin 打包路径已移除
#>
param(
[switch]$Setup, # 只打 Bookworm-Setup.exe
[switch]$Admin # 只打 gen-authcode.exe
[switch]$Setup # 保留参数兼容旧调用; 当前仅产出 Bookworm-Setup.exe
)
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$DistDir = Join-Path $ScriptDir "dist"
# 默认两个都打
$buildSetup = $Setup -or (-not $Setup -and -not $Admin)
$buildAdmin = $Admin -or (-not $Setup -and -not $Admin)
# 仅产出用户安装器 (admin 授权码工具已剥离, 见 .NOTES)
$buildSetup = $true
function Write-Step($msg) {
Write-Host ""
@ -148,53 +146,6 @@ if ($buildSetup) {
}
}
# ════════════════════════════════════════════════════════
# 2. Bookworm-AuthGen.exe (admin-authcode-gui.ps1 → PS2EXE)
# ════════════════════════════════════════════════════════
if ($buildAdmin) {
Write-Step "打包 Bookworm-AuthGen.exe (PS2EXE GUI)"
$inputPs1 = Join-Path $ScriptDir "admin-authcode-gui.ps1"
$outputExe = Join-Path $DistDir "Bookworm-AuthGen.exe"
if (-not (Test-Path $inputPs1)) {
Write-Fail "找不到 admin-authcode-gui.ps1"
exit 1
}
Write-Host " 输入: $inputPs1" -ForegroundColor Gray
Write-Host " 输出: $outputExe" -ForegroundColor Gray
# 优先用书虫学者图标, 回退到 B 圆
$adminIcon = Join-Path $ScriptDir "admin-authcode.ico"
if (-not (Test-Path $adminIcon)) { $adminIcon = Join-Path $ScriptDir "bookworm-desktop.ico" }
$ps2exeArgs = @{
InputFile = $inputPs1
OutputFile = $outputExe
Title = "Bookworm AuthCode Generator"
Description = "Bookworm 授权码生成器 (管理员工具)"
Company = "Bookworm"
Version = "1.5.1.0"
NoConsole = $true
NoOutput = $true
NoError = $true
}
if (Test-Path $adminIcon) {
$ps2exeArgs.IconFile = $adminIcon
Write-Host " 图标: $adminIcon" -ForegroundColor Gray
}
Invoke-ps2exe @ps2exeArgs
if (Test-Path $outputExe) {
$sizeKB = [math]::Round((Get-Item $outputExe).Length / 1KB)
Write-OK "Bookworm-AuthGen.exe 打包完成 (${sizeKB} KB)"
} else {
Write-Fail "Bookworm-AuthGen.exe 打包失败"
exit 1
}
}
# ════════════════════════════════════════════════════════
# 完成
# ════════════════════════════════════════════════════════
@ -226,5 +177,4 @@ Get-ChildItem $DistDir | ForEach-Object {
Write-Host ""
Write-Host " 分发说明:" -ForegroundColor Gray
Write-Host " Bookworm-Setup.exe → 用户安装器 (公开下载)" -ForegroundColor Gray
Write-Host " Bookworm-AuthGen.exe → 管理员授权码生成器 (勿对外分发)" -ForegroundColor Gray
Write-Host ""

View File

@ -1,166 +0,0 @@
#!/usr/bin/env node
'use strict';
/**
* Bookworm 授权码生成工具 (管理员使用)
*
* 用法:
* node gen-authcode.js <days> [选项]
*
* 选项:
* --relay-key, -k <key> 中转站限额子 Key (替换 ANTHROPIC_API_KEY)
* --user, -u <name> 用户标识 (仅用于显示, 不影响加密)
* [secrets.txt路径] 明文凭证文件 (默认: ./secrets.txt)
*
* 示例:
* node gen-authcode.js 30 # 共享 Key (单用户)
* node gen-authcode.js 30 --relay-key sk-relay-xxx # 独立限额 Key
* node gen-authcode.js 90 -k sk-relay-xxx -u alice # 指定用户名
*
* 原理:
* 1. 生成随机 24位Hex Token (96bit )
* 2. Token 前8位 = 文件 ID 输出 secrets-XXXXXXXX.enc (多用户模式)
* --relay-key 输出 secrets.enc (单用户/共享模式)
* 3. 授权码 BW-YYYYMMDD-TOKEN (发给用户)
* 4. 安全说明: Token = 解密密钥, YYYYMMDD = 客户端到期校验
*/
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ALGO = 'aes-256-cbc';
const ITERATIONS = 600000;
const DIGEST = 'sha256';
const SALT_LEN = 16;
const KEY_LEN = 32;
const IV_LEN = 16;
function deriveKey(password, salt) {
return crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_LEN + IV_LEN, DIGEST);
}
function encrypt(plaintext, password) {
const salt = crypto.randomBytes(SALT_LEN);
const derived = deriveKey(password, salt);
const key = derived.slice(0, KEY_LEN);
const iv = derived.slice(KEY_LEN, KEY_LEN + IV_LEN);
const cipher = crypto.createCipheriv(ALGO, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
return Buffer.concat([Buffer.from('BWENC1'), salt, encrypted]);
}
// ─── 解析 CLI 参数 ───────────────────────────────────────
const rawArgs = process.argv.slice(2);
const DAYS = parseInt(rawArgs[0]);
if (!DAYS || DAYS < 1 || DAYS > 3650) {
console.error('用法: node gen-authcode.js <有效天数> [选项]');
console.error('选项:');
console.error(' --relay-key, -k <key> 中转站限额子 Key');
console.error(' --user, -u <name> 用户标识 (仅显示)');
console.error(' [secrets.txt路径] 默认: ./secrets.txt');
console.error('');
console.error('示例:');
console.error(' node gen-authcode.js 30 # 共享模式');
console.error(' node gen-authcode.js 30 -k sk-relay-xxx -u alice # 独立限额');
process.exit(1);
}
let relayKey = null;
let userName = null;
let secretsTxtArg = null;
for (let i = 1; i < rawArgs.length; i++) {
const a = rawArgs[i];
if (a === '--relay-key' || a === '-k') { relayKey = rawArgs[++i]; }
else if (a === '--user' || a === '-u') { userName = rawArgs[++i]; }
else if (!a.startsWith('-')) { secretsTxtArg = a; }
}
// pkg 打包后 __filename 指向虚拟快照路径,需用 process.execPath 获取真实目录
const SCRIPT_DIR = path.dirname(
typeof process.pkg !== 'undefined' ? process.execPath : path.resolve(__filename)
);
const SECRETS_TXT = secretsTxtArg || path.join(SCRIPT_DIR, 'secrets.txt');
if (!fs.existsSync(SECRETS_TXT)) {
console.error(`[错误] 找不到 secrets.txt: ${SECRETS_TXT}`);
console.error('请先创建 secrets.txt, 每行格式: KEY=VALUE');
process.exit(1);
}
if (relayKey && !/^[A-Za-z0-9\-_.]+$/.test(relayKey)) {
console.error('[错误] --relay-key 格式不合法');
process.exit(1);
}
// ─── 生成到期日 ──────────────────────────────────────────
const expiry = new Date();
expiry.setDate(expiry.getDate() + DAYS);
const pad = n => String(n).padStart(2, '0');
const expiryStr = `${expiry.getFullYear()}${pad(expiry.getMonth()+1)}${pad(expiry.getDate())}`;
const expiryDisplay = `${expiryStr.slice(0,4)}-${expiryStr.slice(4,6)}-${expiryStr.slice(6,8)}`;
// ─── 生成随机 Token ──────────────────────────────────────
const token = crypto.randomBytes(12).toString('hex'); // 小写 24位
const authCode = `BW-${expiryStr}-${token.toUpperCase()}`;
const fileId = token.slice(0, 8); // 前8位作为文件 ID
// ─── 构建待加密内容 ──────────────────────────────────────
let secretsPlain = fs.readFileSync(SECRETS_TXT, 'utf8').trim();
const multiUser = !!relayKey;
if (multiUser) {
// 用中转站 relay key 替换 ANTHROPIC_API_KEY
if (/^ANTHROPIC_API_KEY=/m.test(secretsPlain)) {
secretsPlain = secretsPlain.replace(
/^ANTHROPIC_API_KEY=.*/m,
`ANTHROPIC_API_KEY=${relayKey}`
);
} else {
secretsPlain = `ANTHROPIC_API_KEY=${relayKey}\n${secretsPlain}`;
}
}
// ─── 加密并写出 ──────────────────────────────────────────
const encBuffer = encrypt(secretsPlain, token);
let outFileName, outFilePath;
if (multiUser) {
outFileName = `secrets-${fileId}.enc`;
} else {
outFileName = 'secrets.enc';
}
outFilePath = path.join(SCRIPT_DIR, outFileName);
fs.writeFileSync(outFilePath, encBuffer);
// ─── 输出 ────────────────────────────────────────────────
const modeLabel = multiUser
? `多用户独立 Key (${userName || '未命名'})`
: '共享 Key (单/多用户共享)';
console.log('\n═══════════════════════════════════════════════════');
console.log(' Bookworm 授权码生成完毕');
console.log('═══════════════════════════════════════════════════');
console.log('');
console.log(` 模式: ${modeLabel}`);
if (userName) console.log(` 用户: ${userName}`);
console.log(` 授权码: ${authCode}`);
console.log(` 有效期: ${DAYS} 天 (至 ${expiryDisplay})`);
if (multiUser) {
console.log(` Relay Key: ${relayKey.slice(0,12)}... (已替换 ANTHROPIC_API_KEY)`);
console.log(` 文件 ID: ${fileId}${outFileName}`);
}
console.log('');
console.log(' ▶ 操作步骤:');
console.log(` 1. 将授权码发给用户: ${authCode}`);
console.log(` 2. 推送 ${outFileName} 到 Gitea:`);
console.log(` git add ${outFileName} && git commit -m "add user ${userName || fileId}" && git push`);
console.log('');
console.log(' ⚠ 安全提醒:');
console.log(' - 授权码即解密密钥, 请勿通过不安全渠道明文发送');
console.log(` - 到期日 (${expiryDisplay}) 后自动失效`);
if (multiUser) {
console.log(` - 各用户 secrets-XXXXXXXX.enc 独立, 轮换互不影响`);
}
console.log('═══════════════════════════════════════════════════\n');