diff --git a/admin-authcode-gui.ps1 b/admin-authcode-gui.ps1 new file mode 100644 index 0000000..32e1a9f --- /dev/null +++ b/admin-authcode-gui.ps1 @@ -0,0 +1,337 @@ +<# +.SYNOPSIS + Bookworm 授权码生成器 (管理员 GUI 工具) +.DESCRIPTION + 内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码。 + 打包命令: 见 build.ps1 -Admin +#> +$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 } + +$GenScript = Join-Path $ScriptDir "gen-authcode.js" +$SecretsTxt = Join-Path $ScriptDir "secrets.txt" + +# ─── 品牌色 ─────────────────────────────────────────── +$brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) +$brandDark = [System.Drawing.Color]::FromArgb(30, 31, 46) +$brandLight = [System.Drawing.Color]::FromArgb(245, 245, 250) +$successGreen = [System.Drawing.Color]::FromArgb(46, 160, 67) +$warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29) + +# ─── 主窗口 ─────────────────────────────────────────── +$form = New-Object System.Windows.Forms.Form +$form.Text = "Bookworm 授权码生成器 — 管理员工具" +$form.Size = New-Object System.Drawing.Size(560, 580) +$form.StartPosition = "CenterScreen" +$form.FormBorderStyle = "FixedDialog" +$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.Location = New-Object System.Drawing.Point(0, 0) +$header.Size = New-Object System.Drawing.Size(560, 56) +$header.BackColor = $brandDark +$form.Controls.Add($header) + +$titleLabel = New-Object System.Windows.Forms.Label +$titleLabel.Location = New-Object System.Drawing.Point(20, 14) +$titleLabel.Size = New-Object System.Drawing.Size(520, 28) +$titleLabel.Text = "Bookworm 授权码生成器" +$titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold) +$titleLabel.ForeColor = [System.Drawing.Color]::White +$header.Controls.Add($titleLabel) + +# ─── 输入区 ─────────────────────────────────────────── +$y = 72 + +# 用户名 +$lblUser = New-Object System.Windows.Forms.Label +$lblUser.Location = New-Object System.Drawing.Point(24, $y) +$lblUser.Size = New-Object System.Drawing.Size(120, 22) +$lblUser.Text = "用户标识 (--user):" +$form.Controls.Add($lblUser) +$txtUser = New-Object System.Windows.Forms.TextBox +$txtUser.Location = New-Object System.Drawing.Point(150, $y - 2) +$txtUser.Size = New-Object System.Drawing.Size(370, 28) +$txtUser.Font = New-Object System.Drawing.Font("Consolas", 11) +$txtUser.PlaceholderText = "例如: alice, 张三, 茶师兄" +$form.Controls.Add($txtUser) +$y += 38 + +# Relay Sub-Key +$lblKey = New-Object System.Windows.Forms.Label +$lblKey.Location = New-Object System.Drawing.Point(24, $y) +$lblKey.Size = New-Object System.Drawing.Size(120, 22) +$lblKey.Text = "Relay Sub-Key:" +$form.Controls.Add($lblKey) +$txtKey = New-Object System.Windows.Forms.TextBox +$txtKey.Location = New-Object System.Drawing.Point(150, $y - 2) +$txtKey.Size = New-Object System.Drawing.Size(370, 28) +$txtKey.Font = New-Object System.Drawing.Font("Consolas", 10) +$txtKey.PasswordChar = '*' +$txtKey.PlaceholderText = "sk-... (中转站后台创建的子 Key)" +$form.Controls.Add($txtKey) +$y += 28 + +# 显示/隐藏 Key +$chkShowKey = New-Object System.Windows.Forms.CheckBox +$chkShowKey.Location = New-Object System.Drawing.Point(150, $y) +$chkShowKey.Size = New-Object System.Drawing.Size(120, 22) +$chkShowKey.Text = "显示 Key" +$chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } }) +$form.Controls.Add($chkShowKey) +$y += 32 + +# 有效期 +$lblDays = New-Object System.Windows.Forms.Label +$lblDays.Location = New-Object System.Drawing.Point(24, $y) +$lblDays.Size = New-Object System.Drawing.Size(120, 22) +$lblDays.Text = "有效期 (天):" +$form.Controls.Add($lblDays) +$cmbDays = New-Object System.Windows.Forms.ComboBox +$cmbDays.Location = New-Object System.Drawing.Point(150, $y - 2) +$cmbDays.Size = New-Object System.Drawing.Size(100, 28) +$cmbDays.DropDownStyle = "DropDownList" +@(7, 14, 30, 60, 90, 180, 365) | ForEach-Object { $cmbDays.Items.Add($_) | Out-Null } +$cmbDays.SelectedIndex = 2 # 默认 30 天 +$form.Controls.Add($cmbDays) + +$lblDaysHint = New-Object System.Windows.Forms.Label +$lblDaysHint.Location = New-Object System.Drawing.Point(260, $y) +$lblDaysHint.Size = New-Object System.Drawing.Size(260, 22) +$lblDaysHint.Text = "" +$lblDaysHint.ForeColor = [System.Drawing.Color]::Gray +$form.Controls.Add($lblDaysHint) +$cmbDays.Add_SelectedIndexChanged({ + $d = [int]$cmbDays.SelectedItem + $exp = (Get-Date).AddDays($d).ToString("yyyy-MM-dd") + $lblDaysHint.Text = "到期: $exp" +}) +# 触发初始值 +$cmbDays.SelectedIndex = 2 +$y += 38 + +# secrets.txt 状态 +$lblSecrets = New-Object System.Windows.Forms.Label +$lblSecrets.Location = New-Object System.Drawing.Point(24, $y) +$lblSecrets.Size = New-Object System.Drawing.Size(500, 22) +if (Test-Path $SecretsTxt) { + $lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count + $lblSecrets.Text = "secrets.txt: $lineCount 个凭证已配置" + $lblSecrets.ForeColor = $successGreen +} else { + $lblSecrets.Text = "secrets.txt: 未找到 ($SecretsTxt)" + $lblSecrets.ForeColor = [System.Drawing.Color]::Red +} +$form.Controls.Add($lblSecrets) +$y += 28 + +# Node.js 状态 +$lblNode = New-Object System.Windows.Forms.Label +$lblNode.Location = New-Object System.Drawing.Point(24, $y) +$lblNode.Size = New-Object System.Drawing.Size(500, 22) +$nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue) +if ($nodeOK) { + $nodeVer = try { (& node --version 2>$null) } catch { "" } + $lblNode.Text = "Node.js: $nodeVer" + $lblNode.ForeColor = $successGreen +} else { + $lblNode.Text = "Node.js: 未安装 (必需)" + $lblNode.ForeColor = [System.Drawing.Color]::Red +} +$form.Controls.Add($lblNode) +$y += 36 + +# ─── 生成按钮 ───────────────────────────────────────── +$btnGenerate = New-Object System.Windows.Forms.Button +$btnGenerate.Location = New-Object System.Drawing.Point(150, $y) +$btnGenerate.Size = New-Object System.Drawing.Size(160, 40) +$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 +$form.Controls.Add($btnGenerate) + +$btnClear = New-Object System.Windows.Forms.Button +$btnClear.Location = New-Object System.Drawing.Point(320, $y) +$btnClear.Size = New-Object System.Drawing.Size(80, 40) +$btnClear.Text = "清空" +$btnClear.FlatStyle = "Flat" +$btnClear.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray +$form.Controls.Add($btnClear) +$y += 54 + +# ─── 分隔线 ─────────────────────────────────────────── +$sep = New-Object System.Windows.Forms.Label +$sep.Location = New-Object System.Drawing.Point(20, $y) +$sep.Size = New-Object System.Drawing.Size(510, 1) +$sep.BorderStyle = "Fixed3D" +$form.Controls.Add($sep) +$y += 10 + +# ─── 结果区 ─────────────────────────────────────────── +$lblResultTitle = New-Object System.Windows.Forms.Label +$lblResultTitle.Location = New-Object System.Drawing.Point(24, $y) +$lblResultTitle.Size = New-Object System.Drawing.Size(200, 22) +$lblResultTitle.Text = "生成结果" +$lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold) +$lblResultTitle.ForeColor = $brandDark +$form.Controls.Add($lblResultTitle) +$y += 26 + +# 授权码 (大字 Consolas, 可选中复制) +$txtAuthCode = New-Object System.Windows.Forms.TextBox +$txtAuthCode.Location = New-Object System.Drawing.Point(24, $y) +$txtAuthCode.Size = New-Object System.Drawing.Size(400, 32) +$txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold) +$txtAuthCode.ReadOnly = $true +$txtAuthCode.BackColor = $brandLight +$txtAuthCode.ForeColor = $brandDark +$txtAuthCode.Text = "" +$form.Controls.Add($txtAuthCode) + +$btnCopy = New-Object System.Windows.Forms.Button +$btnCopy.Location = New-Object System.Drawing.Point(430, $y) +$btnCopy.Size = New-Object System.Drawing.Size(90, 32) +$btnCopy.Text = "复制" +$btnCopy.FlatStyle = "Flat" +$btnCopy.BackColor = $brandLight +$btnCopy.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray +$btnCopy.Enabled = $false +$form.Controls.Add($btnCopy) +$y += 40 + +# 详细信息 +$lblDetails = New-Object System.Windows.Forms.Label +$lblDetails.Location = New-Object System.Drawing.Point(24, $y) +$lblDetails.Size = New-Object System.Drawing.Size(500, 66) +$lblDetails.Text = "" +$lblDetails.ForeColor = [System.Drawing.Color]::FromArgb(100, 100, 120) +$form.Controls.Add($lblDetails) +$y += 70 + +# 状态栏 +$statusBar = New-Object System.Windows.Forms.Label +$statusBar.Location = New-Object System.Drawing.Point(0, $y) +$statusBar.Size = New-Object System.Drawing.Size(560, 24) +$statusBar.BackColor = $brandLight +$statusBar.ForeColor = [System.Drawing.Color]::Gray +$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 + } +}) + +$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 { + $args = @($GenScript, $days, "-k", $key, "-u", $user) + $proc = Start-Process node -ArgumentList $args -NoNewWindow -PassThru ` + -RedirectStandardOutput "$env:TEMP\bw-gen-out.tmp" ` + -RedirectStandardError "$env:TEMP\bw-gen-err.tmp" ` + -WorkingDirectory $ScriptDir + + $sw = [System.Diagnostics.Stopwatch]::StartNew() + while (-not $proc.HasExited -and $sw.ElapsedMilliseconds -lt 30000) { + [System.Windows.Forms.Application]::DoEvents() + Start-Sleep -Milliseconds 100 + } + if (-not $proc.HasExited) { $proc.Kill(); throw "超时 (30s)" } + + $stdout = Get-Content "$env:TEMP\bw-gen-out.tmp" -Raw -ErrorAction SilentlyContinue + $stderr = Get-Content "$env:TEMP\bw-gen-err.tmp" -Raw -ErrorAction SilentlyContinue + Remove-Item "$env:TEMP\bw-gen-out.tmp", "$env:TEMP\bw-gen-err.tmp" -Force -ErrorAction SilentlyContinue + + if ($proc.ExitCode -ne 0) { + throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr" + } + + # 解析输出 + $authCode = if ($stdout -match '授权码:\s*(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" } + $fileId = if ($stdout -match '文件 ID:\s*([a-f0-9]{8})\s*→\s*(secrets-[a-f0-9]{8}\.enc)') { $Matches[2] } else { "" } + $expiry = if ($stdout -match '有效期:\s*\d+\s*天\s*\(至\s*(\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 + $statusBar.Text = " 生成成功 — $fileId | 请 git add + push 后将授权码发给 $user" + $statusBar.ForeColor = $successGreen + } 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) diff --git a/admin-authcode.ico b/admin-authcode.ico new file mode 100644 index 0000000..3dfd0d6 Binary files /dev/null and b/admin-authcode.ico differ diff --git a/build.ps1 b/build.ps1 index 7a0c103..d46f64d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -97,58 +97,48 @@ if ($buildSetup) { } # ════════════════════════════════════════════════════════ -# 2. gen-authcode.exe (gen-authcode.js → pkg) +# 2. Bookworm-AuthGen.exe (admin-authcode-gui.ps1 → PS2EXE) # ════════════════════════════════════════════════════════ if ($buildAdmin) { - Write-Step "打包 gen-authcode.exe (pkg)" + Write-Step "打包 Bookworm-AuthGen.exe (PS2EXE GUI)" - # 检查 Node.js - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { - Write-Fail "需要 Node.js,请先安装: https://nodejs.org" + $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 } - # 安装/检查 pkg - # 优先找 npm global bin 目录下的 pkg(不依赖 PATH 是否刷新) - $npmGlobalBin = Join-Path (npm root -g 2>$null) "..\pkg.cmd" | Resolve-Path -ErrorAction SilentlyContinue - $pkgCmd = if ($npmGlobalBin) { $npmGlobalBin.Path } else { (Get-Command pkg -ErrorAction SilentlyContinue)?.Source } - if (-not $pkgCmd) { - Write-Warn "pkg 未安装,正在安装..." - npm install -g pkg - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + - [System.Environment]::GetEnvironmentVariable("Path","User") - $npmGlobalBin = Join-Path (npm root -g 2>$null) "..\pkg.cmd" | Resolve-Path -ErrorAction SilentlyContinue - $pkgCmd = $npmGlobalBin.Path - } - - $inputJs = Join-Path $ScriptDir "gen-authcode.js" - $outputExe = Join-Path $DistDir "gen-authcode.exe" - - if (-not (Test-Path $inputJs)) { - Write-Fail "找不到 gen-authcode.js" - exit 1 - } - - Write-Host " 输入: $inputJs" -ForegroundColor Gray + Write-Host " 输入: $inputPs1" -ForegroundColor Gray Write-Host " 输出: $outputExe" -ForegroundColor Gray - Write-Host " (内嵌 Node.js 运行时,首次打包约 1 分钟)" -ForegroundColor Gray - & $pkgCmd $inputJs --targets node18-win-x64 --output $outputExe 2>&1 | - ForEach-Object { Write-Host " $_" -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) { - $sizeMB = [math]::Round((Get-Item $outputExe).Length / 1MB, 1) - Write-OK "gen-authcode.exe 打包完成 (${sizeMB} MB)" - - # 注入图标 (rcedit) - $iconFile = Join-Path $ScriptDir "bookworm.ico" - $rcedit = "$env:APPDATA\npm\node_modules\rcedit\bin\rcedit-x64.exe" - if ((Test-Path $iconFile) -and (Test-Path $rcedit)) { - & $rcedit $outputExe --set-icon $iconFile | Out-Null - Write-OK "gen-authcode.exe 图标注入完成" - } + $sizeKB = [math]::Round((Get-Item $outputExe).Length / 1KB) + Write-OK "Bookworm-AuthGen.exe 打包完成 (${sizeKB} KB)" } else { - Write-Fail "gen-authcode.exe 打包失败" + Write-Fail "Bookworm-AuthGen.exe 打包失败" exit 1 } } @@ -169,6 +159,6 @@ Get-ChildItem $DistDir | ForEach-Object { Write-Host "" Write-Host " 分发说明:" -ForegroundColor Gray -Write-Host " Bookworm-Setup.exe → 放入 bookworm-boot 仓库根目录,发给用户" -ForegroundColor Gray -Write-Host " gen-authcode.exe → 管理员本机使用,勿对外分发" -ForegroundColor Gray +Write-Host " Bookworm-Setup.exe → 用户安装器 (公开下载)" -ForegroundColor Gray +Write-Host " Bookworm-AuthGen.exe → 管理员授权码生成器 (勿对外分发)" -ForegroundColor Gray Write-Host ""