<# .SYNOPSIS JingTian LLM - File Sync Script .DESCRIPTION Syncs files between Windows (BenjaminTeam/) and the Ubuntu server. - PULL first: Ubuntu -> Windows for bidirectional files (pipeline outputs) - PUSH second: Windows -> Ubuntu (one-way, Windows is source of truth) Order matters: pull gets pipeline updates, then push sends everything (including those updates + Mr. Choi's edits) back as source of truth. Called automatically by Windows Task Scheduler every N minutes. #> param( [string]$SyncRoot = $PSScriptRoot ) $ErrorActionPreference = "Continue" # ============================================================================ # Helpers # ============================================================================ function Read-Config { param([string]$Path) $config = @{} $section = "" $listItems = @() $listSection = "" foreach ($line in Get-Content $Path) { $line = $line.Trim() if ($line -eq "" -or $line.StartsWith("#")) { continue } if ($line -match '^\[(.+)\]$') { if ($listSection -and $listItems.Count -gt 0) { $config[$listSection] = $listItems } $section = $Matches[1] if ($section -eq "bidirectional") { $listSection = $section $listItems = @() } else { $listSection = "" } continue } if ($listSection) { $listItems += $line } elseif ($line -match '^(.+?)=(.+)$') { $key = "$section.$($Matches[1].Trim())" $config[$key] = $Matches[2].Trim() } } if ($listSection -and $listItems.Count -gt 0) { $config[$listSection] = $listItems } return $config } function Write-Log { param([string]$Message, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $entry = "[$timestamp] [$Level] $Message" Add-Content -Path $logFile -Value $entry if ($Level -eq "ERROR") { Write-Host $entry -ForegroundColor Red } } # ============================================================================ # Setup # ============================================================================ $configPath = Join-Path $SyncRoot "config.ini" $rcloneExe = Join-Path $SyncRoot "rclone\rclone.exe" $rcloneConf = Join-Path $SyncRoot "rclone\rclone.conf" $logsPath = Join-Path $SyncRoot "logs" $ignoreFile = Join-Path $SyncRoot ".sync_ignore" # BenjaminTeam is three levels up from Code/Sync/ (_LLM -> BenjaminTeam) $benjaminTeam = (Resolve-Path (Join-Path $SyncRoot "..\..\.." )).Path # Log file: one per day $logDate = Get-Date -Format "yyyy-MM-dd" $logFile = Join-Path $logsPath "sync-$logDate.log" # Ensure logs directory if (-not (Test-Path $logsPath)) { New-Item -ItemType Directory -Path $logsPath -Force | Out-Null } # Preflight checks if (-not (Test-Path $configPath)) { Write-Log "config.ini not found at $configPath" "ERROR" exit 1 } if (-not (Test-Path $rcloneExe)) { Write-Log "rclone.exe not found at $rcloneExe. Run Setup.bat first." "ERROR" exit 1 } if (-not (Test-Path $rcloneConf)) { Write-Log "rclone.conf not found at $rcloneConf. Run Setup.bat first." "ERROR" exit 1 } # Read config $config = Read-Config $configPath $remoteName = $config["sync.remote_name"] $remotePath = $config["server.remote_path"] $bidirectionalFiles = @() if ($config.ContainsKey("bidirectional")) { $bidirectionalFiles = $config["bidirectional"] } $remoteBase = "${remoteName}:${remotePath}" Write-Log "=== Sync started ===" Write-Log "BenjaminTeam: $benjaminTeam" Write-Log "Remote: $remoteBase" # ============================================================================ # Phase 1: PULL bidirectional files (Ubuntu -> Windows) # Runs FIRST so pipeline updates reach Windows before push sends everything back. # Uses --update: only copies if remote file is newer than local. # This means Mr. Choi's recent edits are preserved (his file is newer). # ============================================================================ if ($bidirectionalFiles.Count -gt 0) { Write-Log "--- Phase 1: PULL bidirectional files (Ubuntu -> Windows) ---" foreach ($relPath in $bidirectionalFiles) { # Normalize path separators $relPath = $relPath.Replace("/", "\") $remoteSrc = "${remoteBase}/$($relPath.Replace('\', '/'))" $localDest = Join-Path $benjaminTeam $relPath $localDir = Split-Path $localDest -Parent # Ensure local directory exists if (-not (Test-Path $localDir)) { New-Item -ItemType Directory -Path $localDir -Force | Out-Null } Write-Log "Pulling: $relPath" try { $pullOutput = & $rcloneExe copyto $remoteSrc $localDest ` --config $rcloneConf ` --update ` --contimeout 30s ` --timeout 60s ` --retries 3 ` --log-level NOTICE 2>&1 $pullExitCode = $LASTEXITCODE if ($pullExitCode -eq 0) { Write-Log " Pulled OK: $relPath" } else { # Exit code 3 = directory not found / file doesn't exist yet (OK) if ($pullExitCode -eq 3) { Write-Log " Not found on server (yet): $relPath" } else { Write-Log " Pull failed (exit code: $pullExitCode): $relPath" "ERROR" foreach ($line in $pullOutput) { Write-Log " $line" "ERROR" } } } } catch { Write-Log " Pull exception for ${relPath}: $_" "ERROR" } } } else { Write-Log "No bidirectional files configured, skipping pull phase" } # ============================================================================ # Phase 2: PUSH (Windows -> Ubuntu) # Windows is source of truth. rclone sync mirrors local to remote. # Bidirectional files are EXCLUDED from push — they are managed by the # pipeline on Ubuntu and pulled down in Phase 1. # ============================================================================ Write-Log "--- Phase 2: PUSH (Windows -> Ubuntu) ---" $pushArgs = @( "sync" $benjaminTeam $remoteBase "--config", $rcloneConf "--transfers", "4" "--checkers", "8" "--contimeout", "30s" "--timeout", "120s" "--retries", "3" "--log-level", "NOTICE" ) # Add filter file if it exists if (Test-Path $ignoreFile) { $pushArgs += "--filter-from" $pushArgs += $ignoreFile } # Exclude bidirectional files from push (pipeline manages these on Ubuntu) foreach ($relPath in $bidirectionalFiles) { $excludePath = $relPath.Replace("\", "/") $pushArgs += "--exclude" $pushArgs += $excludePath } $pushStart = Get-Date Write-Log "Running: rclone sync (push)" try { $pushOutput = & $rcloneExe @pushArgs 2>&1 $pushExitCode = $LASTEXITCODE $pushDuration = ((Get-Date) - $pushStart).TotalSeconds if ($pushExitCode -eq 0) { Write-Log "Push completed in ${pushDuration}s" } else { Write-Log "Push failed (exit code: $pushExitCode) in ${pushDuration}s" "ERROR" foreach ($line in $pushOutput) { Write-Log " $line" "ERROR" } } } catch { Write-Log "Push exception: $_" "ERROR" } # ============================================================================ # Done # ============================================================================ Write-Log "=== Sync finished ==="