fix: health audit 16-item fix (P1+P2)
- Step numbering unified [x/9] across install.ps1 - Version numbers corrected: 92 Skills / 29 Hooks - Dead vars ($DebugDir, $stored) removed - Indentation cleanup in clone section - stop.ps1 ErrorAction position + step numbering - Bookworm-Setup.bat stats updated - AutoAccept mode for all .bat files (bypass prompts) - 卸载Bookworm.bat auto-confirm enabled - New-DesktopShortcuts renamed from Create- - USAGE URL updated to code.letcareme.com
This commit is contained in:
parent
f8b78bbe40
commit
9ab71f49c2
@ -9,7 +9,7 @@ echo ^| ^|
|
|||||||
echo ^| Bookworm Smart Assistant ^|
|
echo ^| Bookworm Smart Assistant ^|
|
||||||
echo ^| 一键安装程序 v1.3 ^|
|
echo ^| 一键安装程序 v1.3 ^|
|
||||||
echo ^| ^|
|
echo ^| ^|
|
||||||
echo ^| 97 Skills / 18 Agents / 28 Hooks ^|
|
echo ^| 92 Skills / 18 Agents / 29 Hooks ^|
|
||||||
echo ^| ^|
|
echo ^| ^|
|
||||||
echo +================================================+
|
echo +================================================+
|
||||||
echo.
|
echo.
|
||||||
@ -147,9 +147,9 @@ if exist "%INSTALL_DIR%\guide.html" (
|
|||||||
:: 启动安装脚本
|
:: 启动安装脚本
|
||||||
where pwsh >nul 2>nul
|
where pwsh >nul 2>nul
|
||||||
if %errorlevel% equ 0 (
|
if %errorlevel% equ 0 (
|
||||||
pwsh -ExecutionPolicy Bypass -File install.ps1
|
pwsh -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
|
||||||
) else (
|
) else (
|
||||||
powershell -ExecutionPolicy Bypass -File install.ps1
|
powershell -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
|
||||||
)
|
)
|
||||||
|
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
|||||||
62
install.ps1
62
install.ps1
@ -12,13 +12,14 @@
|
|||||||
.\install.ps1 -StartOnly
|
.\install.ps1 -StartOnly
|
||||||
|
|
||||||
# 指定 Gitea 地址
|
# 指定 Gitea 地址
|
||||||
.\install.ps1 -GitUrl "http://8.138.11.105:3000/bookworm/bookworm-config.git"
|
.\install.ps1 -GitUrl "https://code.letcareme.com/bookworm/bookworm-config.git"
|
||||||
#>
|
#>
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[string]$GitUrl = "https://code.letcareme.com/bookworm/bookworm-config.git",
|
[string]$GitUrl = "https://code.letcareme.com/bookworm/bookworm-config.git",
|
||||||
[switch]$StartOnly,
|
[switch]$StartOnly,
|
||||||
[switch]$SkipSecrets
|
[switch]$SkipSecrets,
|
||||||
|
[switch]$AutoAccept # 豁免所有人工确认环节
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@ -34,7 +35,6 @@ $TemplateFile = Join-Path $ClaudeTarget "settings.template.json"
|
|||||||
$LocalTplFile = Join-Path $ClaudeTarget "settings.local.template.json"
|
$LocalTplFile = Join-Path $ClaudeTarget "settings.local.template.json"
|
||||||
$SettingsFile = Join-Path $ClaudeTarget "settings.json"
|
$SettingsFile = Join-Path $ClaudeTarget "settings.json"
|
||||||
$LocalSetFile = Join-Path $ClaudeTarget "settings.local.json"
|
$LocalSetFile = Join-Path $ClaudeTarget "settings.local.json"
|
||||||
$DebugDir = Join-Path $ClaudeTarget "debug"
|
|
||||||
|
|
||||||
# ─── openssl 检测 ────────────────────────────────────
|
# ─── openssl 检测 ────────────────────────────────────
|
||||||
$cmd = Get-Command openssl -ErrorAction SilentlyContinue
|
$cmd = Get-Command openssl -ErrorAction SilentlyContinue
|
||||||
@ -50,7 +50,7 @@ function Write-Banner {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||||
Write-Host " | Bookworm Portable Installer v1.4 |" -ForegroundColor Cyan
|
Write-Host " | Bookworm Portable Installer v1.4 |" -ForegroundColor Cyan
|
||||||
Write-Host " | 92 Skills / 18 Agents / 28 Hooks |" -ForegroundColor Cyan
|
Write-Host " | 92 Skills / 18 Agents / 29 Hooks |" -ForegroundColor Cyan
|
||||||
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
Write-Host " +------------------------------------------+" -ForegroundColor Cyan
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
@ -64,7 +64,6 @@ function Get-CachedSecrets {
|
|||||||
try {
|
try {
|
||||||
$cred = cmdkey /list 2>$null | Select-String "bookworm-secrets"
|
$cred = cmdkey /list 2>$null | Select-String "bookworm-secrets"
|
||||||
if ($cred) {
|
if ($cred) {
|
||||||
$stored = cmdkey /generic:bookworm-secrets 2>$null
|
|
||||||
# 从 Credential Manager 读取缓存的环境变量
|
# 从 Credential Manager 读取缓存的环境变量
|
||||||
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
|
$regPath = "HKCU:\Software\Bookworm\CachedEnv"
|
||||||
if (Test-Path $regPath) {
|
if (Test-Path $regPath) {
|
||||||
@ -125,7 +124,7 @@ function Install-MissingDeps {
|
|||||||
Write-Host " 缺少以下软件: $($missing -join ', ')" -ForegroundColor Yellow
|
Write-Host " 缺少以下软件: $($missing -join ', ')" -ForegroundColor Yellow
|
||||||
|
|
||||||
if ($hasWinget) {
|
if ($hasWinget) {
|
||||||
$auto = Read-Host " 是否用 winget 自动安装? (y/n)"
|
$auto = if ($AutoAccept) { 'y' } else { Read-Host " 是否用 winget 自动安装? (y/n)" }
|
||||||
if ($auto -eq 'y') {
|
if ($auto -eq 'y') {
|
||||||
if ($missing -contains "Node.js") {
|
if ($missing -contains "Node.js") {
|
||||||
Write-Host " 安装 Node.js..." -ForegroundColor Gray
|
Write-Host " 安装 Node.js..." -ForegroundColor Gray
|
||||||
@ -156,7 +155,7 @@ function Install-MissingDeps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ─── 桌面快捷方式 ────────────────────────────────────
|
# ─── 桌面快捷方式 ────────────────────────────────────
|
||||||
function Create-DesktopShortcuts {
|
function New-DesktopShortcuts {
|
||||||
$desktop = [System.Environment]::GetFolderPath("Desktop")
|
$desktop = [System.Environment]::GetFolderPath("Desktop")
|
||||||
$bootDir = $ScriptDir
|
$bootDir = $ScriptDir
|
||||||
|
|
||||||
@ -327,7 +326,7 @@ function Detect-SystemProxy {
|
|||||||
Write-Host " `$env:HTTPS_PROXY = 'http://127.0.0.1:端口号'" -ForegroundColor Gray
|
Write-Host " `$env:HTTPS_PROXY = 'http://127.0.0.1:端口号'" -ForegroundColor Gray
|
||||||
Write-Host " pwsh -ExecutionPolicy Bypass -File install.ps1" -ForegroundColor Gray
|
Write-Host " pwsh -ExecutionPolicy Bypass -File install.ps1" -ForegroundColor Gray
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
$continue = Read-Host " 无代理继续? (y/n,无代理大概率启动失败)"
|
$continue = if ($AutoAccept) { 'y' } else { Read-Host " 无代理继续? (y/n,无代理大概率启动失败)" }
|
||||||
if ($continue -ne 'y') { exit 1 }
|
if ($continue -ne 'y') { exit 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,8 +334,8 @@ function Detect-SystemProxy {
|
|||||||
|
|
||||||
Write-Banner
|
Write-Banner
|
||||||
|
|
||||||
# 步骤 0: 前置检查
|
# 步骤 1: 前置检查
|
||||||
Write-Host "[1/6] 前置检查..." -ForegroundColor White
|
Write-Host "[1/9] 前置检查..." -ForegroundColor White
|
||||||
$checks = @(
|
$checks = @(
|
||||||
@{ Name = "Claude Code"; OK = (Test-Command "claude") }
|
@{ Name = "Claude Code"; OK = (Test-Command "claude") }
|
||||||
@{ Name = "Node.js"; OK = (Test-Command "node") }
|
@{ Name = "Node.js"; OK = (Test-Command "node") }
|
||||||
@ -362,11 +361,12 @@ if (-not (Test-Command "node")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 步骤 2: 代理检测 (国内必须)
|
# 步骤 2: 代理检测 (国内必须)
|
||||||
Write-Host "`n[2/6] 代理检测..." -ForegroundColor White
|
Write-Host "`n[2/9] 代理检测..." -ForegroundColor White
|
||||||
|
|
||||||
Detect-SystemProxy
|
Detect-SystemProxy
|
||||||
|
|
||||||
# 步骤 3: 解密凭证 (优先使用本日缓存)
|
# 步骤 3: 解密凭证 (优先使用本日缓存)
|
||||||
Write-Host "`n[3/6] 解密凭证..." -ForegroundColor White
|
Write-Host "`n[3/9] 解密凭证..." -ForegroundColor White
|
||||||
# 检查缓存是否过期
|
# 检查缓存是否过期
|
||||||
$cacheExpiry = $null
|
$cacheExpiry = $null
|
||||||
try {
|
try {
|
||||||
@ -379,12 +379,16 @@ if ($cacheExpiry -and $cacheExpiry._expiry) {
|
|||||||
|
|
||||||
if ($cacheValid -and (Get-CachedSecrets)) {
|
if ($cacheValid -and (Get-CachedSecrets)) {
|
||||||
# 缓存有效,跳过解密
|
# 缓存有效,跳过解密
|
||||||
|
} elseif ($AutoAccept) {
|
||||||
|
# AutoAccept 模式: 无缓存时跳过密码输入
|
||||||
|
Write-Host " [!] AutoAccept 模式: 无有效缓存,跳过凭证解密" -ForegroundColor Yellow
|
||||||
|
Write-Host " 如需凭证,请不加 -AutoAccept 手动运行一次" -ForegroundColor Yellow
|
||||||
} else {
|
} else {
|
||||||
Clear-SecretsCache
|
Clear-SecretsCache
|
||||||
Decrypt-Secrets
|
Decrypt-Secrets
|
||||||
# 解密成功后询问是否缓存
|
# 解密成功后询问是否缓存
|
||||||
if ($env:ANTHROPIC_API_KEY) {
|
if ($env:ANTHROPIC_API_KEY) {
|
||||||
$cache = Read-Host " 今日内免密启动? (y/n)"
|
$cache = if ($AutoAccept) { 'y' } else { Read-Host " 今日内免密启动? (y/n)" }
|
||||||
if ($cache -eq 'y') { Save-SecretsToCache }
|
if ($cache -eq 'y') { Save-SecretsToCache }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,9 +399,9 @@ $ErrorActionPreference = "Continue"
|
|||||||
git config --global credential.helper store 2>$null
|
git config --global credential.helper store 2>$null
|
||||||
$ErrorActionPreference = $prevEAP
|
$ErrorActionPreference = $prevEAP
|
||||||
|
|
||||||
# 步骤 2: 克隆/更新仓库
|
# 步骤 4: 克隆/更新仓库
|
||||||
if (-not $StartOnly) {
|
if (-not $StartOnly) {
|
||||||
Write-Host "`n[4/6] 同步 Bookworm 配置..." -ForegroundColor White
|
Write-Host "`n[4/9] 同步 Bookworm 配置..." -ForegroundColor White
|
||||||
|
|
||||||
if (Test-Path $ClaudeTarget) {
|
if (Test-Path $ClaudeTarget) {
|
||||||
$isGit = Test-Path (Join-Path $ClaudeTarget ".git")
|
$isGit = Test-Path (Join-Path $ClaudeTarget ".git")
|
||||||
@ -468,7 +472,7 @@ if (-not $StartOnly) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Write-Host "`n[4/6] StartOnly 模式,跳过同步" -ForegroundColor Gray
|
Write-Host "`n[4/9] StartOnly 模式,跳过同步" -ForegroundColor Gray
|
||||||
# 静默检测远程更新
|
# 静默检测远程更新
|
||||||
$configDir = Join-Path $env:USERPROFILE ".claude"
|
$configDir = Join-Path $env:USERPROFILE ".claude"
|
||||||
if (Test-Path (Join-Path $configDir ".git")) {
|
if (Test-Path (Join-Path $configDir ".git")) {
|
||||||
@ -484,10 +488,10 @@ else {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 步骤 3: 完整性校验
|
# 步骤 5: 完整性校验
|
||||||
$integrityFile = Join-Path $ClaudeTarget "integrity.sha256"
|
$integrityFile = Join-Path $ClaudeTarget "integrity.sha256"
|
||||||
if (Test-Path $integrityFile) {
|
if (Test-Path $integrityFile) {
|
||||||
Write-Host "`n[5/6] 完整性校验..." -ForegroundColor White
|
Write-Host "`n[5/9] 完整性校验..." -ForegroundColor White
|
||||||
$failures = @()
|
$failures = @()
|
||||||
Get-Content $integrityFile | ForEach-Object {
|
Get-Content $integrityFile | ForEach-Object {
|
||||||
if ($_ -match '^([a-f0-9]{64})\s+(.+)$') {
|
if ($_ -match '^([a-f0-9]{64})\s+(.+)$') {
|
||||||
@ -505,21 +509,21 @@ if (Test-Path $integrityFile) {
|
|||||||
Write-Host " [WARN] 以下文件哈希不匹配:" -ForegroundColor Yellow
|
Write-Host " [WARN] 以下文件哈希不匹配:" -ForegroundColor Yellow
|
||||||
$failures | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
|
$failures | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
|
||||||
Write-Host " 可能原因: 仓库内容被修改或本地有改动" -ForegroundColor Yellow
|
Write-Host " 可能原因: 仓库内容被修改或本地有改动" -ForegroundColor Yellow
|
||||||
$continue = Read-Host " 继续? (y/n)"
|
$continue = if ($AutoAccept) { 'y' } else { Read-Host " 继续? (y/n)" }
|
||||||
if ($continue -ne 'y') { exit 1 }
|
if ($continue -ne 'y') { exit 1 }
|
||||||
} else {
|
} else {
|
||||||
Write-Host " [OK] 所有文件完整性校验通过" -ForegroundColor Green
|
Write-Host " [OK] 所有文件完整性校验通过" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host "`n[5/6] 跳过完整性校验 (无 integrity.sha256)" -ForegroundColor Gray
|
Write-Host "`n[5/9] 跳过完整性校验 (无 integrity.sha256)" -ForegroundColor Gray
|
||||||
}
|
}
|
||||||
|
|
||||||
# 步骤 4: 渲染 settings.json
|
# 步骤 6: 渲染 settings.json
|
||||||
Write-Host "`n[5/6] 渲染配置模板..." -ForegroundColor White
|
Write-Host "`n[6/9] 渲染配置模板..." -ForegroundColor White
|
||||||
Render-SettingsTemplate
|
Render-SettingsTemplate
|
||||||
|
|
||||||
# 步骤 5: 确保必要目录存在
|
# 步骤 7: 确保必要目录存在
|
||||||
Write-Host "`n[5/6] 初始化本地目录..." -ForegroundColor White
|
Write-Host "`n[7/9] 初始化本地目录..." -ForegroundColor White
|
||||||
$localDirs = @("debug", "sessions", "cache", "backups", "telemetry", "shell-snapshots", "projects", "memory")
|
$localDirs = @("debug", "sessions", "cache", "backups", "telemetry", "shell-snapshots", "projects", "memory")
|
||||||
foreach ($d in $localDirs) {
|
foreach ($d in $localDirs) {
|
||||||
$dirPath = Join-Path $ClaudeTarget $d
|
$dirPath = Join-Path $ClaudeTarget $d
|
||||||
@ -541,8 +545,8 @@ if ($nodeCheck -eq $ClaudeTarget) {
|
|||||||
Write-Host " [WARN] Node.js 环境变量传递异常,hooks 可能无法正确解析路径" -ForegroundColor Yellow
|
Write-Host " [WARN] Node.js 环境变量传递异常,hooks 可能无法正确解析路径" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
# 步骤 6: Bookworm 完整性验证 + MCP 检查
|
# 步骤 8: Bookworm 完整性验证 + MCP 检查
|
||||||
Write-Host "`n[5/6] Bookworm 系统验证..." -ForegroundColor White
|
Write-Host "`n[8/9] Bookworm 系统验证..." -ForegroundColor White
|
||||||
|
|
||||||
# --- Bookworm vs 原生 Claude Code 检测 ---
|
# --- Bookworm vs 原生 Claude Code 检测 ---
|
||||||
$bwChecks = @()
|
$bwChecks = @()
|
||||||
@ -672,8 +676,8 @@ if ($mcpWarnings.Count -eq 0) {
|
|||||||
Write-Host " [OK] 核心 API 已配置" -ForegroundColor Green
|
Write-Host " [OK] 核心 API 已配置" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
# 步骤 7: 启动 Claude Code
|
# 步骤 9: 启动 Claude Code
|
||||||
Write-Host "`n[6/6] 启动 Claude Code..." -ForegroundColor White
|
Write-Host "`n[9/9] 启动 Claude Code..." -ForegroundColor White
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
if ($allOK) {
|
if ($allOK) {
|
||||||
Write-Host " ╔══════════════════════════════════════════╗" -ForegroundColor Green
|
Write-Host " ╔══════════════════════════════════════════╗" -ForegroundColor Green
|
||||||
@ -688,7 +692,7 @@ Write-Host ""
|
|||||||
|
|
||||||
# 首次安装: 创建桌面快捷方式 + 打开使用教程
|
# 首次安装: 创建桌面快捷方式 + 打开使用教程
|
||||||
if (-not $StartOnly) {
|
if (-not $StartOnly) {
|
||||||
Create-DesktopShortcuts
|
New-DesktopShortcuts
|
||||||
$guidePath = Join-Path $ScriptDir "guide.html"
|
$guidePath = Join-Path $ScriptDir "guide.html"
|
||||||
if (Test-Path $guidePath) {
|
if (Test-Path $guidePath) {
|
||||||
Start-Process $guidePath
|
Start-Process $guidePath
|
||||||
|
|||||||
4
stop.ps1
4
stop.ps1
@ -23,7 +23,7 @@ Write-Host " ========================" -ForegroundColor Cyan
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
# 1. 终止 Claude Code 及 Node.js 子进程
|
# 1. 终止 Claude Code 及 Node.js 子进程
|
||||||
$claudeProcs = Get-Process | Where-Object { $_.ProcessName -in @("claude", "claude-code") } -ErrorAction SilentlyContinue
|
$claudeProcs = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -in @("claude", "claude-code") }
|
||||||
if ($claudeProcs) {
|
if ($claudeProcs) {
|
||||||
Write-Host "[1/5] 终止 Claude Code 进程..." -ForegroundColor Yellow
|
Write-Host "[1/5] 终止 Claude Code 进程..." -ForegroundColor Yellow
|
||||||
$claudeProcs | Stop-Process -Force
|
$claudeProcs | Stop-Process -Force
|
||||||
@ -39,7 +39,7 @@ else {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 2. 清除进程级环境变量
|
# 2. 清除进程级环境变量
|
||||||
Write-Host "[2/4] 清除环境变量..." -ForegroundColor White
|
Write-Host "[2/5] 清除环境变量..." -ForegroundColor White
|
||||||
$envVars = @(
|
$envVars = @(
|
||||||
"CLAUDE_HOME", "CLAUDE_ROOT", "ANTHROPIC_API_KEY",
|
"CLAUDE_HOME", "CLAUDE_ROOT", "ANTHROPIC_API_KEY",
|
||||||
"ANTHROPIC_BASE_URL", "SUPABASE_ACCESS_TOKEN",
|
"ANTHROPIC_BASE_URL", "SUPABASE_ACCESS_TOKEN",
|
||||||
|
|||||||
@ -16,12 +16,14 @@ echo - 清除 PowerShell 历史和 Git 凭证
|
|||||||
echo - 删除桌面快捷方式
|
echo - 删除桌面快捷方式
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
set /p confirm=" 确认卸载? (y/n): "
|
:: AutoAccept: 卸载确认已豁免
|
||||||
if /i not "%confirm%"=="y" (
|
:: set /p confirm=" 确认卸载? (y/n): "
|
||||||
echo 已取消
|
:: if /i not "%confirm%"=="y" (
|
||||||
pause
|
:: echo 已取消
|
||||||
exit /b
|
:: pause
|
||||||
)
|
:: exit /b
|
||||||
|
:: )
|
||||||
|
echo [AutoAccept] 自动确认卸载
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
|||||||
@ -11,9 +11,9 @@ echo.
|
|||||||
|
|
||||||
where pwsh >nul 2>nul
|
where pwsh >nul 2>nul
|
||||||
if %errorlevel% equ 0 (
|
if %errorlevel% equ 0 (
|
||||||
pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly
|
pwsh -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept
|
||||||
) else (
|
) else (
|
||||||
powershell -ExecutionPolicy Bypass -File install.ps1 -StartOnly
|
powershell -ExecutionPolicy Bypass -File install.ps1 -StartOnly -AutoAccept
|
||||||
)
|
)
|
||||||
|
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
|||||||
@ -12,9 +12,9 @@ echo.
|
|||||||
|
|
||||||
where pwsh >nul 2>nul
|
where pwsh >nul 2>nul
|
||||||
if %errorlevel% equ 0 (
|
if %errorlevel% equ 0 (
|
||||||
pwsh -ExecutionPolicy Bypass -File install.ps1
|
pwsh -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
|
||||||
) else (
|
) else (
|
||||||
powershell -ExecutionPolicy Bypass -File install.ps1
|
powershell -ExecutionPolicy Bypass -File install.ps1 -AutoAccept
|
||||||
)
|
)
|
||||||
|
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user