# rclone-dropbox-bisync Windows setup # Paths: D: (volume label DataStorage) \Dropbox and \rclone-bisync # Paste into PowerShell, or: irm /windows/bootstrap.ps1 | iex $ErrorActionPreference = 'Stop' $GitBase = 'https://git.denkena-consulting.com/f-denkena' $PackRepo = "$GitBase/rclone-dropbox-bisync.git" $RcloneRepo = "$GitBase/rclone.git" $Src = Join-Path $env:LOCALAPPDATA 'rclone-dropbox-bisync\src' $Dest = Join-Path $env:LOCALAPPDATA 'rclone-dropbox-bisync' $Dropbox = 'D:\Dropbox' $Workdir = 'D:\rclone-bisync' # Do not use winget: 0x8a15000f (empty source cache) is common and not recoverable # from this script. Fetch portable MinGit + Go into LOCALAPPDATA instead. $Tools = Join-Path $Dest 'tools' function Prepend-UserPath($Dir) { $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ([string]::IsNullOrEmpty($userPath)) { $userPath = '' } if ($userPath -notlike "*$Dir*") { [Environment]::SetEnvironmentVariable('Path', $(if ($userPath) { "$Dir;$userPath" } else { $Dir }), 'User') } if ($env:Path -notlike "*$Dir*") { $env:Path = "$Dir;$env:Path" } } function Get-File($Url, $Out) { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri $Url -OutFile $Out -UseBasicParsing } function Ensure-Git { if (Get-Command git -ErrorAction SilentlyContinue) { return } $root = Join-Path $Tools 'mingit' $cmd = Join-Path $root 'cmd' if (-not (Test-Path (Join-Path $cmd 'git.exe'))) { Write-Host 'Fetching MinGit...' $rel = Invoke-RestMethod -Uri 'https://api.github.com/repos/git-for-windows/git/releases/latest' $asset = $rel.assets | Where-Object { $_.name -match '^MinGit-.+-64-bit\.zip$' -and $_.name -notmatch 'busybox' } | Select-Object -First 1 if (-not $asset) { throw 'No MinGit-*-64-bit.zip on git-for-windows latest release' } $zip = Join-Path $env:TEMP $asset.name Get-File $asset.browser_download_url $zip New-Item -ItemType Directory -Force -Path $root | Out-Null Expand-Archive -Path $zip -DestinationPath $root -Force } Prepend-UserPath $cmd if (-not (Get-Command git -ErrorAction SilentlyContinue)) { throw "git.exe missing under $cmd" } } function Ensure-Go { if (Get-Command go -ErrorAction SilentlyContinue) { return } $root = Join-Path $Tools 'go' $bin = Join-Path $root 'go\bin' if (-not (Test-Path (Join-Path $bin 'go.exe'))) { Write-Host 'Fetching Go...' $dl = Invoke-RestMethod -Uri 'https://go.dev/dl/?mode=json' $file = $null foreach ($rel in $dl) { $file = $rel.files | Where-Object { $_.os -eq 'windows' -and $_.arch -eq 'amd64' -and $_.kind -eq 'archive' } | Select-Object -First 1 if ($file) { break } } if (-not $file) { throw 'No windows-amd64 Go archive on go.dev/dl' } $zip = Join-Path $env:TEMP $file.filename Get-File "https://go.dev/dl/$($file.filename)" $zip New-Item -ItemType Directory -Force -Path $root | Out-Null Expand-Archive -Path $zip -DestinationPath $root -Force } Prepend-UserPath $bin if (-not (Get-Command go -ErrorAction SilentlyContinue)) { throw "go.exe missing under $bin" } } Ensure-Git Ensure-Go New-Item -ItemType Directory -Force -Path $Src, (Join-Path $Dest 'bin') | Out-Null if (-not (Test-Path (Join-Path $Src 'packaging\.git'))) { git clone --depth 1 $PackRepo (Join-Path $Src 'packaging') } else { git -C (Join-Path $Src 'packaging') pull --ff-only } if (-not (Test-Path (Join-Path $Src 'rclone\.git'))) { git clone --depth 1 --branch bisync-delta-list $RcloneRepo (Join-Path $Src 'rclone') } else { git -C (Join-Path $Src 'rclone') fetch origin bisync-delta-list git -C (Join-Path $Src 'rclone') checkout bisync-delta-list git -C (Join-Path $Src 'rclone') pull --ff-only } Write-Host 'Building rclone (forked)...' Push-Location (Join-Path $Src 'rclone') $env:CGO_ENABLED = '0' go build -trimpath -o (Join-Path $Dest 'bin\rclone.exe') . Pop-Location Write-Host 'Building rclone-bisyncd...' Push-Location (Join-Path $Src 'packaging\cmd\rclone-bisyncd') go build -trimpath -o (Join-Path $Dest 'bin\rclone-bisyncd.exe') . Pop-Location Copy-Item -Force (Join-Path $Src 'packaging\windows\tray.ps1') (Join-Path $Dest 'tray.ps1') if (-not (Test-Path 'D:\')) { throw 'D: is missing (expected DataStorage volume)' } if (-not (Test-Path $Dropbox)) { throw "Dropbox folder not found: $Dropbox" } New-Item -ItemType Directory -Force -Path $Workdir | Out-Null $testFile = Join-Path $Dropbox 'RCLONE_TEST' if (-not (Test-Path $testFile)) { New-Item -ItemType File -Path $testFile | Out-Null } $cfgPath = Join-Path $Dest 'config.json' @{ enabled = $false path1 = $Dropbox path2 = 'dropbox:' workdir = $Workdir interval_minutes = 15 rclone = (Join-Path $Dest 'bin\rclone.exe') check_filename = 'RCLONE_TEST' session_name = 'datastorage-dropbox' } | ConvertTo-Json | Set-Content -Encoding UTF8 $cfgPath $env:RCLONE_BISYNC_CONFIG = $cfgPath $bin = Join-Path $Dest 'bin' $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($userPath -notlike "*$bin*") { [Environment]::SetEnvironmentVariable('Path', "$bin;$userPath", 'User') } $run = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' $tray = Join-Path $Dest 'tray.ps1' New-ItemProperty -Path $run -Name 'rclone-dropbox-bisync-tray' -PropertyType String -Force ` -Value "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$tray`"" | Out-Null $daemon = Join-Path $Dest 'bin\rclone-bisyncd.exe' schtasks /Delete /F /TN 'rclone-dropbox-bisync' 2>$null schtasks /Create /F /TN 'rclone-dropbox-bisync' /SC ONLOGON /RL LIMITED /TR "`"$daemon`"" | Out-Null New-ItemProperty -Path $run -Name 'rclone-dropbox-bisync-daemon' -PropertyType String -Force ` -Value "`"$daemon`"" | Out-Null Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @( '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $tray ) Start-Process $daemon -WindowStyle Hidden Write-Host @" Configured: path1 $Dropbox (D: DataStorage) workdir $Workdir session datastorage-dropbox (shared with Linux) daemon looping; 15 min after each run; mkdir lock If dropbox: has RCLONE_TEST and listings are missing, the daemon dry-run --resync then live --resync on its own. Never after a failed run. "@