3 Commits

Author SHA1 Message Date
1101f188e8 Merge pull request 'feat: fix permissions + add SSH key for Windows clients' (#3) from opencode-agent/JingTian-Rclone:feature/fix-permissions-add-key into main
Reviewed-on: http://git.mangopig.tech/Goko/JingTian-Rclone/pulls/3
2026-02-13 10:01:19 +00:00
f79712e8bd feat: add SSH private key for rclone SFTP connection
This key is generated by the Ubuntu setup script and allows
Windows clients to authenticate via SFTP to the sync server.
2026-02-13 10:00:43 +00:00
a145e82ffa fix: improve permission handling for pre-existing directories
- Create rclone-sync user BEFORE creating directories
- Use chown -R to handle pre-existing directories from repo clone
- Add ownership verification output
- Support optional PUBLIC_IP argument for connection details
2026-02-13 10:00:28 +00:00
5 changed files with 59 additions and 657 deletions

View File

@@ -1,25 +0,0 @@
# 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

View File

@@ -3,7 +3,10 @@
# JingTian rclone Server Setup Script # JingTian rclone Server Setup Script
# Run this on the Ubuntu VM that will receive synced files # Run this on the Ubuntu VM that will receive synced files
# #
# Usage: sudo bash setup.sh # Usage: sudo bash setup.sh [PUBLIC_IP]
#
# If PUBLIC_IP is provided, it will be shown in the connection details.
# Otherwise, the script will try to detect it or use the first local IP.
# #
set -e set -e
@@ -13,6 +16,7 @@ DATA_DIR="/data/jingtian/BenjaminTeam"
RCLONE_USER="rclone-sync" RCLONE_USER="rclone-sync"
SSH_KEY_NAME="jingtian_rclone" SSH_KEY_NAME="jingtian_rclone"
SSH_KEY_DIR="/home/$RCLONE_USER/.ssh" SSH_KEY_DIR="/home/$RCLONE_USER/.ssh"
PUBLIC_IP="${1:-}"
echo "==========================================" echo "=========================================="
echo "JingTian rclone Server Setup" echo "JingTian rclone Server Setup"
@@ -24,37 +28,9 @@ if [ "$EUID" -ne 0 ]; then
exit 1 exit 1
fi fi
# Step 1: Create data directory # Step 1: Create dedicated user for rclone sync (FIRST, so we can set ownership correctly)
echo "" echo ""
echo "[1/5] Creating data directory..." echo "[1/5] Creating dedicated sync user: $RCLONE_USER..."
mkdir -p "$DATA_DIR"
mkdir -p "$DATA_DIR/_LLM_Sync"
# Create the same folder structure as client
mkdir -p "$DATA_DIR/Admin/E-Signature"
mkdir -p "$DATA_DIR/Admin/General Matter"
mkdir -p "$DATA_DIR/Admin/IPD e-filing"
mkdir -p "$DATA_DIR/Admin/JT Logo"
mkdir -p "$DATA_DIR/Admin/Letterhead"
mkdir -p "$DATA_DIR/Admin/Matter Open"
mkdir -p "$DATA_DIR/Admin/Template"
mkdir -p "$DATA_DIR/BD&M/2025 GCP"
mkdir -p "$DATA_DIR/BD&M/HKPC"
mkdir -p "$DATA_DIR/BD&M/WKCDA WKProcure"
mkdir -p "$DATA_DIR/Billing/Draft Bills"
mkdir -p "$DATA_DIR/Billing/Invoice Templates"
mkdir -p "$DATA_DIR/Billing/Issued Bills"
mkdir -p "$DATA_DIR/Client"
mkdir -p "$DATA_DIR/Free Schedules/Price List"
mkdir -p "$DATA_DIR/Free Schedules/Emails"
mkdir -p "$DATA_DIR/IP"
mkdir -p "$DATA_DIR/Precedent"
echo " Created: $DATA_DIR"
# Step 2: Create dedicated user for rclone sync
echo ""
echo "[2/5] Creating dedicated sync user: $RCLONE_USER..."
if id "$RCLONE_USER" &>/dev/null; then if id "$RCLONE_USER" &>/dev/null; then
echo " User $RCLONE_USER already exists, skipping..." echo " User $RCLONE_USER already exists, skipping..."
else else
@@ -62,10 +38,44 @@ else
echo " Created user: $RCLONE_USER" echo " Created user: $RCLONE_USER"
fi fi
# Set ownership of data directory # Step 2: Create data directory structure with correct ownership from the start
chown -R "$RCLONE_USER:$RCLONE_USER" "$DATA_DIR" echo ""
chmod -R 755 "$DATA_DIR" echo "[2/5] Creating data directory..."
echo " Set ownership of $DATA_DIR to $RCLONE_USER"
# Create parent directories with root, then hand off to rclone-sync
mkdir -p /data/jingtian
chown root:root /data
chown -R "$RCLONE_USER:$RCLONE_USER" /data/jingtian
# Create BenjaminTeam structure as rclone-sync user
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/_LLM_Sync"
# Create the same folder structure as client
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/E-Signature"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/General Matter"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/IPD e-filing"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/JT Logo"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/Letterhead"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/Matter Open"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Admin/Template"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/BD&M/2025 GCP"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/BD&M/HKPC"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/BD&M/WKCDA WKProcure"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Billing/Draft Bills"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Billing/Invoice Templates"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Billing/Issued Bills"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Client"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Free Schedules/Price List"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Free Schedules/Emails"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/IP"
sudo -u "$RCLONE_USER" mkdir -p "$DATA_DIR/Precedent"
echo " Created: $DATA_DIR"
# Verify ownership
echo " Verifying ownership..."
ls -la /data/jingtian/ | head -5
# Step 3: Generate SSH key pair for rclone # Step 3: Generate SSH key pair for rclone
echo "" echo ""
@@ -111,6 +121,12 @@ else
echo " Installed: $(rclone version | head -1)" echo " Installed: $(rclone version | head -1)"
fi fi
# Determine the IP to show
if [ -z "$PUBLIC_IP" ]; then
# Try to get public IP, fall back to first local IP
PUBLIC_IP=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}')
fi
# Print summary # Print summary
echo "" echo ""
echo "==========================================" echo "=========================================="
@@ -134,7 +150,7 @@ echo "Save this key to: windows/rclone-key"
echo "It will be used by Windows clients to connect." echo "It will be used by Windows clients to connect."
echo "" echo ""
echo "Connection details for Windows rclone config:" echo "Connection details for Windows rclone config:"
echo " Host: $(hostname -I | awk '{print $1}')" echo " Host: $PUBLIC_IP"
echo " User: $RCLONE_USER" echo " User: $RCLONE_USER"
echo " Path: $DATA_DIR" echo " Path: $DATA_DIR"
echo "" echo ""

7
windows/rclone-key Normal file
View File

@@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBhGez4Yaqdq5kS2nLWDMje+Ay48G9pkTUgVHByrVrHOAAAAJjg45Xm4OOV
5gAAAAtzc2gtZWQyNTUxOQAAACBhGez4Yaqdq5kS2nLWDMje+Ay48G9pkTUgVHByrVrHOA
AAAEAw5C+98JLaNZakWuw88val82lV8ZgLzNLXcbh35aAVCWEZ7Phhqp2rmRLactYMyN74
DLjwb2mRNSBUcHKtWsc4AAAAFGppbmd0aWFuLXJjbG9uZS1zeW5jAQ==
-----END OPENSSH PRIVATE KEY-----

View File

@@ -1,361 +0,0 @@
#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 ""

View File

@@ -1,235 +0,0 @@
<#
.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
}