From 45f2c42ea2d4096967e0f6e4174e819c75a1b80f Mon Sep 17 00:00:00 2001 From: opencode-agent Date: Fri, 13 Feb 2026 11:16:46 +0000 Subject: [PATCH 1/3] feat: add Windows setup script - Checks/installs Git and rclone automatically - Auto-detects BenjaminTeam folder or prompts user - Copies SSH key and tests connection - Configures rclone with SFTP remote - Creates Windows Scheduled Task for periodic sync (default: 5 min) --- windows/setup.ps1 | 361 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 windows/setup.ps1 diff --git a/windows/setup.ps1 b/windows/setup.ps1 new file mode 100644 index 0000000..de90b1c --- /dev/null +++ b/windows/setup.ps1 @@ -0,0 +1,361 @@ +#Requires -RunAsAdministrator +<# +.SYNOPSIS + JingTian rclone Client Setup Script for Windows + +.DESCRIPTION + This script sets up rclone sync on a Windows client machine to sync + the BenjaminTeam folder with the remote Ubuntu server. + +.PARAMETER ServerHost + The IP address or hostname of the Ubuntu sync server. + Default: 20.240.140.78 + +.PARAMETER ServerUser + The SSH username on the Ubuntu server. + Default: rclone-sync + +.PARAMETER ServerPath + The remote path on the Ubuntu server. + Default: /data/jingtian/BenjaminTeam + +.PARAMETER LocalPath + The local BenjaminTeam folder path. If not specified, the script will + attempt to auto-detect or prompt the user. + +.PARAMETER SyncInterval + Sync interval in minutes for the scheduled task. + Default: 5 + +.EXAMPLE + .\setup.ps1 + +.EXAMPLE + .\setup.ps1 -LocalPath "D:\Work\BenjaminTeam" -SyncInterval 10 +#> + +param( + [string]$ServerHost = "20.240.140.78", + [string]$ServerUser = "rclone-sync", + [string]$ServerPath = "/data/jingtian/BenjaminTeam", + [string]$LocalPath = "", + [int]$SyncInterval = 5 +) + +# Configuration +$ErrorActionPreference = "Stop" +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RcloneKeySource = Join-Path $ScriptDir "rclone-key" +$RcloneKeyDest = "$env:USERPROFILE\.ssh\jingtian_rclone" +$RcloneConfigDir = "$env:APPDATA\rclone" +$RcloneConfigFile = "$RcloneConfigDir\rclone.conf" +$SyncScriptPath = Join-Path $ScriptDir "sync.ps1" +$LogDir = "$env:LOCALAPPDATA\JingTian\logs" + +# Styling +function Write-Header { + param([string]$Text) + Write-Host "" + Write-Host "===========================================" -ForegroundColor Cyan + Write-Host $Text -ForegroundColor Cyan + Write-Host "===========================================" -ForegroundColor Cyan +} + +function Write-Step { + param([string]$Step, [string]$Text) + Write-Host "" + Write-Host "[$Step] $Text" -ForegroundColor Yellow +} + +function Write-Success { + param([string]$Text) + Write-Host " [OK] $Text" -ForegroundColor Green +} + +function Write-Info { + param([string]$Text) + Write-Host " $Text" -ForegroundColor Gray +} + +function Write-ErrorMsg { + param([string]$Text) + Write-Host " [ERROR] $Text" -ForegroundColor Red +} + +# ============================================================ +# MAIN SCRIPT +# ============================================================ + +Write-Header "JingTian rclone Client Setup" + +Write-Host "" +Write-Host "Server: $ServerHost" -ForegroundColor White +Write-Host "User: $ServerUser" -ForegroundColor White +Write-Host "Remote: $ServerPath" -ForegroundColor White + +# ------------------------------------------------------------ +# Step 1: Check/Install Git +# ------------------------------------------------------------ +Write-Step "1/7" "Checking Git installation..." + +$gitPath = "C:\Program Files\Git\bin\git.exe" +if (Test-Path $gitPath) { + $gitVersion = & $gitPath --version + Write-Success "Git already installed: $gitVersion" +} else { + Write-Info "Git not found. Installing..." + + $gitInstaller = "$env:TEMP\git-installer.exe" + $gitUrl = "https://github.com/git-for-windows/git/releases/download/v2.43.0.windows.1/Git-2.43.0-64-bit.exe" + + Write-Info "Downloading Git installer..." + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $gitUrl -OutFile $gitInstaller + + Write-Info "Installing Git (this may take a few minutes)..." + Start-Process -FilePath $gitInstaller -ArgumentList '/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS=icons,ext\reg\shellhere,assoc,assoc_sh' -Wait + + if (Test-Path $gitPath) { + Write-Success "Git installed successfully" + } else { + Write-ErrorMsg "Git installation failed" + exit 1 + } +} + +# ------------------------------------------------------------ +# Step 2: Check/Install rclone +# ------------------------------------------------------------ +Write-Step "2/7" "Checking rclone installation..." + +$rclonePath = "$env:LOCALAPPDATA\rclone\rclone.exe" +$rclonePathAlt = "C:\rclone\rclone.exe" + +if (Test-Path $rclonePath) { + $rcloneVersion = & $rclonePath version | Select-Object -First 1 + Write-Success "rclone already installed: $rcloneVersion" +} elseif (Test-Path $rclonePathAlt) { + $rclonePath = $rclonePathAlt + $rcloneVersion = & $rclonePath version | Select-Object -First 1 + Write-Success "rclone already installed: $rcloneVersion" +} else { + Write-Info "rclone not found. Installing..." + + $rcloneZip = "$env:TEMP\rclone.zip" + $rcloneExtract = "$env:TEMP\rclone-extract" + $rcloneInstallDir = "$env:LOCALAPPDATA\rclone" + $rcloneUrl = "https://downloads.rclone.org/rclone-current-windows-amd64.zip" + + Write-Info "Downloading rclone..." + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri $rcloneUrl -OutFile $rcloneZip + + Write-Info "Extracting rclone..." + if (Test-Path $rcloneExtract) { Remove-Item -Recurse -Force $rcloneExtract } + Expand-Archive -Path $rcloneZip -DestinationPath $rcloneExtract + + # Find the extracted folder (it has version in the name) + $extractedFolder = Get-ChildItem -Path $rcloneExtract -Directory | Select-Object -First 1 + + # Create install directory and copy rclone + if (-not (Test-Path $rcloneInstallDir)) { + New-Item -ItemType Directory -Path $rcloneInstallDir -Force | Out-Null + } + Copy-Item -Path "$($extractedFolder.FullName)\rclone.exe" -Destination $rclonePath -Force + + # Clean up + Remove-Item -Recurse -Force $rcloneZip, $rcloneExtract + + if (Test-Path $rclonePath) { + Write-Success "rclone installed to $rclonePath" + } else { + Write-ErrorMsg "rclone installation failed" + exit 1 + } +} + +# ------------------------------------------------------------ +# Step 3: Detect/Confirm BenjaminTeam folder +# ------------------------------------------------------------ +Write-Step "3/7" "Locating BenjaminTeam folder..." + +if ($LocalPath -and (Test-Path $LocalPath)) { + Write-Success "Using specified path: $LocalPath" +} else { + # Common locations to search + $searchPaths = @( + "$env:USERPROFILE\Documents\BenjaminTeam", + "$env:USERPROFILE\Documents\Benjamin Team", + "D:\BenjaminTeam", + "D:\Benjamin Team", + "C:\BenjaminTeam", + "C:\Benjamin Team" + ) + + $foundPath = $null + foreach ($path in $searchPaths) { + if (Test-Path $path) { + $foundPath = $path + break + } + } + + if ($foundPath) { + Write-Info "Found BenjaminTeam folder at: $foundPath" + $confirm = Read-Host " Use this path? (Y/n)" + if ($confirm -eq "" -or $confirm -match "^[Yy]") { + $LocalPath = $foundPath + } + } + + if (-not $LocalPath -or -not (Test-Path $LocalPath)) { + Write-Info "Could not auto-detect BenjaminTeam folder." + $LocalPath = Read-Host " Enter the full path to your BenjaminTeam folder" + + if (-not (Test-Path $LocalPath)) { + Write-ErrorMsg "Path does not exist: $LocalPath" + exit 1 + } + } + + Write-Success "Using path: $LocalPath" +} + +# Ensure _LLM_Sync folder exists +$llmSyncPath = Join-Path $LocalPath "_LLM_Sync" +if (-not (Test-Path $llmSyncPath)) { + New-Item -ItemType Directory -Path $llmSyncPath -Force | Out-Null + Write-Info "Created _LLM_Sync folder" +} + +# ------------------------------------------------------------ +# Step 4: Setup SSH key +# ------------------------------------------------------------ +Write-Step "4/7" "Setting up SSH key..." + +# Create .ssh directory if it doesn't exist +$sshDir = "$env:USERPROFILE\.ssh" +if (-not (Test-Path $sshDir)) { + New-Item -ItemType Directory -Path $sshDir -Force | Out-Null +} + +# Copy the SSH key +if (-not (Test-Path $RcloneKeySource)) { + Write-ErrorMsg "SSH key not found at: $RcloneKeySource" + Write-Info "Make sure the rclone-key file is in the same directory as this script." + exit 1 +} + +Copy-Item -Path $RcloneKeySource -Destination $RcloneKeyDest -Force +Write-Success "SSH key copied to $RcloneKeyDest" + +# Test SSH connection +Write-Info "Testing SSH connection to $ServerHost..." +$sshTest = & ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes -i $RcloneKeyDest "$ServerUser@$ServerHost" "echo 'SSH_OK'" 2>&1 + +if ($sshTest -match "SSH_OK") { + Write-Success "SSH connection successful" +} else { + Write-ErrorMsg "SSH connection failed: $sshTest" + Write-Info "Please check the server is running and the key is correct." + exit 1 +} + +# ------------------------------------------------------------ +# Step 5: Configure rclone +# ------------------------------------------------------------ +Write-Step "5/7" "Configuring rclone..." + +# Create rclone config directory +if (-not (Test-Path $RcloneConfigDir)) { + New-Item -ItemType Directory -Path $RcloneConfigDir -Force | Out-Null +} + +# Generate rclone config +$rcloneConfig = @" +[jingtian-server] +type = sftp +host = $ServerHost +user = $ServerUser +key_file = $RcloneKeyDest +shell_type = unix +"@ + +# Write config file +Set-Content -Path $RcloneConfigFile -Value $rcloneConfig -Encoding ASCII +Write-Success "rclone config written to $RcloneConfigFile" + +# Test rclone connection +Write-Info "Testing rclone connection..." +$rcloneTest = & $rclonePath lsd "jingtian-server:$ServerPath" 2>&1 + +if ($LASTEXITCODE -eq 0) { + Write-Success "rclone connection successful" + Write-Info "Remote folders:" + $rcloneTest | ForEach-Object { Write-Info " $_" } +} else { + Write-ErrorMsg "rclone connection failed: $rcloneTest" + exit 1 +} + +# ------------------------------------------------------------ +# Step 6: Create log directory +# ------------------------------------------------------------ +Write-Step "6/7" "Setting up logging..." + +if (-not (Test-Path $LogDir)) { + New-Item -ItemType Directory -Path $LogDir -Force | Out-Null +} +Write-Success "Log directory: $LogDir" + +# ------------------------------------------------------------ +# Step 7: Create Scheduled Task +# ------------------------------------------------------------ +Write-Step "7/7" "Creating scheduled sync task..." + +$taskName = "JingTian-Sync" +$taskDescription = "Syncs BenjaminTeam folder with remote server every $SyncInterval minutes" + +# Remove existing task if it exists +$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue +if ($existingTask) { + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false + Write-Info "Removed existing scheduled task" +} + +# Create the scheduled task +$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$SyncScriptPath`" -LocalPath `"$LocalPath`" -RclonePath `"$rclonePath`" -LogDir `"$LogDir`"" + +$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $SyncInterval) -RepetitionDuration (New-TimeSpan -Days 9999) + +$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest + +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 1) + +Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description $taskDescription | Out-Null + +Write-Success "Scheduled task created: $taskName" +Write-Info "Sync will run every $SyncInterval minutes" + +# ------------------------------------------------------------ +# Summary +# ------------------------------------------------------------ +Write-Header "Setup Complete!" + +Write-Host "" +Write-Host "Configuration Summary:" -ForegroundColor White +Write-Host " Local Path: $LocalPath" -ForegroundColor Gray +Write-Host " Remote: $ServerUser@$ServerHost`:$ServerPath" -ForegroundColor Gray +Write-Host " Sync Interval: Every $SyncInterval minutes" -ForegroundColor Gray +Write-Host " Log Directory: $LogDir" -ForegroundColor Gray +Write-Host "" +Write-Host "Sync Behavior:" -ForegroundColor White +Write-Host " - Main folders: One-way sync (Windows -> Server)" -ForegroundColor Gray +Write-Host " - _LLM_Sync: Bidirectional sync" -ForegroundColor Gray +Write-Host "" +Write-Host "To manually trigger a sync, run:" -ForegroundColor White +Write-Host " .\sync.ps1" -ForegroundColor Cyan +Write-Host "" +Write-Host "To check sync logs:" -ForegroundColor White +Write-Host " Get-Content `"$LogDir\sync.log`" -Tail 50" -ForegroundColor Cyan +Write-Host "" From 157599be4a7cfa1eff915aa7c851e26d5c63635b Mon Sep 17 00:00:00 2001 From: opencode-agent Date: Fri, 13 Feb 2026 11:17:19 +0000 Subject: [PATCH 2/3] feat: add Windows sync script - Phase 1: One-way sync (Windows -> Server) for main folders - Phase 2: Bidirectional sync using rclone bisync for _LLM_Sync - Auto-detects rclone and BenjaminTeam folder paths - Comprehensive logging to %LOCALAPPDATA%\JingTian\logs - Supports verbose output with -VerboseOutput flag --- windows/sync.ps1 | 235 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 windows/sync.ps1 diff --git a/windows/sync.ps1 b/windows/sync.ps1 new file mode 100644 index 0000000..d762761 --- /dev/null +++ b/windows/sync.ps1 @@ -0,0 +1,235 @@ +<# +.SYNOPSIS + JingTian rclone Sync Script + +.DESCRIPTION + Syncs the local BenjaminTeam folder with the remote Ubuntu server. + - Main folders: One-way sync (Windows -> Server) + - _LLM_Sync folder: Bidirectional sync + +.PARAMETER LocalPath + Path to the local BenjaminTeam folder. + +.PARAMETER RclonePath + Path to rclone.exe. Default: auto-detect + +.PARAMETER LogDir + Directory for log files. Default: %LOCALAPPDATA%\JingTian\logs + +.PARAMETER Verbose + Show detailed output during sync. +#> + +param( + [string]$LocalPath = "", + [string]$RclonePath = "", + [string]$LogDir = "$env:LOCALAPPDATA\JingTian\logs", + [switch]$VerboseOutput +) + +# Configuration +$RemoteName = "jingtian-server" +$RemotePath = "/data/jingtian/BenjaminTeam" +$LLMSyncFolder = "_LLM_Sync" + +# Find rclone if not specified +if (-not $RclonePath -or -not (Test-Path $RclonePath)) { + $possiblePaths = @( + "$env:LOCALAPPDATA\rclone\rclone.exe", + "C:\rclone\rclone.exe", + "rclone.exe" + ) + foreach ($path in $possiblePaths) { + if (Test-Path $path) { + $RclonePath = $path + break + } + } +} + +if (-not (Test-Path $RclonePath)) { + Write-Error "rclone not found. Please run setup.ps1 first." + exit 1 +} + +# Find LocalPath if not specified (check common locations) +if (-not $LocalPath -or -not (Test-Path $LocalPath)) { + $searchPaths = @( + "$env:USERPROFILE\Documents\BenjaminTeam", + "$env:USERPROFILE\Documents\Benjamin Team", + "D:\BenjaminTeam", + "C:\BenjaminTeam" + ) + foreach ($path in $searchPaths) { + if (Test-Path $path) { + $LocalPath = $path + break + } + } +} + +if (-not (Test-Path $LocalPath)) { + Write-Error "BenjaminTeam folder not found. Please specify -LocalPath" + exit 1 +} + +# Setup logging +$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" +$logFile = Join-Path $LogDir "sync.log" +$detailLogFile = Join-Path $LogDir "sync_$timestamp.log" + +if (-not (Test-Path $LogDir)) { + New-Item -ItemType Directory -Path $LogDir -Force | Out-Null +} + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $logLine = "[$ts] [$Level] $Message" + Add-Content -Path $logFile -Value $logLine + + if ($VerboseOutput) { + switch ($Level) { + "ERROR" { Write-Host $logLine -ForegroundColor Red } + "WARN" { Write-Host $logLine -ForegroundColor Yellow } + "OK" { Write-Host $logLine -ForegroundColor Green } + default { Write-Host $logLine } + } + } +} + +# ============================================================ +# MAIN SYNC LOGIC +# ============================================================ + +Write-Log "========== Sync Started ==========" +Write-Log "Local: $LocalPath" +Write-Log "Remote: ${RemoteName}:${RemotePath}" + +$syncErrors = @() +$startTime = Get-Date + +# ------------------------------------------------------------ +# Phase 1: One-way sync (Windows -> Server) for main folders +# Excludes _LLM_Sync which is handled separately +# ------------------------------------------------------------ + +Write-Log "Phase 1: Syncing main folders (Windows -> Server)..." + +$rcloneArgs = @( + "sync", + $LocalPath, + "${RemoteName}:${RemotePath}", + "--exclude", "$LLMSyncFolder/**", + "--transfers", "4", + "--checkers", "8", + "--contimeout", "60s", + "--timeout", "300s", + "--retries", "3", + "--low-level-retries", "10", + "--stats", "1m", + "--stats-one-line", + "--log-file", $detailLogFile, + "--log-level", "INFO" +) + +if ($VerboseOutput) { + $rcloneArgs += "--progress" +} + +try { + $result = & $RclonePath @rcloneArgs 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Log "Phase 1 complete: Main folders synced successfully" "OK" + } else { + Write-Log "Phase 1 warning: rclone exited with code $LASTEXITCODE" "WARN" + $syncErrors += "Main folder sync had warnings (exit code: $LASTEXITCODE)" + } +} catch { + Write-Log "Phase 1 error: $_" "ERROR" + $syncErrors += "Main folder sync failed: $_" +} + +# ------------------------------------------------------------ +# Phase 2: Bidirectional sync for _LLM_Sync folder +# Uses rclone bisync for true bidirectional sync +# ------------------------------------------------------------ + +Write-Log "Phase 2: Syncing _LLM_Sync folder (bidirectional)..." + +$llmLocalPath = Join-Path $LocalPath $LLMSyncFolder +$llmRemotePath = "${RemoteName}:${RemotePath}/${LLMSyncFolder}" + +# Ensure local _LLM_Sync exists +if (-not (Test-Path $llmLocalPath)) { + New-Item -ItemType Directory -Path $llmLocalPath -Force | Out-Null + Write-Log "Created local _LLM_Sync folder" +} + +# Check if bisync has been initialized (look for .rclone-bisync directory) +$bisyncStateDir = "$env:USERPROFILE\.cache\rclone\bisync" +$bisyncStateFile = Join-Path $bisyncStateDir "*.lst" + +# For first run, we need to use --resync to initialize +$needsResync = $false +if (-not (Test-Path $bisyncStateDir) -or -not (Get-ChildItem $bisyncStateFile -ErrorAction SilentlyContinue)) { + Write-Log "First bisync run detected, will initialize with --resync" + $needsResync = $true +} + +$bisyncArgs = @( + "bisync", + $llmLocalPath, + $llmRemotePath, + "--transfers", "4", + "--checkers", "8", + "--contimeout", "60s", + "--timeout", "300s", + "--retries", "3", + "--log-file", $detailLogFile, + "--log-level", "INFO" +) + +if ($needsResync) { + $bisyncArgs += "--resync" + $bisyncArgs += "--resync-mode", "newer" +} + +if ($VerboseOutput) { + $bisyncArgs += "--progress" +} + +try { + $result = & $RclonePath @bisyncArgs 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Log "Phase 2 complete: _LLM_Sync folder synced successfully" "OK" + } else { + # bisync can return non-zero for various reasons, check output + Write-Log "Phase 2 warning: bisync exited with code $LASTEXITCODE" "WARN" + $syncErrors += "_LLM_Sync bisync had warnings (exit code: $LASTEXITCODE)" + } +} catch { + Write-Log "Phase 2 error: $_" "ERROR" + $syncErrors += "_LLM_Sync sync failed: $_" +} + +# ------------------------------------------------------------ +# Summary +# ------------------------------------------------------------ + +$endTime = Get-Date +$duration = $endTime - $startTime + +Write-Log "========== Sync Completed ==========" +Write-Log "Duration: $($duration.TotalSeconds.ToString('F1')) seconds" + +if ($syncErrors.Count -gt 0) { + Write-Log "Completed with $($syncErrors.Count) warning(s):" "WARN" + foreach ($err in $syncErrors) { + Write-Log " - $err" "WARN" + } + exit 1 +} else { + Write-Log "All syncs completed successfully" "OK" + exit 0 +} From a100463f09b41ac6798315d6fe01a620cb89dcb5 Mon Sep 17 00:00:00 2001 From: opencode-agent Date: Fri, 13 Feb 2026 11:17:28 +0000 Subject: [PATCH 3/3] docs: add rclone config template for reference --- config/rclone.conf.template | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 config/rclone.conf.template diff --git a/config/rclone.conf.template b/config/rclone.conf.template new file mode 100644 index 0000000..4a6be7b --- /dev/null +++ b/config/rclone.conf.template @@ -0,0 +1,25 @@ +# JingTian rclone Configuration Template +# +# This file is auto-generated by setup.ps1 +# Manual edits may be overwritten. +# +# Remote: SFTP connection to Ubuntu sync server +# Used for syncing BenjaminTeam folder + +[jingtian-server] +type = sftp +host = {{SERVER_HOST}} +user = {{SERVER_USER}} +key_file = {{SSH_KEY_PATH}} +shell_type = unix + +# Optional settings (uncomment to enable): +# +# # Limit bandwidth (in KiB/s) +# bwlimit = 10M +# +# # Use compression +# use_insecure_cipher = false +# +# # Connection timeout +# conn_timeout = 30s