diff --git a/admin-authcode-gui.ps1 b/admin-authcode-gui.ps1 deleted file mode 100644 index c9133e5..0000000 --- a/admin-authcode-gui.ps1 +++ /dev/null @@ -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 - } -} diff --git a/admin-authcode.ico b/admin-authcode.ico deleted file mode 100644 index 3dfd0d6..0000000 Binary files a/admin-authcode.ico and /dev/null differ diff --git a/build.ps1 b/build.ps1 index 71a357c..2ad3738 100644 --- a/build.ps1 +++ b/build.ps1 @@ -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 "" diff --git a/gen-authcode.js b/gen-authcode.js deleted file mode 100644 index b3c08e9..0000000 --- a/gen-authcode.js +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env node -'use strict'; -/** - * Bookworm 授权码生成工具 (管理员使用) - * - * 用法: - * node gen-authcode.js [选项] - * - * 选项: - * --relay-key, -k 中转站限额子 Key (替换 ANTHROPIC_API_KEY) - * --user, -u 用户标识 (仅用于显示, 不影响加密) - * [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'); - console.error(' --user, -u 用户标识 (仅显示)'); - 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');