241 lines
7.5 KiB
PowerShell
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 ==="
|