Compare commits
2 Commits
e5dc77cb96
...
1191123602
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1191123602 | ||
|
|
887a672e7d |
222
README.md
Normal file
222
README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# ClaudeTools Bootstrap / Reinstall Guide
|
||||
|
||||
Complete instructions for backing up and restoring a ClaudeTools development environment on Windows 11.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Reinstall: Creating the Archive
|
||||
|
||||
Before wiping or reinstalling Windows, create a backup archive.
|
||||
|
||||
### Option A: Automated Archive (Recommended)
|
||||
|
||||
Run the bootstrap script in archive mode:
|
||||
|
||||
```powershell
|
||||
cd D:\ClaudeTools\bootstrap
|
||||
.\bootstrap.ps1 -Archive
|
||||
```
|
||||
|
||||
This creates `D:\ClaudeTools-backup.zip` containing:
|
||||
- The full ClaudeTools repository (excluding `node_modules`, `__pycache__`, `venv`)
|
||||
- Claude configuration and memory from `C:\Users\<you>\.claude\`
|
||||
|
||||
To specify a custom output path:
|
||||
|
||||
```powershell
|
||||
.\bootstrap.ps1 -Archive -ArchivePath "E:\Backups\claudetools-2026-03-17.zip"
|
||||
```
|
||||
|
||||
### Option B: Manual Archive
|
||||
|
||||
If the script is unavailable, manually zip these locations:
|
||||
|
||||
1. **ClaudeTools repository**: `D:\ClaudeTools\` (entire directory)
|
||||
2. **Claude memory and config**: `C:\Users\<you>\.claude\` (entire directory)
|
||||
|
||||
Copy the archive(s) to external storage (USB, NAS, cloud) before proceeding with the Windows reinstall.
|
||||
|
||||
### What Does NOT Need Archiving
|
||||
|
||||
These are restored automatically by the bootstrap script:
|
||||
- Git, Node.js, Python, Ollama (reinstalled via winget)
|
||||
- npm global packages (reinstalled)
|
||||
- Python pip packages (reinstalled)
|
||||
- Ollama models (re-pulled)
|
||||
- MCP server virtual environments (recreated)
|
||||
|
||||
---
|
||||
|
||||
## Post-Reinstall: Running the Bootstrap
|
||||
|
||||
### Step 1: Prepare the D: Drive
|
||||
|
||||
If `D:\ClaudeTools` was on a separate partition that survived the reinstall, skip to Step 2.
|
||||
|
||||
Otherwise, extract your archive:
|
||||
|
||||
```powershell
|
||||
# Extract the ClaudeTools repo to D:\
|
||||
Expand-Archive -Path "E:\Backups\claudetools-2026-03-17.zip" -DestinationPath "D:\"
|
||||
|
||||
# Extract Claude config to your user profile
|
||||
# (The archive contains a 'claude-config' folder - copy it to the right place)
|
||||
Copy-Item -Path "D:\claude-config\*" -Destination "$env:USERPROFILE\.claude\" -Recurse -Force
|
||||
```
|
||||
|
||||
### Step 2: Run the Bootstrap Script
|
||||
|
||||
Open an **elevated PowerShell** (Run as Administrator):
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
D:\ClaudeTools\bootstrap\bootstrap.ps1
|
||||
```
|
||||
|
||||
The script runs 9 phases and takes approximately 15-30 minutes depending on download speeds and Ollama model sizes.
|
||||
|
||||
### Step 3: Advanced Usage
|
||||
|
||||
Run a single phase:
|
||||
|
||||
```powershell
|
||||
.\bootstrap.ps1 -OnlyPhase 4 # Only install Python packages
|
||||
```
|
||||
|
||||
Skip specific phases:
|
||||
|
||||
```powershell
|
||||
.\bootstrap.ps1 -SkipPhase 5 # Skip Ollama model pulls (slow)
|
||||
.\bootstrap.ps1 -SkipPhase 4,5 # Skip Python packages and Ollama models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase Reference
|
||||
|
||||
| Phase | What It Does | Duration |
|
||||
|-------|-------------|----------|
|
||||
| 1 | Install Git, Node.js, Python 3.13, Ollama via winget | 2-5 min |
|
||||
| 2 | Install Claude Code CLI + global npm packages | 1-2 min |
|
||||
| 3 | Clone or configure ClaudeTools git repository | <1 min |
|
||||
| 4 | Install all Python pip packages globally | 3-5 min |
|
||||
| 5 | Pull Ollama models (nomic-embed-text, llama3.1:8b, qwen2.5-coder:7b) | 5-15 min |
|
||||
| 6 | Create MCP server venv and install dependencies | 1-2 min |
|
||||
| 7 | Write Claude Code settings.json, copy commands, create directories | <1 min |
|
||||
| 8 | Initialize GrepAI | <1 min |
|
||||
| 9 | Verify all components are installed and working | <1 min |
|
||||
|
||||
---
|
||||
|
||||
## Manual Steps (Cannot Be Automated)
|
||||
|
||||
These steps require interactive authentication or browser actions:
|
||||
|
||||
### 1. Authenticate Claude Code
|
||||
|
||||
```powershell
|
||||
claude
|
||||
```
|
||||
|
||||
Follow the prompts to enter your Anthropic API key or log in via browser.
|
||||
|
||||
### 2. GitHub Personal Access Token
|
||||
|
||||
Edit `D:\ClaudeTools\.mcp.json` and set the `GITHUB_PERSONAL_ACCESS_TOKEN` value:
|
||||
|
||||
```json
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_your_token_here"
|
||||
}
|
||||
```
|
||||
|
||||
Generate a new token at: https://github.com/settings/tokens
|
||||
|
||||
### 3. Claude-in-Chrome Extension
|
||||
|
||||
Install the Chrome extension manually:
|
||||
- Open Chrome and navigate to the Chrome Web Store
|
||||
- Search for "Claude in Chrome" (or install from the MCP extension source)
|
||||
- Configure the extension to connect to your local MCP server
|
||||
|
||||
### 4. Restore Memory Files (If Needed)
|
||||
|
||||
If the bootstrap reports memory files are missing:
|
||||
|
||||
```powershell
|
||||
# Copy from your archive
|
||||
Copy-Item -Path "E:\Backups\claude-config\projects\D--ClaudeTools\memory\*" `
|
||||
-Destination "$env:USERPROFILE\.claude\projects\D--ClaudeTools\memory\" `
|
||||
-Recurse -Force
|
||||
```
|
||||
|
||||
### 5. Git Credentials
|
||||
|
||||
When you first `git pull` or `git push` to Gitea, you will be prompted for credentials. Use the Gitea username and password from `credentials.md`.
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After bootstrap completes, verify manually:
|
||||
|
||||
- [ ] `git --version` returns a version
|
||||
- [ ] `node --version` returns v24.x or later
|
||||
- [ ] `python --version` returns 3.13.x
|
||||
- [ ] `claude --version` returns a version
|
||||
- [ ] `ollama list` shows all 3 models
|
||||
- [ ] `D:\ClaudeTools` exists and has `.git` directory
|
||||
- [ ] `D:\ClaudeTools\.mcp.json` exists
|
||||
- [ ] `D:\ClaudeTools\grepai.exe` exists
|
||||
- [ ] `C:\Users\<you>\.claude\settings.json` exists
|
||||
- [ ] `C:\Users\<you>\.claude\commands\` has command files
|
||||
- [ ] Run `claude` from `D:\ClaudeTools` and confirm MCP servers connect
|
||||
- [ ] Claude-in-Chrome extension is installed and responsive
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### winget not found
|
||||
Install "App Installer" from the Microsoft Store. It ships with Windows 11 but may need updating.
|
||||
|
||||
### Node.js/Python not on PATH after install
|
||||
Close and reopen your terminal, or run:
|
||||
```powershell
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
```
|
||||
|
||||
### Ollama models fail to pull
|
||||
Ensure the Ollama service is running:
|
||||
```powershell
|
||||
ollama serve
|
||||
```
|
||||
Then retry:
|
||||
```powershell
|
||||
.\bootstrap.ps1 -OnlyPhase 5
|
||||
```
|
||||
|
||||
### pip install fails for specific packages
|
||||
Some packages (pywin32, opencv-python, pyzbar) require Visual C++ Build Tools. Install if needed:
|
||||
```powershell
|
||||
winget install Microsoft.VisualStudio.2022.BuildTools
|
||||
```
|
||||
Then re-run Phase 4:
|
||||
```powershell
|
||||
.\bootstrap.ps1 -OnlyPhase 4
|
||||
```
|
||||
|
||||
### GrepAI init requires interaction
|
||||
Run manually:
|
||||
```powershell
|
||||
cd D:\ClaudeTools
|
||||
.\grepai.exe init
|
||||
```
|
||||
Select Ollama as the provider and nomic-embed-text as the embedding model.
|
||||
|
||||
### SSL certificate errors with Gitea
|
||||
The bootstrap configures `http.sslVerify false` automatically. If you still see errors:
|
||||
```powershell
|
||||
cd D:\ClaudeTools
|
||||
git config http.sslVerify false
|
||||
```
|
||||
810
bootstrap.ps1
Normal file
810
bootstrap.ps1
Normal file
@@ -0,0 +1,810 @@
|
||||
#Requires -RunAsAdministrator
|
||||
<#
|
||||
.SYNOPSIS
|
||||
ClaudeTools Bootstrap / Reinstall Script
|
||||
.DESCRIPTION
|
||||
One-and-done script to restore a complete ClaudeTools development environment
|
||||
after a fresh Windows 11 install. Idempotent - safe to re-run at any time.
|
||||
.NOTES
|
||||
Run from an elevated PowerShell prompt:
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
.\bootstrap.ps1
|
||||
|
||||
Optional flags:
|
||||
-SkipPhase <number> Skip a specific phase (1-9)
|
||||
-OnlyPhase <number> Run only a specific phase (1-9)
|
||||
-Archive Create pre-reinstall archive instead of installing
|
||||
-ArchivePath <path> Custom archive output path (default: D:\ClaudeTools-backup.zip)
|
||||
#>
|
||||
|
||||
param(
|
||||
[int[]]$SkipPhase = @(),
|
||||
[int]$OnlyPhase = 0,
|
||||
[switch]$Archive,
|
||||
[string]$ArchivePath = "D:\ClaudeTools-backup.zip"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Globals
|
||||
# ---------------------------------------------------------------------------
|
||||
$Script:ClaudeToolsRoot = "D:\ClaudeTools"
|
||||
$Script:GiteaRepo = "https://git.azcomputerguru.com/azcomputerguru/claudetools.git"
|
||||
$Script:ClaudeConfigDir = Join-Path $env:USERPROFILE ".claude"
|
||||
$Script:ClaudeCommandsDir = Join-Path $Script:ClaudeConfigDir "commands"
|
||||
$Script:MemoryDir = Join-Path $Script:ClaudeConfigDir "projects\D--ClaudeTools\memory"
|
||||
$Script:Errors = [System.Collections.Generic.List[string]]::new()
|
||||
$Script:Warnings = [System.Collections.Generic.List[string]]::new()
|
||||
$Script:Successes = [System.Collections.Generic.List[string]]::new()
|
||||
$Script:Skipped = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
function Write-Status {
|
||||
param(
|
||||
[string]$Prefix,
|
||||
[string]$Message,
|
||||
[ConsoleColor]$Color = [ConsoleColor]::White
|
||||
)
|
||||
Write-Host "[$Prefix] " -ForegroundColor $Color -NoNewline
|
||||
Write-Host $Message
|
||||
}
|
||||
|
||||
function Write-OK { param([string]$Msg) Write-Status "OK" $Msg Green; $Script:Successes.Add($Msg) }
|
||||
function Write-Err { param([string]$Msg) Write-Status "ERROR" $Msg Red; $Script:Errors.Add($Msg) }
|
||||
function Write-Warn { param([string]$Msg) Write-Status "WARNING" $Msg Yellow; $Script:Warnings.Add($Msg) }
|
||||
function Write-Skip { param([string]$Msg) Write-Status "SKIP" $Msg Cyan; $Script:Skipped.Add($Msg) }
|
||||
function Write-Info { param([string]$Msg) Write-Status "INFO" $Msg White }
|
||||
function Write-Phase { param([int]$Num, [string]$Title)
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 70) -ForegroundColor Magenta
|
||||
Write-Host " Phase ${Num}: $Title" -ForegroundColor Magenta
|
||||
Write-Host ("=" * 70) -ForegroundColor Magenta
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Command)
|
||||
$null -ne (Get-Command $Command -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Refresh-PathEnv {
|
||||
# Reload PATH from registry so newly-installed tools are visible
|
||||
$machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
$userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$env:Path = "$machinePath;$userPath"
|
||||
}
|
||||
|
||||
function Install-WingetPackage {
|
||||
param(
|
||||
[string]$PackageId,
|
||||
[string]$FriendlyName,
|
||||
[string]$VersionHint = ""
|
||||
)
|
||||
|
||||
# Check if already installed via winget list
|
||||
$installed = winget list --id $PackageId 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $installed -match $PackageId) {
|
||||
Write-Skip "$FriendlyName is already installed"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Info "Installing $FriendlyName ($PackageId)..."
|
||||
$args = @("install", "--id", $PackageId, "--accept-source-agreements", "--accept-package-agreements", "--silent")
|
||||
if ($VersionHint) {
|
||||
$args += @("--version", $VersionHint)
|
||||
}
|
||||
$result = & winget @args 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "$FriendlyName installed successfully"
|
||||
return $true
|
||||
} else {
|
||||
# winget sometimes returns non-zero even on success (already installed race)
|
||||
$resultText = $result -join "`n"
|
||||
if ($resultText -match "already installed" -or $resultText -match "No applicable update") {
|
||||
Write-Skip "$FriendlyName is already installed"
|
||||
return $true
|
||||
}
|
||||
Write-Err "Failed to install $FriendlyName : $resultText"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function ShouldRunPhase {
|
||||
param([int]$PhaseNum)
|
||||
if ($OnlyPhase -gt 0) { return $PhaseNum -eq $OnlyPhase }
|
||||
return $PhaseNum -notin $SkipPhase
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Archive Mode: Create pre-reinstall backup
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Archive {
|
||||
Write-Phase 0 "Creating Pre-Reinstall Archive"
|
||||
|
||||
$tempStaging = Join-Path $env:TEMP "claudetools-archive-staging"
|
||||
if (Test-Path $tempStaging) { Remove-Item $tempStaging -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $tempStaging -Force | Out-Null
|
||||
|
||||
# Create subdirectories in staging
|
||||
$ctDest = Join-Path $tempStaging "ClaudeTools"
|
||||
$memDest = Join-Path $tempStaging "claude-memory"
|
||||
|
||||
# Copy ClaudeTools repo (exclude node_modules, __pycache__, venv, .git large objects)
|
||||
Write-Info "Copying ClaudeTools repository..."
|
||||
if (Test-Path $Script:ClaudeToolsRoot) {
|
||||
$robocopyArgs = @(
|
||||
$Script:ClaudeToolsRoot, $ctDest,
|
||||
"/E", "/NFL", "/NDL", "/NJH", "/NJS",
|
||||
"/XD", "node_modules", "__pycache__", "venv", ".venv", ".mypy_cache"
|
||||
)
|
||||
& robocopy @robocopyArgs | Out-Null
|
||||
Write-OK "ClaudeTools repository copied"
|
||||
} else {
|
||||
Write-Err "ClaudeTools directory not found at $Script:ClaudeToolsRoot"
|
||||
}
|
||||
|
||||
# Copy Claude memory/config
|
||||
Write-Info "Copying Claude configuration and memory..."
|
||||
if (Test-Path $Script:ClaudeConfigDir) {
|
||||
$memSrc = $Script:ClaudeConfigDir
|
||||
& robocopy $memSrc (Join-Path $tempStaging "claude-config") /E /NFL /NDL /NJH /NJS /XD "node_modules" | Out-Null
|
||||
Write-OK "Claude configuration copied"
|
||||
} else {
|
||||
Write-Warn "Claude config directory not found at $Script:ClaudeConfigDir"
|
||||
}
|
||||
|
||||
# Compress
|
||||
Write-Info "Compressing archive to $ArchivePath ..."
|
||||
if (Test-Path $ArchivePath) { Remove-Item $ArchivePath -Force }
|
||||
Compress-Archive -Path "$tempStaging\*" -DestinationPath $ArchivePath -CompressionLevel Optimal
|
||||
Write-OK "Archive created: $ArchivePath"
|
||||
|
||||
# Cleanup staging
|
||||
Remove-Item $tempStaging -Recurse -Force
|
||||
|
||||
$sizeMB = [math]::Round((Get-Item $ArchivePath).Length / 1MB, 1)
|
||||
Write-Host ""
|
||||
Write-OK "Archive complete: $ArchivePath ($sizeMB MB)"
|
||||
Write-Info "Copy this file to external storage before reinstalling Windows."
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 1: Prerequisites
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase1 {
|
||||
Write-Phase 1 "Prerequisites Installation (winget)"
|
||||
|
||||
if (-not (Test-CommandExists "winget")) {
|
||||
Write-Err "winget is not available. Please install App Installer from the Microsoft Store."
|
||||
return
|
||||
}
|
||||
|
||||
Install-WingetPackage "Git.Git" "Git"
|
||||
Install-WingetPackage "OpenJS.NodeJS" "Node.js (Latest LTS)"
|
||||
Install-WingetPackage "Python.Python.3.13" "Python 3.13"
|
||||
Install-WingetPackage "Ollama.Ollama" "Ollama"
|
||||
|
||||
# Refresh PATH so subsequent phases see the new installs
|
||||
Refresh-PathEnv
|
||||
|
||||
# Verify critical tools are now on PATH
|
||||
foreach ($tool in @("git", "node", "python", "ollama")) {
|
||||
if (Test-CommandExists $tool) {
|
||||
$ver = & $tool --version 2>&1 | Select-Object -First 1
|
||||
Write-OK "$tool found: $ver"
|
||||
} else {
|
||||
Write-Warn "$tool not found on PATH after install. You may need to restart your terminal."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 2: Claude Code + Global NPM Packages
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase2 {
|
||||
Write-Phase 2 "Claude Code and Global NPM Packages"
|
||||
|
||||
if (-not (Test-CommandExists "npm")) {
|
||||
Refresh-PathEnv
|
||||
if (-not (Test-CommandExists "npm")) {
|
||||
Write-Err "npm not found. Node.js installation may have failed."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$npmPackages = @(
|
||||
@{ Name = "@anthropic-ai/claude-code"; Cmd = "claude" },
|
||||
@{ Name = "clawhub"; Cmd = "clawhub" },
|
||||
@{ Name = "mcporter"; Cmd = "mcporter" },
|
||||
@{ Name = "openclaw"; Cmd = "openclaw" }
|
||||
)
|
||||
|
||||
foreach ($pkg in $npmPackages) {
|
||||
# Check if already installed globally
|
||||
$installed = npm list -g $($pkg.Name) --depth=0 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $installed -match $pkg.Name) {
|
||||
Write-Skip "$($pkg.Name) is already installed globally"
|
||||
} else {
|
||||
Write-Info "Installing $($pkg.Name) globally..."
|
||||
npm install -g $($pkg.Name) 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "$($pkg.Name) installed"
|
||||
} else {
|
||||
Write-Err "Failed to install $($pkg.Name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 3: Clone or Configure ClaudeTools Repository
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase3 {
|
||||
Write-Phase 3 "ClaudeTools Repository"
|
||||
|
||||
if (-not (Test-CommandExists "git")) {
|
||||
Refresh-PathEnv
|
||||
if (-not (Test-CommandExists "git")) {
|
||||
Write-Err "git not found. Cannot proceed with repository setup."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path (Join-Path $Script:ClaudeToolsRoot ".git")) {
|
||||
Write-Skip "ClaudeTools repository already exists at $Script:ClaudeToolsRoot"
|
||||
Write-Info "Configuring git for self-signed certificate..."
|
||||
Push-Location $Script:ClaudeToolsRoot
|
||||
git config http.sslVerify false 2>&1 | Out-Null
|
||||
Pop-Location
|
||||
Write-OK "Git SSL verification disabled for self-signed Gitea cert"
|
||||
|
||||
# Ensure remote is correct
|
||||
Push-Location $Script:ClaudeToolsRoot
|
||||
$currentRemote = git remote get-url origin 2>$null
|
||||
if ($currentRemote -ne $Script:GiteaRepo) {
|
||||
git remote set-url origin $Script:GiteaRepo 2>&1 | Out-Null
|
||||
Write-OK "Git remote updated to $Script:GiteaRepo"
|
||||
} else {
|
||||
Write-OK "Git remote is correct"
|
||||
}
|
||||
Pop-Location
|
||||
}
|
||||
elseif (Test-Path $Script:ClaudeToolsRoot) {
|
||||
# Directory exists but is not a git repo (restored from archive)
|
||||
Write-Info "Directory exists but is not a git repo. Initializing..."
|
||||
Push-Location $Script:ClaudeToolsRoot
|
||||
git init 2>&1 | Out-Null
|
||||
git remote add origin $Script:GiteaRepo 2>&1 | Out-Null
|
||||
git config http.sslVerify false 2>&1 | Out-Null
|
||||
git fetch origin 2>&1 | Out-Null
|
||||
git checkout -B main origin/main 2>&1 | Out-Null
|
||||
Pop-Location
|
||||
Write-OK "Repository initialized from archive and connected to remote"
|
||||
}
|
||||
else {
|
||||
# Fresh clone
|
||||
Write-Info "Cloning ClaudeTools repository..."
|
||||
# Ensure D:\ exists
|
||||
if (-not (Test-Path "D:\")) {
|
||||
Write-Err "D:\ drive not found. Cannot clone repository."
|
||||
return
|
||||
}
|
||||
git clone -c http.sslVerify=false $Script:GiteaRepo $Script:ClaudeToolsRoot 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Push-Location $Script:ClaudeToolsRoot
|
||||
git config http.sslVerify false 2>&1 | Out-Null
|
||||
Pop-Location
|
||||
Write-OK "Repository cloned successfully"
|
||||
} else {
|
||||
Write-Err "Failed to clone repository"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 4: Python Environment
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase4 {
|
||||
Write-Phase 4 "Python Packages (Global pip install)"
|
||||
|
||||
if (-not (Test-CommandExists "python")) {
|
||||
Refresh-PathEnv
|
||||
if (-not (Test-CommandExists "python")) {
|
||||
Write-Err "python not found. Python installation may have failed."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$pyVersion = python --version 2>&1
|
||||
Write-OK "Python detected: $pyVersion"
|
||||
|
||||
# Full list of required packages with pinned versions
|
||||
$pipPackages = @(
|
||||
"alembic==1.13.1",
|
||||
"annotated-types==0.7.0",
|
||||
"anyio==4.12.1",
|
||||
"argon2-cffi==25.1.0",
|
||||
"argon2-cffi-bindings==25.1.0",
|
||||
"bcrypt==5.0.0",
|
||||
"beautifulsoup4==4.13.5",
|
||||
"certifi==2026.1.4",
|
||||
"cffi==2.0.0",
|
||||
"claude-agent-sdk==0.1.19",
|
||||
"claude-code-sdk==0.0.25",
|
||||
"click==8.3.1",
|
||||
"colorama==0.4.6",
|
||||
"compressed-rtf==1.0.6",
|
||||
"cryptography==46.0.3",
|
||||
"easygui==0.98.3",
|
||||
"extract-msg==0.55.0",
|
||||
"fastapi==0.128.0",
|
||||
"google-api-core==2.30.0",
|
||||
"google-api-python-client==2.192.0",
|
||||
"google-auth==2.49.0",
|
||||
"google-auth-httplib2==0.3.0",
|
||||
"googleapis-common-protos==1.73.0",
|
||||
"greenlet==3.3.0",
|
||||
"httpcore==1.0.9",
|
||||
"httplib2==0.31.2",
|
||||
"httpx==0.28.1",
|
||||
"httpx-sse==0.4.3",
|
||||
"invoke==2.2.1",
|
||||
"jsonschema==4.26.0",
|
||||
"lark==1.3.1",
|
||||
"Mako==1.3.10",
|
||||
"MarkupSafe==3.0.3",
|
||||
"mcp==1.25.0",
|
||||
"msoffcrypto-tool==5.4.2",
|
||||
"numpy==2.4.2",
|
||||
"olefile==0.47",
|
||||
"oletools==0.60.2",
|
||||
"opencv-python==4.13.0.90",
|
||||
"paramiko==4.0.0",
|
||||
"passlib==1.7.4",
|
||||
"pillow==12.1.0",
|
||||
"proto-plus==1.27.1",
|
||||
"protobuf==6.33.5",
|
||||
"pydantic==2.12.5",
|
||||
"pydantic-settings==2.12.0",
|
||||
"PyJWT==2.10.1",
|
||||
"PyMySQL==1.1.0",
|
||||
"PyNaCl==1.6.2",
|
||||
"python-dotenv==1.2.1",
|
||||
"python-multipart==0.0.21",
|
||||
"pywin32==311",
|
||||
"pyzbar==0.1.9",
|
||||
"requests==2.32.5",
|
||||
"RTFDE==0.1.2.2",
|
||||
"SQLAlchemy==2.0.45",
|
||||
"sse-starlette==3.1.2",
|
||||
"starlette==0.50.0",
|
||||
"tzdata==2025.3",
|
||||
"tzlocal==5.3.1",
|
||||
"uritemplate==4.2.0",
|
||||
"uvicorn==0.40.0",
|
||||
"uv==0.9.9",
|
||||
"websockets==15.0.1"
|
||||
)
|
||||
|
||||
# Write a temporary requirements file for batch install
|
||||
$reqFile = Join-Path $env:TEMP "claudetools-requirements.txt"
|
||||
$pipPackages | Out-File -FilePath $reqFile -Encoding UTF8
|
||||
|
||||
Write-Info "Installing $($pipPackages.Count) Python packages (this may take several minutes)..."
|
||||
$pipOutput = python -m pip install --upgrade pip 2>&1
|
||||
$pipOutput = python -m pip install -r $reqFile 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "All Python packages installed successfully"
|
||||
} else {
|
||||
# Check for partial failures
|
||||
$failLines = ($pipOutput | Select-String "ERROR:" | ForEach-Object { $_.Line })
|
||||
if ($failLines) {
|
||||
foreach ($line in $failLines) {
|
||||
Write-Err "pip: $line"
|
||||
}
|
||||
} else {
|
||||
Write-Warn "pip exited with warnings but may have succeeded. Review output above."
|
||||
}
|
||||
}
|
||||
|
||||
Remove-Item $reqFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 5: Ollama Models
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase5 {
|
||||
Write-Phase 5 "Ollama Models"
|
||||
|
||||
if (-not (Test-CommandExists "ollama")) {
|
||||
Refresh-PathEnv
|
||||
if (-not (Test-CommandExists "ollama")) {
|
||||
Write-Err "ollama not found. Ollama installation may have failed."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure Ollama service is running
|
||||
Write-Info "Ensuring Ollama service is running..."
|
||||
$ollamaProcess = Get-Process "ollama" -ErrorAction SilentlyContinue
|
||||
if (-not $ollamaProcess) {
|
||||
Write-Info "Starting Ollama service..."
|
||||
Start-Process "ollama" -ArgumentList "serve" -WindowStyle Hidden
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
|
||||
$models = @(
|
||||
"nomic-embed-text",
|
||||
"llama3.1:8b",
|
||||
"qwen2.5-coder:7b"
|
||||
)
|
||||
|
||||
foreach ($model in $models) {
|
||||
Write-Info "Pulling model: $model (this may take a while)..."
|
||||
$pullOutput = ollama pull $model 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "Model $model pulled successfully"
|
||||
} else {
|
||||
$outputText = $pullOutput -join "`n"
|
||||
if ($outputText -match "up to date") {
|
||||
Write-Skip "Model $model is already up to date"
|
||||
} else {
|
||||
Write-Err "Failed to pull model $model : $outputText"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 6: MCP Server Setup
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase6 {
|
||||
Write-Phase 6 "MCP Server Setup (Ollama Assistant)"
|
||||
|
||||
$ollamaAssistantDir = Join-Path $Script:ClaudeToolsRoot "mcp-servers\ollama-assistant"
|
||||
$venvDir = Join-Path $ollamaAssistantDir "venv"
|
||||
$venvPython = Join-Path $venvDir "Scripts\python.exe"
|
||||
$requirementsFile = Join-Path $ollamaAssistantDir "requirements.txt"
|
||||
|
||||
if (-not (Test-Path $ollamaAssistantDir)) {
|
||||
Write-Err "Ollama assistant directory not found at $ollamaAssistantDir"
|
||||
return
|
||||
}
|
||||
|
||||
# Create venv if it doesn't exist or is broken
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
Write-Info "Creating Python virtual environment..."
|
||||
if (Test-Path $venvDir) { Remove-Item $venvDir -Recurse -Force }
|
||||
python -m venv $venvDir 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "Virtual environment created"
|
||||
} else {
|
||||
Write-Err "Failed to create virtual environment"
|
||||
return
|
||||
}
|
||||
} else {
|
||||
Write-Skip "Virtual environment already exists"
|
||||
}
|
||||
|
||||
# Install requirements
|
||||
Write-Info "Installing MCP server dependencies..."
|
||||
if (Test-Path $requirementsFile) {
|
||||
& $venvPython -m pip install --upgrade pip 2>&1 | Out-Null
|
||||
$installOutput = & $venvPython -m pip install -r $requirementsFile 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "MCP server dependencies installed"
|
||||
} else {
|
||||
Write-Err "Failed to install MCP server dependencies: $installOutput"
|
||||
}
|
||||
} else {
|
||||
# Fallback: install mcp and httpx directly
|
||||
& $venvPython -m pip install --upgrade pip 2>&1 | Out-Null
|
||||
& $venvPython -m pip install "mcp>=0.1.0" "httpx>=0.25.0" 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "MCP server dependencies installed (fallback)"
|
||||
} else {
|
||||
Write-Err "Failed to install MCP server dependencies"
|
||||
}
|
||||
}
|
||||
|
||||
# Verify .mcp.json exists in repo
|
||||
$mcpJson = Join-Path $Script:ClaudeToolsRoot ".mcp.json"
|
||||
if (Test-Path $mcpJson) {
|
||||
Write-OK ".mcp.json configuration found in repository"
|
||||
} else {
|
||||
Write-Warn ".mcp.json not found - MCP servers will not be configured"
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 7: Claude Code Configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase7 {
|
||||
Write-Phase 7 "Claude Code Configuration"
|
||||
|
||||
# Create directories
|
||||
foreach ($dir in @($Script:ClaudeConfigDir, $Script:ClaudeCommandsDir, $Script:MemoryDir)) {
|
||||
if (-not (Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
Write-OK "Created directory: $dir"
|
||||
} else {
|
||||
Write-Skip "Directory exists: $dir"
|
||||
}
|
||||
}
|
||||
|
||||
# Write settings.json
|
||||
$settingsPath = Join-Path $Script:ClaudeConfigDir "settings.json"
|
||||
$settingsContent = @'
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git:*)", "Bash(gh:*)", "Bash(ssh:*)", "Bash(scp:*)", "Bash(rsync:*)",
|
||||
"Bash(wsl:*)", "Bash(wsl.exe:*)", "Bash(cat:*)", "Bash(ls:*)", "Bash(find:*)",
|
||||
"Bash(grep:*)", "Bash(echo:*)", "Bash(chmod:*)", "Bash(chown:*)", "Bash(mkdir:*)",
|
||||
"Bash(rm:*)", "Bash(cp:*)", "Bash(mv:*)", "Bash(curl:*)", "Bash(wget:*)",
|
||||
"Bash(nslookup:*)", "Bash(dig:*)", "Bash(ping:*)", "Bash(python:*)", "Bash(python3:*)",
|
||||
"Bash(node:*)", "Bash(npm:*)", "Bash(npx:*)", "Bash(cargo:*)", "Bash(rustc:*)",
|
||||
"Bash(rustup:*)", "Bash(powershell:*)", "Bash(powershell.exe:*)", "Bash(pwsh:*)",
|
||||
"Bash(which:*)", "Bash(where:*)", "Bash(whoami:*)", "Bash(date:*)", "Bash(head:*)",
|
||||
"Bash(tail:*)", "Bash(less:*)", "Bash(more:*)", "Bash(diff:*)", "Bash(tar:*)",
|
||||
"Bash(unzip:*)", "Bash(zip:*)", "Bash(docker:*)", "Bash(docker-compose:*)",
|
||||
"Bash(systemctl:*)", "Bash(service:*)", "Bash(journalctl:*)", "Bash(apt:*)",
|
||||
"Bash(apt-get:*)", "Bash(brew:*)", "Bash(code:*)", "Bash(make:*)", "Bash(cmake:*)",
|
||||
"Bash(dir:*)", "Bash(wc:*)", "Bash(winget:*)", "Bash(choco:*)", "Bash(ipconfig:*)",
|
||||
"Bash(net:*)", "Bash(perl:*)", "Bash(xxd:*)", "Bash(timeout:*)", "Bash(claude:*)",
|
||||
"Bash(plink:*)", "WebFetch(domain:*)", "Skill(s)",
|
||||
"Read(//c/Users/$env:USERNAME/.claude/**)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
},
|
||||
"statusLine": {
|
||||
"type": "command",
|
||||
"command": "input=$(cat); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); [ -n \"$remaining\" ] && printf \"Context: %.0f%% remaining\" \"$remaining\" || echo \"\""
|
||||
},
|
||||
"skipDangerousModePermissionPrompt": true
|
||||
}
|
||||
'@
|
||||
|
||||
# Replace $env:USERNAME placeholder with actual username
|
||||
$settingsContent = $settingsContent -replace '\$env:USERNAME', $env:USERNAME
|
||||
|
||||
Set-Content -Path $settingsPath -Value $settingsContent -Encoding UTF8 -Force
|
||||
Write-OK "settings.json written to $settingsPath"
|
||||
|
||||
# Copy commands from repo to global
|
||||
$repoCommandsDir = Join-Path $Script:ClaudeToolsRoot ".claude\commands"
|
||||
if (Test-Path $repoCommandsDir) {
|
||||
$commandFiles = Get-ChildItem -Path $repoCommandsDir -File
|
||||
if ($commandFiles.Count -gt 0) {
|
||||
Copy-Item -Path "$repoCommandsDir\*" -Destination $Script:ClaudeCommandsDir -Force
|
||||
Write-OK "Copied $($commandFiles.Count) command files to $Script:ClaudeCommandsDir"
|
||||
} else {
|
||||
Write-Warn "No command files found in $repoCommandsDir"
|
||||
}
|
||||
} else {
|
||||
Write-Warn "Repo commands directory not found at $repoCommandsDir (run after Phase 3)"
|
||||
}
|
||||
|
||||
# Check for archived memory to restore
|
||||
Write-Info "Checking for memory directory..."
|
||||
if (Test-Path (Join-Path $Script:MemoryDir "MEMORY.md")) {
|
||||
Write-Skip "Memory files already present"
|
||||
} else {
|
||||
Write-Warn "Memory directory is empty. If you have an archive, manually restore:"
|
||||
Write-Warn " Copy contents to $Script:MemoryDir"
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 8: GrepAI Setup
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase8 {
|
||||
Write-Phase 8 "GrepAI Setup"
|
||||
|
||||
$grepaiExe = Join-Path $Script:ClaudeToolsRoot "grepai.exe"
|
||||
|
||||
if (-not (Test-Path $grepaiExe)) {
|
||||
Write-Err "grepai.exe not found at $grepaiExe"
|
||||
Write-Warn "GrepAI binary should be in the repository. Ensure Phase 3 completed successfully."
|
||||
return
|
||||
}
|
||||
|
||||
Write-OK "grepai.exe found at $grepaiExe"
|
||||
|
||||
# Check if already initialized
|
||||
$grepaiConfig = Join-Path $Script:ClaudeToolsRoot ".grepai"
|
||||
if (Test-Path $grepaiConfig) {
|
||||
Write-Skip "GrepAI appears to already be initialized (.grepai directory exists)"
|
||||
} else {
|
||||
Write-Info "Initializing GrepAI..."
|
||||
Write-Info "This may prompt for configuration. Accept defaults for Ollama + nomic-embed-text."
|
||||
Push-Location $Script:ClaudeToolsRoot
|
||||
& $grepaiExe init 2>&1
|
||||
Pop-Location
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-OK "GrepAI initialized"
|
||||
} else {
|
||||
Write-Warn "GrepAI init may require manual interaction. Run manually:"
|
||||
Write-Warn " cd $Script:ClaudeToolsRoot && .\grepai.exe init"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 9: Verification
|
||||
# ---------------------------------------------------------------------------
|
||||
function Invoke-Phase9 {
|
||||
Write-Phase 9 "Verification"
|
||||
|
||||
$checks = @(
|
||||
@{ Name = "Git"; Cmd = "git --version" },
|
||||
@{ Name = "Node.js"; Cmd = "node --version" },
|
||||
@{ Name = "npm"; Cmd = "npm --version" },
|
||||
@{ Name = "Python"; Cmd = "python --version" },
|
||||
@{ Name = "pip"; Cmd = "python -m pip --version" },
|
||||
@{ Name = "Ollama"; Cmd = "ollama --version" },
|
||||
@{ Name = "Claude Code CLI"; Cmd = "claude --version" }
|
||||
)
|
||||
|
||||
Refresh-PathEnv
|
||||
|
||||
foreach ($check in $checks) {
|
||||
try {
|
||||
$result = Invoke-Expression $check.Cmd 2>&1 | Select-Object -First 1
|
||||
if ($LASTEXITCODE -eq 0 -or $result) {
|
||||
Write-OK "$($check.Name): $result"
|
||||
} else {
|
||||
Write-Err "$($check.Name): not found or not working"
|
||||
}
|
||||
} catch {
|
||||
Write-Err "$($check.Name): $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Check directories
|
||||
Write-Host ""
|
||||
Write-Info "Directory checks:"
|
||||
$dirs = @(
|
||||
$Script:ClaudeToolsRoot,
|
||||
$Script:ClaudeConfigDir,
|
||||
$Script:ClaudeCommandsDir,
|
||||
(Join-Path $Script:ClaudeToolsRoot "mcp-servers\ollama-assistant\venv")
|
||||
)
|
||||
foreach ($dir in $dirs) {
|
||||
if (Test-Path $dir) {
|
||||
Write-OK "EXISTS: $dir"
|
||||
} else {
|
||||
Write-Err "MISSING: $dir"
|
||||
}
|
||||
}
|
||||
|
||||
# Check key files
|
||||
Write-Host ""
|
||||
Write-Info "File checks:"
|
||||
$files = @(
|
||||
(Join-Path $Script:ClaudeConfigDir "settings.json"),
|
||||
(Join-Path $Script:ClaudeToolsRoot ".mcp.json"),
|
||||
(Join-Path $Script:ClaudeToolsRoot "grepai.exe")
|
||||
)
|
||||
foreach ($file in $files) {
|
||||
if (Test-Path $file) {
|
||||
Write-OK "EXISTS: $file"
|
||||
} else {
|
||||
Write-Err "MISSING: $file"
|
||||
}
|
||||
}
|
||||
|
||||
# Check Ollama models
|
||||
Write-Host ""
|
||||
Write-Info "Ollama model checks:"
|
||||
if (Test-CommandExists "ollama") {
|
||||
$modelList = ollama list 2>&1
|
||||
foreach ($model in @("nomic-embed-text", "llama3.1:8b", "qwen2.5-coder:7b")) {
|
||||
if ($modelList -match [regex]::Escape($model)) {
|
||||
Write-OK "Model loaded: $model"
|
||||
} else {
|
||||
Write-Warn "Model not found: $model"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Warn "Cannot check Ollama models - ollama not on PATH"
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main Execution
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " ClaudeTools Bootstrap / Reinstall Script" -ForegroundColor Cyan
|
||||
Write-Host " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Archive mode
|
||||
if ($Archive) {
|
||||
Invoke-Archive
|
||||
return
|
||||
}
|
||||
|
||||
# Check for admin
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
Write-Err "This script requires administrator privileges for winget installations."
|
||||
Write-Err "Re-run from an elevated PowerShell prompt."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Execute phases
|
||||
$phases = @(
|
||||
@{ Num = 1; Func = { Invoke-Phase1 } },
|
||||
@{ Num = 2; Func = { Invoke-Phase2 } },
|
||||
@{ Num = 3; Func = { Invoke-Phase3 } },
|
||||
@{ Num = 4; Func = { Invoke-Phase4 } },
|
||||
@{ Num = 5; Func = { Invoke-Phase5 } },
|
||||
@{ Num = 6; Func = { Invoke-Phase6 } },
|
||||
@{ Num = 7; Func = { Invoke-Phase7 } },
|
||||
@{ Num = 8; Func = { Invoke-Phase8 } },
|
||||
@{ Num = 9; Func = { Invoke-Phase9 } }
|
||||
)
|
||||
|
||||
foreach ($phase in $phases) {
|
||||
if (ShouldRunPhase $phase.Num) {
|
||||
try {
|
||||
& $phase.Func
|
||||
} catch {
|
||||
Write-Err "Phase $($phase.Num) failed with exception: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
Write-Skip "Phase $($phase.Num) skipped (by request)"
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host " Bootstrap Summary" -ForegroundColor Cyan
|
||||
Write-Host "================================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if ($Script:Successes.Count -gt 0) {
|
||||
Write-Host " Successes: $($Script:Successes.Count)" -ForegroundColor Green
|
||||
}
|
||||
if ($Script:Skipped.Count -gt 0) {
|
||||
Write-Host " Skipped: $($Script:Skipped.Count)" -ForegroundColor Cyan
|
||||
}
|
||||
if ($Script:Warnings.Count -gt 0) {
|
||||
Write-Host " Warnings: $($Script:Warnings.Count)" -ForegroundColor Yellow
|
||||
foreach ($w in $Script:Warnings) {
|
||||
Write-Host " - $w" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
if ($Script:Errors.Count -gt 0) {
|
||||
Write-Host " Errors: $($Script:Errors.Count)" -ForegroundColor Red
|
||||
foreach ($e in $Script:Errors) {
|
||||
Write-Host " - $e" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " Manual steps remaining:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Run 'claude' and authenticate with Anthropic API key" -ForegroundColor Yellow
|
||||
Write-Host " 2. Install Claude-in-Chrome browser extension" -ForegroundColor Yellow
|
||||
Write-Host " 3. Set GITHUB_PERSONAL_ACCESS_TOKEN in .mcp.json" -ForegroundColor Yellow
|
||||
Write-Host " 4. Restore memory files if not already present" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
if ($Script:Errors.Count -eq 0) {
|
||||
Write-Host " [OK] Bootstrap completed successfully!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [WARNING] Bootstrap completed with $($Script:Errors.Count) error(s). Review above." -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ""
|
||||
@@ -0,0 +1,275 @@
|
||||
# Session Log: 2026-03-17 - Neptune Exchange Server Cleanup & Mailprotector Configuration
|
||||
|
||||
## Session Summary
|
||||
|
||||
Comprehensive Exchange Server maintenance on Neptune (mail.acghosting.com / 67.206.163.124). Cleaned up stale accepted domains and mailboxes, fixed outbound mail routing through Mailprotector (emailservice.io) smarthosts, created inbound restriction rules, tightened DNS security records, and purged ~20K spam messages that bypassed the filter.
|
||||
|
||||
### Key Accomplishments
|
||||
|
||||
1. **Accepted Domain Cleanup** - Removed 9 stale domains, disabled 23 mailboxes total (12 on removed domains, 11 orphans, 1 leftover)
|
||||
2. **Send Connector Fix** - Moved all send connectors from dead MAIL server to NEPTUNE
|
||||
3. **SBR Routing Restored** - Added devconllc.com and littlehearts domains to Mailprotector SBR agent config
|
||||
4. **Transport Rule for Inbound Restriction** - Created rule blocking direct delivery (bypassing Mailprotector) for devcon and littlehearts domains
|
||||
5. **DNS Hardening** - Added secondary MX records and tightened DMARC to p=reject for devconllc.com
|
||||
6. **Spam Purge** - Soft-deleted 20,473 spam messages from littlehearts/airandspace mailboxes that bypassed filter
|
||||
|
||||
### Key Decisions
|
||||
- MAIL server no longer exists - all routing moved to NEPTUNE
|
||||
- airandspaceacademy.com is the old domain name for littleheartslittlehands (school renamed)
|
||||
- simplehost.email kept as default accepted domain (was originally slated for removal)
|
||||
- littleheartslittlehands.com and acg.local kept as safe domains
|
||||
- Transport rules using RouteMessageOutboundConnector are NOT supported on-prem Exchange 2016 (Multi-tenant only error)
|
||||
- SBR routing uses two transport agents: messageconcept ExSBR + Microsoft Exchange SBR with config files in agents\Custom folder
|
||||
|
||||
### Problems Encountered
|
||||
1. **Transport rules crashed transport service** - RouteMessageOutboundConnector action throws "Multi-tenant deployments supported only" on standalone Exchange 2016. All messages got poisoned. Fixed by removing rules and using SBR agent config instead.
|
||||
2. **Pickup/Replay directory messages poisoned** - Test messages injected via pickup/replay directories were marked as poison. Used real mailbox send for testing instead.
|
||||
3. **Search-Mailbox can't move within same mailbox** - "source mailbox cannot be used as the target mailbox." Used -DeleteContent (soft delete to Recoverable Items) instead.
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure Details
|
||||
|
||||
### Exchange Servers
|
||||
- **NEPTUNE** (primary, this server): Exchange 2016 Standard Evaluation, Build 15.1.2507.17
|
||||
- **MAIL**: Exchange 2016 Enterprise, Build 15.1.2507.18 - **NO LONGER EXISTS**
|
||||
- Both registered as Mailbox role servers
|
||||
|
||||
### Server Details
|
||||
- **Hostname:** neptune.acghosting.com / mail.acghosting.com
|
||||
- **External IP:** 67.206.163.124
|
||||
- **Internal IP:** 172.16.3.11
|
||||
- **Domain:** acg.local
|
||||
- **Let's Encrypt Cert:** CN=mail.acghosting.com, SANs: autodiscover.acghosting.com, autodiscover.amtransit.com, mail.amtransit.com, mail.devconllc.com, mail.littleheartslittlehands.org, mail.packetdial.com, mail.rieussetcorp.com, mail.tucsongoldencorral.com
|
||||
- **Cert Expiry:** 2026-05-31
|
||||
|
||||
### DKIM Signer
|
||||
- **Agent:** Exchange DkimSigner (C:\Program Files\Exchange DkimSigner\ExchangeDkimSigner.dll)
|
||||
- **Algorithm:** RSA-SHA256, Simple/Simple canonicalization
|
||||
- **Configured Domains:**
|
||||
- amtransit.com (selector: s1)
|
||||
- littleheartslittlehands.org (selector: default)
|
||||
- tucsongoldencorral.com (selector: dkim)
|
||||
- devconllc.com (selector: default)
|
||||
- jparkinsonaz.com (selector: s1)
|
||||
- rieussetcorp.com (selector: s1)
|
||||
- **Keys:** C:\Program Files\Exchange DkimSigner\keys\
|
||||
|
||||
### SBR Agent Configuration
|
||||
- **Config Path:** C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\agents\Custom\
|
||||
- **Files:**
|
||||
- `Microsoft.Exchange.SBR.dll` - SBR routing agent
|
||||
- `Microsoft.Exchange.SBR.InternalDomains.config` - Domain list
|
||||
- `Microsoft.Exchange.SBR.OverrideSettings.config` - Domain-to-SBR mapping
|
||||
- `Microsoft.Exchange.SBR.IgnoreAuthAs.config` - (empty)
|
||||
- **Also installed:** messageconcept ExSBR (C:\Program Files\messageconcept\ExSBR\SenderBasedRouting.dll)
|
||||
|
||||
### SBR Config (OverrideSettings.config) - Current State
|
||||
```
|
||||
amtransit.com;amtransit.sbr
|
||||
littleheartslittlehands.org;littleheartslittlehands.sbr
|
||||
tucsonsafety.com;tucsonsafety.sbr
|
||||
rieussetcorp.com;rieussetcorp.sbr
|
||||
devconllc.com;devconllc.sbr
|
||||
littleheartslittlehands.com;littleheartslittlehands.sbr
|
||||
airandspaceacademy.com;airandspaceacademy.sbr
|
||||
```
|
||||
|
||||
### SBR Config (InternalDomains.config) - Current State
|
||||
```
|
||||
amtransit.com
|
||||
littleheartslittlehands.org
|
||||
tucsonsafety.com
|
||||
rieussetcorp.com
|
||||
devconllc.com
|
||||
littleheartslittlehands.com
|
||||
airandspaceacademy.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mailprotector (emailservice.io) IPs
|
||||
|
||||
### Transport Servers (US)
|
||||
- 52.0.70.91
|
||||
- 52.0.74.211
|
||||
- 52.0.31.31
|
||||
|
||||
### Inbound Gateway Servers
|
||||
- 52.0.43.153, 52.0.90.6, 52.0.156.43, 52.0.161.190
|
||||
- 52.1.76.196, 52.1.130.188, 52.1.217.73
|
||||
- 54.85.114.151, 54.152.152.44, 54.80.77.105
|
||||
- 52.204.186.160, 3.213.159.102, 23.20.39.50
|
||||
- 18.214.219.227, 34.233.23.45
|
||||
|
||||
### LDAP/AD Sync
|
||||
- 54.152.160.142, 54.152.160.187
|
||||
|
||||
### Europe Transport
|
||||
- 54.229.38.56, 54.229.197.37, 54.229.198.191
|
||||
|
||||
### Asia Pacific Transport
|
||||
- 54.66.143.79, 54.66.158.252, 54.66.239.122
|
||||
|
||||
---
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Accepted Domains Removed (9)
|
||||
botapro.com, capacitance.rocks, cycloneinspiredproducts.com, gurushow.com, heieck.org, rondieyancey.com, royalweedcontrol.com, sstargroup.com, thisisnotmy.email
|
||||
|
||||
### 2. Mailboxes Disabled (24 total)
|
||||
**On removed domains (12):** kurt/brit/christine/mailer/orders/payments@botapro.com, info@cycloneinspiredproducts.com (acg.local primary), sheila/jjh@heieck.org, sales/admin@royalweedcontrol.com, crf@sstargroup.com
|
||||
|
||||
**Orphan domains (11):** rondie@lamaddux.com, social@erinhelm.com, 8231/skeener/skeener2/y226/bt/walid@tedards.net, info@retiredpaws.org, info/katta@emoxpress.com
|
||||
|
||||
**Leftover (1):** cyclone@acg.local
|
||||
|
||||
### 3. Remaining Accepted Domains (19)
|
||||
acg.local, acghosting.com (ExternalRelay), airandspaceacademy.com, amtransit.com, devconllc.com, farwestwell.com, goldenchoicecatering.com, jparkinsonaz.com, justsimplysmart.com, lifelonglearningacademy.com, littleheartslittlehands.com, littleheartslittlehands.org, outaboundssports.com, packetdial.com, patriotinternalmedicine.com, rieussetcorp.com, simplehost.email (Default), tucsongoldencorral.com, tucsonsafety.com
|
||||
|
||||
### 4. Send Connectors (Final State)
|
||||
All sourced from NEPTUNE:
|
||||
|
||||
| Connector | Address Space | Smart Host |
|
||||
|-----------|--------------|------------|
|
||||
| Outbound.DEVCON | devconllc.sbr | devconllc-com.outbound.emailservice.io |
|
||||
| Outbound.LittleHearts | littleheartslittlehands.sbr, airandspaceacademy.sbr | littleheartslittlehands-org.outbound.emailservice.io |
|
||||
| Outbound.Patriot | patriotinternalmedicine.sbr | patriotinternalmedicine-com.outbound.emailservice.io |
|
||||
| Outbound.Farwestwell | farwestwell.sbr | farwestwell-com.outbound.emailservice.io |
|
||||
| Outbound.TGC | tucsongoldencorral.sbr | tucsongoldencorral-com.outbound.emailservice.io |
|
||||
| Outbound.LLA | lifelonglearningacademy.sbr | lifelonglearningacademy-com.outbound.emailservice.io |
|
||||
| Outbound.AMT | amtransit.sbr | amtransit-com.outbound.emailservice.io |
|
||||
| Outbound.TucsonSafety | tucsonsafety.sbr | tucsonsafety-com.outbound.emailservice.io |
|
||||
| Outbound.Sorensen | rieussetcorp.sbr | rieussetcorp-com.outbound.emailservice.io |
|
||||
| Horseshoe Outbound | horseshoemgt.sbr | horseshoemgt-com.outbound.emailservice.io |
|
||||
| Outbound.Avoid Filter | Q.com | webhost.acghosting.com |
|
||||
| Other | * (catch-all) | DNS routing |
|
||||
|
||||
**Removed:** devconllc.com_ExSBR (duplicate), AOL/YAHOO (disabled)
|
||||
|
||||
### 5. Transport Rules (Final State)
|
||||
| Rule | Priority | Description |
|
||||
|------|----------|-------------|
|
||||
| Restrict Inbound - Devcon and LittleHearts | 0 | Reject 5.7.1 if recipient is devconllc.com/littleheartslittlehands.org/.com/airandspaceacademy.com AND sender is external AND source IP not in Mailprotector list |
|
||||
| Webhost Spam | 1 | Delete messages from webhost.acghosting.com or fabry |
|
||||
| Bardach BCC | 2 | BCC rule for Bardach |
|
||||
|
||||
### 6. DNS Changes (devconllc.com via IX WHM API)
|
||||
- **Added:** MX 20 devconllc-com.inbound.emailservice.cc
|
||||
- **Added:** MX 30 devconllc-com.inbound.emailservice.co
|
||||
- **Updated:** DMARC from `p=none;sp=none` to `p=reject;sp=reject;fo=1`
|
||||
|
||||
### 7. Spam Purge Results
|
||||
20,473 messages soft-deleted (Recoverable Items, 14 days retention):
|
||||
- rklem@littleheartslittlehands.org: 7,798
|
||||
- marylou@littleheartslittlehands.org: 12,594
|
||||
- sbranch@airandspaceacademy.com: 5
|
||||
- ajoseph@airandspaceacademy.com: 35
|
||||
- mrocha@airandspaceacademy.com: 33
|
||||
- tstevens@airandspaceacademy.com: 4
|
||||
- email@airandspaceacademy.com: 4
|
||||
|
||||
---
|
||||
|
||||
## Credentials Used
|
||||
|
||||
### IX Server (WHM API)
|
||||
- **Host:** ix.azcomputerguru.com:2087
|
||||
- **User:** root
|
||||
- **Password:** Gptf*77ttb!@#!@#
|
||||
- **API:** JSON API via curl with basic auth
|
||||
- **Used for:** DNS zone queries and edits (dumpzone, addzonerecord, editzonerecord)
|
||||
|
||||
### Neptune Exchange
|
||||
- **Access:** Local PowerShell with Exchange Management Shell snapin
|
||||
- **Snapin:** Microsoft.Exchange.Management.PowerShell.SnapIn
|
||||
- **No credentials needed** (running as administrator.ACG)
|
||||
|
||||
---
|
||||
|
||||
## Domain Status Summary
|
||||
|
||||
### devconllc.com - FULLY CONFIGURED
|
||||
- DNS: IX (ns1/ns2.acghosting.com)
|
||||
- MX: 3x Mailprotector inbound [OK]
|
||||
- SPF: Includes spf.us.emailservice.io [OK]
|
||||
- DKIM: default selector, signing on Exchange [OK]
|
||||
- DMARC: p=reject [OK]
|
||||
- Outbound: SBR -> devconllc-com.outbound.emailservice.io [OK]
|
||||
- Inbound restriction: Transport rule [OK]
|
||||
|
||||
### littleheartslittlehands.org - FULLY CONFIGURED
|
||||
- DNS: IX (ns1/ns2.acghosting.com)
|
||||
- MX: 3x Mailprotector inbound [OK]
|
||||
- SPF: Includes spf.us.emailservice.io [OK]
|
||||
- DKIM: default selector, signing on Exchange [OK]
|
||||
- DMARC: p=none (could tighten)
|
||||
- Outbound: SBR -> littleheartslittlehands-org.outbound.emailservice.io [OK]
|
||||
- Inbound restriction: Transport rule [OK]
|
||||
|
||||
### airandspaceacademy.com - NEEDS DNS FIX
|
||||
- DNS: GoDaddy (ns71/ns72.domaincontrol.com)
|
||||
- MX: **STILL POINTS TO mail.acghosting.com (DIRECT - NO FILTER)**
|
||||
- Outbound: SBR -> airandspaceacademy.sbr connector [OK]
|
||||
- Inbound restriction: Transport rule now BLOCKING direct delivery
|
||||
- **ACTION NEEDED:** Change MX on GoDaddy to airandspaceacademy-com.inbound.emailservice.io (if provisioned in Mailprotector)
|
||||
|
||||
### littleheartslittlehands.com - PARTIAL
|
||||
- DNS: Cloudflare (kristina/nile.ns.cloudflare.com)
|
||||
- MX: Points to cbsolt.net (NOT Mailprotector)
|
||||
- Outbound: SBR configured [OK]
|
||||
- **ACTION NEEDED:** Change MX on Cloudflare to Mailprotector
|
||||
|
||||
---
|
||||
|
||||
## Pending/Incomplete Tasks
|
||||
|
||||
1. **airandspaceacademy.com MX** - Needs changing from mail.acghosting.com to Mailprotector inbound on GoDaddy DNS. Currently being REJECTED by the new transport rule.
|
||||
2. **littleheartslittlehands.com MX** - Points to cbsolt.net on Cloudflare, needs updating to Mailprotector.
|
||||
3. **littleheartslittlehands.org DMARC** - Currently p=none, should be tightened to p=reject like devcon.
|
||||
4. **Missing SBR domains** - farwestwell, patriotinternalmedicine, tucsongoldencorral, goldenchoicecatering, lifelonglearningacademy not in SBR config files yet (they have send connectors but SBR agent won't route them).
|
||||
5. **Transport cert expiring** - Thumbprint 5C202EE2700E34A121642FDA07190ABE907D6EAD expires 2026-05-31.
|
||||
6. **Retry queues** - ~40 empty retry queues from flushed spam still visible (will auto-clean).
|
||||
7. **MAIL server removal from AD/Exchange** - Dead server still registered. Should be formally decommissioned.
|
||||
8. **Horseshoe Management** - Has SBR send connector but domain not in SBR config and no accepted domain. Status unknown.
|
||||
9. **5 outdated WordPress sites on IX** - Security risk (from previous IX cleanup session).
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
### Exchange PowerShell Quick Reference
|
||||
```powershell
|
||||
# Load snapin
|
||||
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
|
||||
|
||||
# SBR config files
|
||||
C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\agents\Custom\Microsoft.Exchange.SBR.OverrideSettings.config
|
||||
C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\agents\Custom\Microsoft.Exchange.SBR.InternalDomains.config
|
||||
|
||||
# DKIM config
|
||||
C:\Program Files\Exchange DkimSigner\settings.xml
|
||||
C:\Program Files\Exchange DkimSigner\keys\
|
||||
|
||||
# Frontend protocol logs (contains real source IPs)
|
||||
C:\Program Files\Microsoft\Exchange Server\V15\TransportRoles\Logs\FrontEnd\ProtocolLog\SmtpReceive\
|
||||
|
||||
# Restart transport after SBR config changes
|
||||
Restart-Service MSExchangeTransport -Force
|
||||
```
|
||||
|
||||
### WHM API (IX Server)
|
||||
```bash
|
||||
# Dump zone
|
||||
curl -sk "https://ix.azcomputerguru.com:2087/json-api/dumpzone?domain=DOMAIN" -u "root:PASSWORD"
|
||||
|
||||
# Add record
|
||||
curl -sk "https://ix.azcomputerguru.com:2087/json-api/addzonerecord?domain=DOMAIN&type=TYPE&..." -u "root:PASSWORD"
|
||||
|
||||
# Edit record (need Line number from dumpzone)
|
||||
curl -sk "https://ix.azcomputerguru.com:2087/json-api/editzonerecord?domain=DOMAIN&Line=N&..." -u "root:PASSWORD"
|
||||
|
||||
# Find cPanel user for domain
|
||||
curl -sk "https://ix.azcomputerguru.com:2087/json-api/listaccts?searchtype=domain&search=DOMAIN" -u "root:PASSWORD"
|
||||
```
|
||||
Reference in New Issue
Block a user