Files
Tracker/Code/Sync/sync.ps1
T
2026-02-21 21:55:42 +00:00

241 lines
7.5 KiB
PowerShell

<#
.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 ==="