From 6d3582d5dce37eb7318318682dc30c8204cc29d0 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Tue, 17 Feb 2026 18:17:45 -0700 Subject: [PATCH] sync: Auto-sync from ACG-M-L5090 at 2026-02-17 Synced files: - SolverBot project: wired up tool execution pipeline (ToolCallRecord, complete_with_tools, Coordinator tool_registry, Anthropic/Ollama message conversion fixes, native tool registration) - Dataforth DOS sync script Machine: ACG-M-L5090 Timestamp: 2026-02-17 18:17:29 Co-Authored-By: Claude Opus 4.6 --- projects/dataforth-dos/Sync-FromNAS.ps1 | 460 ++++++++++++++++++++++++ projects/solverbot | 1 + 2 files changed, 461 insertions(+) create mode 100644 projects/dataforth-dos/Sync-FromNAS.ps1 create mode 160000 projects/solverbot diff --git a/projects/dataforth-dos/Sync-FromNAS.ps1 b/projects/dataforth-dos/Sync-FromNAS.ps1 new file mode 100644 index 0000000..63abcbd --- /dev/null +++ b/projects/dataforth-dos/Sync-FromNAS.ps1 @@ -0,0 +1,460 @@ +# Sync-AD2-NAS.ps1 (formerly Sync-FromNAS.ps1) +# Bidirectional sync between AD2 and NAS (D2TESTNAS) +# +# PULL (NAS -> AD2): Test results (LOGS/*.DAT, Reports/*.TXT) -> Database import +# PUSH (AD2 -> NAS): Software updates (ProdSW/*, TODO.BAT) -> DOS machines +# +# Run: powershell -ExecutionPolicy Bypass -File C:\Shares\test\scripts\Sync-FromNAS.ps1 +# Scheduled: Every 15 minutes via Windows Task Scheduler + +param( + [switch]$DryRun, # Show what would be done without doing it + [switch]$Verbose, # Extra output + [int]$MaxAgeMinutes = 1440 # Default: files from last 24 hours (was 60 min, too aggressive) +) + +# ============================================================================ +# Configuration +# ============================================================================ +$NAS_IP = "192.168.0.9" +$NAS_USER = "root" +$NAS_PASSWORD = "Paper123!@#-nas" +$NAS_HOSTKEY = "SHA256:5CVIPlqjLPxO8n48PKLAP99nE6XkEBAjTkaYmJAeOdA" +$NAS_DATA_PATH = "/data/test" + +$AD2_TEST_PATH = "C:\Shares\test" +$AD2_HISTLOGS_PATH = "C:\Shares\test\Ate\HISTLOGS" + +$SSH = "C:\Program Files\OpenSSH\ssh.exe" +$SCP = "C:\Program Files\OpenSSH\scp.exe" +$SSH_KEY = "C:\Users\sysadmin\.ssh\id_ed25519" + +$LOG_FILE = "C:\Shares\test\scripts\sync-from-nas.log" +$STATUS_FILE = "C:\Shares\test\_SYNC_STATUS.txt" + +$LOG_TYPES = @("5BLOG", "7BLOG", "8BLOG", "DSCLOG", "SCTLOG", "VASLOG", "PWRLOG", "HVLOG") + +# Database import configuration +$IMPORT_SCRIPT = "C:\Shares\testdatadb\database\import.js" +$NODE_PATH = "node" + +# ============================================================================ +# Functions +# ============================================================================ + +function Write-Log { + param([string]$Message) + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logLine = "$timestamp : $Message" + Add-Content -Path $LOG_FILE -Value $logLine + if ($Verbose) { Write-Host $logLine } +} + +function Invoke-NASCommand { + param([string]$Command) + $result = & $SSH -i $SSH_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${NAS_USER}@${NAS_IP}" $Command 2>&1 + return $result +} + +function Copy-FromNAS { + param( + [string]$RemotePath, + [string]$LocalPath + ) + + # Ensure local directory exists + $localDir = Split-Path -Parent $LocalPath + if (-not (Test-Path $localDir)) { + New-Item -ItemType Directory -Path $localDir -Force | Out-Null + } + + # FIX: Use -i for key auth, remove embedded quotes around remote path + # DOS 8.3 filenames have no spaces - embedded quotes caused "ambiguous target" + $result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "${NAS_USER}@${NAS_IP}:${RemotePath}" "$LocalPath" 2>&1 + if ($LASTEXITCODE -ne 0) { + $errorMsg = $result | Out-String + Write-Log " SCP PULL ERROR (exit $LASTEXITCODE): $errorMsg" + } + return $LASTEXITCODE -eq 0 +} + +function Remove-FromNAS { + param([string]$RemotePath) + Invoke-NASCommand "rm -f '$RemotePath'" | Out-Null +} + +function Copy-ToNAS { + param( + [string]$LocalPath, + [string]$RemotePath + ) + + # Ensure remote directory exists via SSH mkdir -p + $remoteDir = ($RemotePath -replace '[^/]+$', '').TrimEnd('/') + Invoke-NASCommand "mkdir -p '$remoteDir'" | Out-Null + + # FIX: Use -i for key auth, remove embedded quotes around remote path + # DOS 8.3 filenames have no spaces - embedded quotes caused "ambiguous target" + $result = & $SCP -O -i $SSH_KEY -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile="C:\Shares\test\scripts\.ssh\known_hosts" "$LocalPath" "${NAS_USER}@${NAS_IP}:${RemotePath}" 2>&1 + if ($LASTEXITCODE -ne 0) { + $errorMsg = $result | Out-String + Write-Log " SCP PUSH ERROR (exit $LASTEXITCODE): $errorMsg" + } + return $LASTEXITCODE -eq 0 +} + +function Get-FileHash256 { + param([string]$FilePath) + if (Test-Path $FilePath) { + return (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash + } + return $null +} + +function Import-ToDatabase { + param([string[]]$FilePaths) + + if ($FilePaths.Count -eq 0) { return } + + Write-Log "Importing $($FilePaths.Count) file(s) to database..." + + # Build argument list + $importArgs = @("$IMPORT_SCRIPT", "--file") + $FilePaths + + try { + $output = & $NODE_PATH $importArgs 2>&1 + foreach ($line in $output) { + Write-Log " [DB] $line" + } + Write-Log "Database import complete" + } catch { + Write-Log "ERROR: Database import failed: $_" + } +} + +# ============================================================================ +# Main Script +# ============================================================================ + +Write-Log "==========================================" +Write-Log "Starting sync from NAS" +Write-Log "Max age: $MaxAgeMinutes minutes" +if ($DryRun) { Write-Log "DRY RUN - no changes will be made" } + +$errorCount = 0 +$syncedFiles = 0 +$skippedFiles = 0 +$syncedDatFiles = @() # Track DAT files for database import + +# Find all DAT files on NAS modified within the time window +Write-Log "Finding DAT files on NAS..." +$findCommand = "find $NAS_DATA_PATH/TS-*/LOGS -name '*.DAT' -type f -mmin -$MaxAgeMinutes 2>/dev/null" +$datFiles = Invoke-NASCommand $findCommand + +if (-not $datFiles -or $datFiles.Count -eq 0) { + Write-Log "No new DAT files found on NAS" +} else { + Write-Log "Found $($datFiles.Count) DAT file(s) to process" + + foreach ($remoteFile in $datFiles) { + $remoteFile = $remoteFile.Trim() + if ([string]::IsNullOrWhiteSpace($remoteFile)) { continue } + + # Parse the path: /data/test/TS-XX/LOGS/7BLOG/file.DAT + if ($remoteFile -match "/data/test/(TS-[^/]+)/LOGS/([^/]+)/(.+\.DAT)$") { + $station = $Matches[1] + $logType = $Matches[2] + $fileName = $Matches[3] + + Write-Log "Processing: $station/$logType/$fileName" + + # Destination 1: Per-station folder (preserves structure) + $stationDest = Join-Path $AD2_TEST_PATH "$station\LOGS\$logType\$fileName" + + # Destination 2: Aggregated HISTLOGS folder + $histlogsDest = Join-Path $AD2_HISTLOGS_PATH "$logType\$fileName" + + if ($DryRun) { + Write-Log " [DRY RUN] Would copy to: $stationDest" + $syncedFiles++ + } else { + # Copy to station folder only (skip HISTLOGS to avoid duplicates) + $success1 = Copy-FromNAS -RemotePath $remoteFile -LocalPath $stationDest + + if ($success1) { + Write-Log " Copied to station folder" + + # Remove from NAS after successful sync + Remove-FromNAS -RemotePath $remoteFile + Write-Log " Removed from NAS" + + # Track for database import + $syncedDatFiles += $stationDest + + $syncedFiles++ + } else { + Write-Log " ERROR: Failed to copy from NAS" + $errorCount++ + } + } + } else { + Write-Log " Skipping (unexpected path format): $remoteFile" + $skippedFiles++ + } + } +} + +# Find and sync TXT report files +Write-Log "Finding TXT reports on NAS..." +$findReportsCommand = "find $NAS_DATA_PATH/TS-*/Reports -name '*.TXT' -type f -mmin -$MaxAgeMinutes 2>/dev/null" +$txtFiles = Invoke-NASCommand $findReportsCommand + +if ($txtFiles -and $txtFiles.Count -gt 0) { + Write-Log "Found $($txtFiles.Count) TXT report(s) to process" + + foreach ($remoteFile in $txtFiles) { + $remoteFile = $remoteFile.Trim() + if ([string]::IsNullOrWhiteSpace($remoteFile)) { continue } + + if ($remoteFile -match "/data/test/(TS-[^/]+)/Reports/(.+\.TXT)$") { + $station = $Matches[1] + $fileName = $Matches[2] + + Write-Log "Processing report: $station/$fileName" + + # Destination: Per-station Reports folder + $reportDest = Join-Path $AD2_TEST_PATH "$station\Reports\$fileName" + + if ($DryRun) { + Write-Log " [DRY RUN] Would copy to: $reportDest" + $syncedFiles++ + } else { + $success = Copy-FromNAS -RemotePath $remoteFile -LocalPath $reportDest + + if ($success) { + Write-Log " Copied report" + Remove-FromNAS -RemotePath $remoteFile + Write-Log " Removed from NAS" + $syncedFiles++ + } else { + Write-Log " ERROR: Failed to copy report" + $errorCount++ + } + } + } + } +} + +# ============================================================================ +# Import synced DAT files to database +# ============================================================================ +if (-not $DryRun -and $syncedDatFiles.Count -gt 0) { + Import-ToDatabase -FilePaths $syncedDatFiles +} + +# ============================================================================ +# PUSH: AD2 -> NAS (Software Updates for DOS Machines) +# ============================================================================ +Write-Log "--- AD2 to NAS Sync (Software Updates) ---" + +$pushedFiles = 0 + +# Sync COMMON/ProdSW (batch files for all stations) +# AD2 uses _COMMON, NAS uses COMMON - handle both +$commonSources = @( + @{ Local = "$AD2_TEST_PATH\_COMMON\ProdSW"; Remote = "$NAS_DATA_PATH/COMMON/ProdSW" }, + @{ Local = "$AD2_TEST_PATH\COMMON\ProdSW"; Remote = "$NAS_DATA_PATH/COMMON/ProdSW" } +) + +foreach ($source in $commonSources) { + if (Test-Path $source.Local) { + Write-Log "Syncing COMMON ProdSW from: $($source.Local)" + $commonFiles = Get-ChildItem -Path $source.Local -File -ErrorAction SilentlyContinue + foreach ($file in $commonFiles) { + $remotePath = "$($source.Remote)/$($file.Name)" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push: $($file.Name) -> $remotePath" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $file.FullName -RemotePath $remotePath + if ($success) { + Write-Log " Pushed: $($file.Name)" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push $($file.Name)" + $errorCount++ + } + } + } + } +} + +# Sync Ate/ProdSW (shared ATE data folders - 5BDATA, 7BDATA, 8BDATA, DSCDATA, etc.) +Write-Log "Syncing Ate/ProdSW data folders..." +$ateProdSwPath = "$AD2_TEST_PATH\Ate\ProdSW" +if (Test-Path $ateProdSwPath) { + # Get all files recursively (including subdirectories like DSCDATA, 5BDATA, etc.) + $ateFiles = Get-ChildItem -Path $ateProdSwPath -File -Recurse -ErrorAction SilentlyContinue + foreach ($file in $ateFiles) { + # Calculate relative path from Ate/ProdSW folder + $relativePath = $file.FullName.Substring($ateProdSwPath.Length + 1).Replace('\', '/') + $remotePath = "$NAS_DATA_PATH/Ate/ProdSW/$relativePath" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push: Ate/ProdSW/$relativePath" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $file.FullName -RemotePath $remotePath + if ($success) { + Write-Log " Pushed: Ate/ProdSW/$relativePath" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push Ate/ProdSW/$relativePath" + $errorCount++ + } + } + } +} else { + Write-Log " Ate/ProdSW not found: $ateProdSwPath" +} + +# Sync UPDATE.BAT (root level utility) +Write-Log "Syncing UPDATE.BAT..." +$updateBatLocal = "$AD2_TEST_PATH\UPDATE.BAT" +if (Test-Path $updateBatLocal) { + $updateBatRemote = "$NAS_DATA_PATH/UPDATE.BAT" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push: UPDATE.BAT -> $updateBatRemote" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $updateBatLocal -RemotePath $updateBatRemote + if ($success) { + Write-Log " Pushed: UPDATE.BAT" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push UPDATE.BAT" + $errorCount++ + } + } +} else { + Write-Log " WARNING: UPDATE.BAT not found at $updateBatLocal" +} + +# Sync DEPLOY.BAT (root level utility) +Write-Log "Syncing DEPLOY.BAT..." +$deployBatLocal = "$AD2_TEST_PATH\DEPLOY.BAT" +if (Test-Path $deployBatLocal) { + $deployBatRemote = "$NAS_DATA_PATH/DEPLOY.BAT" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push: DEPLOY.BAT -> $deployBatRemote" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $deployBatLocal -RemotePath $deployBatRemote + if ($success) { + Write-Log " Pushed: DEPLOY.BAT" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push DEPLOY.BAT" + $errorCount++ + } + } +} else { + Write-Log " WARNING: DEPLOY.BAT not found at $deployBatLocal" +} + +# Sync per-station ProdSW folders +Write-Log "Syncing station-specific ProdSW folders..." +$stationFolders = Get-ChildItem -Path $AD2_TEST_PATH -Directory -Filter "TS-*" -ErrorAction SilentlyContinue + +foreach ($station in $stationFolders) { + $prodSwPath = Join-Path $station.FullName "ProdSW" + + if (Test-Path $prodSwPath) { + # Get all files in ProdSW (including subdirectories) + $prodSwFiles = Get-ChildItem -Path $prodSwPath -File -Recurse -ErrorAction SilentlyContinue + + foreach ($file in $prodSwFiles) { + # Calculate relative path from ProdSW folder + $relativePath = $file.FullName.Substring($prodSwPath.Length + 1).Replace('\', '/') + $remotePath = "$NAS_DATA_PATH/$($station.Name)/ProdSW/$relativePath" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push: $($station.Name)/ProdSW/$relativePath" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $file.FullName -RemotePath $remotePath + if ($success) { + Write-Log " Pushed: $($station.Name)/ProdSW/$relativePath" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push $($station.Name)/ProdSW/$relativePath" + $errorCount++ + } + } + } + } + + # Check for TODO.BAT (one-time task file) + $todoBatPath = Join-Path $station.FullName "TODO.BAT" + if (Test-Path $todoBatPath) { + $remoteTodoPath = "$NAS_DATA_PATH/$($station.Name)/TODO.BAT" + + Write-Log "Found TODO.BAT for $($station.Name)" + + if ($DryRun) { + Write-Log " [DRY RUN] Would push TODO.BAT -> $remoteTodoPath" + $pushedFiles++ + } else { + $success = Copy-ToNAS -LocalPath $todoBatPath -RemotePath $remoteTodoPath + if ($success) { + Write-Log " Pushed TODO.BAT to NAS" + # Remove from AD2 after successful push (one-shot mechanism) + Remove-Item -Path $todoBatPath -Force + Write-Log " Removed TODO.BAT from AD2 (pushed to NAS)" + $pushedFiles++ + } else { + Write-Log " ERROR: Failed to push TODO.BAT" + $errorCount++ + } + } + } +} + +Write-Log "AD2 to NAS sync: $pushedFiles file(s) pushed" + +# ============================================================================ +# Update Status File +# ============================================================================ +$status = if ($errorCount -eq 0) { "OK" } else { "ERRORS" } +$statusContent = @" +AD2 <-> NAS Bidirectional Sync Status +====================================== +Timestamp: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") +Status: $status + +PULL (NAS -> AD2 - Test Results): + Files Pulled: $syncedFiles + Files Skipped: $skippedFiles + DAT Files Imported to DB: $($syncedDatFiles.Count) + +PUSH (AD2 -> NAS - Software Updates): + Files Pushed: $pushedFiles + +Errors: $errorCount +"@ + +Set-Content -Path $STATUS_FILE -Value $statusContent + +Write-Log "==========================================" +Write-Log "Sync complete: PULL=$syncedFiles, PUSH=$pushedFiles, Errors=$errorCount" +Write-Log "==========================================" + +# Exit with error code if there were failures +if ($errorCount -gt 0) { + exit 1 +} else { + exit 0 +} diff --git a/projects/solverbot b/projects/solverbot new file mode 160000 index 0000000..342fe0f --- /dev/null +++ b/projects/solverbot @@ -0,0 +1 @@ +Subproject commit 342fe0fdf4e68bbd8b3524a6c7530e566cc1e193