315 lines
11 KiB
PowerShell
315 lines
11 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
<#
|
|
.SYNOPSIS
|
|
JingTian LLM - Windows Sync Setup
|
|
.DESCRIPTION
|
|
Sets up rclone for automatic file syncing between this Windows machine
|
|
and the JingTian Ubuntu server. Run once, then syncing happens automatically.
|
|
.NOTES
|
|
Run via Setup.bat (double-click) or directly with admin privileges.
|
|
#>
|
|
|
|
param(
|
|
[string]$SyncRoot = $PSScriptRoot,
|
|
[string]$Password = ""
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# ============================================================================
|
|
# Helpers
|
|
# ============================================================================
|
|
|
|
function Write-Step {
|
|
param([int]$Number, [string]$Title)
|
|
Write-Host ""
|
|
Write-Host "[$Number/6] $Title" -ForegroundColor Cyan
|
|
Write-Host ("-" * 50)
|
|
}
|
|
|
|
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 '^\[(.+)\]$') {
|
|
# Save any pending list
|
|
if ($listSection -and $listItems.Count -gt 0) {
|
|
$config[$listSection] = $listItems
|
|
}
|
|
$section = $Matches[1]
|
|
# Check if this section is a list section (bidirectional)
|
|
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()
|
|
}
|
|
}
|
|
|
|
# Save final list section
|
|
if ($listSection -and $listItems.Count -gt 0) {
|
|
$config[$listSection] = $listItems
|
|
}
|
|
|
|
return $config
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main Setup
|
|
# ============================================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host " JingTian LLM - Sync Setup" -ForegroundColor Yellow
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
# Determine paths
|
|
$configPath = Join-Path $SyncRoot "config.ini"
|
|
$rclonePath = Join-Path $SyncRoot "rclone"
|
|
$rcloneExe = Join-Path $rclonePath "rclone.exe"
|
|
$rcloneConf = Join-Path $rclonePath "rclone.conf"
|
|
$logsPath = Join-Path $SyncRoot "logs"
|
|
$syncScript = Join-Path $SyncRoot "sync.ps1"
|
|
$ignoreFile = Join-Path $SyncRoot ".sync_ignore"
|
|
|
|
# BenjaminTeam is two levels up from Code/Sync/
|
|
$benjaminTeam = (Resolve-Path (Join-Path $SyncRoot "..\..\.." )).Path
|
|
|
|
Write-Host "Sync root: $SyncRoot"
|
|
Write-Host "BenjaminTeam: $benjaminTeam"
|
|
Write-Host ""
|
|
|
|
# Read config
|
|
if (-not (Test-Path $configPath)) {
|
|
Write-Host "ERROR: config.ini not found at $configPath" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
$config = Read-Config $configPath
|
|
$serverHost = $config["server.host"]
|
|
$serverUser = $config["server.user"]
|
|
$serverPort = $config["server.port"]
|
|
$remotePath = $config["server.remote_path"]
|
|
$remoteName = $config["sync.remote_name"]
|
|
$interval = $config["sync.interval_minutes"]
|
|
|
|
Write-Host "Server: ${serverUser}@${serverHost}:${serverPort}" -ForegroundColor Gray
|
|
Write-Host "Remote: ${remoteName}:${remotePath}" -ForegroundColor Gray
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 1: Install rclone
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 1 "Installing rclone"
|
|
|
|
if (Test-Path $rcloneExe) {
|
|
$version = & $rcloneExe version 2>&1 | Select-Object -First 1
|
|
Write-Host "rclone already installed: $version" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "Downloading rclone..."
|
|
$zipUrl = "https://downloads.rclone.org/rclone-current-windows-amd64.zip"
|
|
$zipPath = Join-Path $env:TEMP "rclone.zip"
|
|
$extractPath = Join-Path $env:TEMP "rclone-extract"
|
|
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
|
|
|
|
Write-Host "Extracting..."
|
|
if (Test-Path $extractPath) { Remove-Item $extractPath -Recurse -Force }
|
|
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
|
|
|
|
# rclone extracts into a subfolder like rclone-v1.68.2-windows-amd64/
|
|
$rcloneSubDir = Get-ChildItem $extractPath -Directory | Select-Object -First 1
|
|
|
|
if (-not (Test-Path $rclonePath)) { New-Item -ItemType Directory -Path $rclonePath -Force | Out-Null }
|
|
Copy-Item (Join-Path $rcloneSubDir.FullName "rclone.exe") $rcloneExe -Force
|
|
|
|
# Cleanup
|
|
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
|
Remove-Item $extractPath -Recurse -Force -ErrorAction SilentlyContinue
|
|
|
|
$version = & $rcloneExe version 2>&1 | Select-Object -First 1
|
|
Write-Host "Installed: $version" -ForegroundColor Green
|
|
}
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 2: Configure rclone (password-based SFTP)
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 2 "Configuring rclone remote"
|
|
|
|
if ($Password -ne "") {
|
|
$plainPassword = $Password
|
|
Write-Host "Using provided password" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "Enter the password for ${serverUser}@${serverHost}:" -ForegroundColor Yellow
|
|
$securePassword = Read-Host -AsSecureString
|
|
$plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
|
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
|
|
)
|
|
}
|
|
|
|
# Obscure the password using rclone
|
|
$obscuredPassword = & $rcloneExe obscure $plainPassword 2>&1
|
|
$plainPassword = $null # Clear from memory
|
|
|
|
$rcloneConfContent = @"
|
|
[$remoteName]
|
|
type = sftp
|
|
host = $serverHost
|
|
user = $serverUser
|
|
port = $serverPort
|
|
pass = $obscuredPassword
|
|
shell_type = unix
|
|
md5sum_command = md5sum
|
|
sha1sum_command = sha1sum
|
|
"@
|
|
|
|
# Write without BOM (rclone can't parse BOM)
|
|
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
|
|
[System.IO.File]::WriteAllText($rcloneConf, $rcloneConfContent, $utf8NoBom)
|
|
Write-Host "rclone config written to $rcloneConf" -ForegroundColor Green
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 3: Test connection
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 3 "Testing connection to server"
|
|
|
|
try {
|
|
$testOutput = & $rcloneExe lsd "${remoteName}:${remotePath}" --config $rcloneConf 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host "ERROR: Connection failed!" -ForegroundColor Red
|
|
Write-Host $testOutput
|
|
Write-Host ""
|
|
Write-Host "Check the server IP, username, and password in config.ini" -ForegroundColor Yellow
|
|
exit 1
|
|
}
|
|
Write-Host "Connection successful! Remote directories:" -ForegroundColor Green
|
|
$testOutput | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
|
|
} catch {
|
|
Write-Host "ERROR: Connection failed: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 4: Generate .sync_ignore
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 4 "Generating sync exclusion rules"
|
|
|
|
$ignoreContent = @"
|
|
# JingTian Sync Ignore Rules
|
|
# rclone filter syntax - one rule per line
|
|
# See: https://rclone.org/filtering/
|
|
|
|
# The _LLM directory (scripts, logs, config - managed via git, not sync)
|
|
- _LLM/**
|
|
|
|
# Dotfiles and hidden items
|
|
- .*
|
|
|
|
# Office temp/lock files
|
|
- ~`$*
|
|
- *.tmp
|
|
- ~*.tmp
|
|
|
|
# OS generated files
|
|
- Thumbs.db
|
|
- desktop.ini
|
|
- .DS_Store
|
|
- ehthumbs.db
|
|
|
|
# Windows shortcuts
|
|
- *.lnk
|
|
"@
|
|
|
|
# Write without BOM (rclone can't parse BOM)
|
|
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
|
|
[System.IO.File]::WriteAllText($ignoreFile, $ignoreContent, $utf8NoBom)
|
|
Write-Host "Sync ignore rules written to $ignoreFile" -ForegroundColor Green
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 5: Create logs directory
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 5 "Setting up logging"
|
|
|
|
if (-not (Test-Path $logsPath)) {
|
|
New-Item -ItemType Directory -Path $logsPath -Force | Out-Null
|
|
}
|
|
Write-Host "Logs directory: $logsPath" -ForegroundColor Green
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Step 6: Create scheduled task
|
|
# --------------------------------------------------------------------------
|
|
Write-Step 6 "Creating scheduled task"
|
|
|
|
$taskName = "JingTian-Sync"
|
|
|
|
# Remove existing task if present
|
|
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
if ($existing) {
|
|
Write-Host "Removing existing task '$taskName'..." -ForegroundColor Yellow
|
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
|
}
|
|
|
|
# Build the action - run sync.ps1 via PowerShell
|
|
$action = New-ScheduledTaskAction `
|
|
-Execute "powershell.exe" `
|
|
-Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$syncScript`" -SyncRoot `"$SyncRoot`"" `
|
|
-WorkingDirectory $SyncRoot
|
|
|
|
# Trigger: every N minutes, indefinitely
|
|
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
|
|
-RepetitionInterval (New-TimeSpan -Minutes ([int]$interval)) `
|
|
-RepetitionDuration (New-TimeSpan -Days 9999)
|
|
|
|
# Settings
|
|
$settings = New-ScheduledTaskSettingsSet `
|
|
-AllowStartIfOnBatteries `
|
|
-DontStopIfGoingOnBatteries `
|
|
-StartWhenAvailable `
|
|
-RunOnlyIfNetworkAvailable `
|
|
-MultipleInstances IgnoreNew
|
|
|
|
# Register as SYSTEM so it runs even when user is logged out
|
|
Register-ScheduledTask `
|
|
-TaskName $taskName `
|
|
-Action $action `
|
|
-Trigger $trigger `
|
|
-Settings $settings `
|
|
-RunLevel Highest `
|
|
-User "SYSTEM" `
|
|
-Description "JingTian LLM - Sync files with Ubuntu server every $interval minutes" `
|
|
| Out-Null
|
|
|
|
Write-Host "Scheduled task '$taskName' created (every $interval minutes)" -ForegroundColor Green
|
|
|
|
# ============================================================================
|
|
# Done
|
|
# ============================================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Green
|
|
Write-Host " Setup Complete!" -ForegroundColor Green
|
|
Write-Host "========================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host "Sync will run automatically every $interval minutes." -ForegroundColor White
|
|
Write-Host "Logs are saved to: $logsPath" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host "To sync manually, run:" -ForegroundColor Gray
|
|
Write-Host " powershell -File `"$syncScript`" -SyncRoot `"$SyncRoot`"" -ForegroundColor Gray
|
|
Write-Host ""
|