用 Rclone 自动备份小狼毫配置到 WebDAV
更新:
1. Rclone 配置 webdav
scoop 下载 rclone
scoop install rclonerclone versionrclone config我用的是 openlist 自建的 webdav,其他 webdav 参考官方规范填写
name: listtype: webdavurl: https://list.miuo.me/dav/vendor: otheruser: 你的 OpenList 用户名password: 你的 OpenList 密码bearer_token>Enter跳过即可Edit advanced config?nKeep this "list" remote?yq测试
rclone lsd list:出现全部列表
PS C:\Users\Sakura> rclone lsd list: -1 2026-03-04 18:22:52 -1 infini-cloud -1 2026-03-29 20:15:30 -1 Cloudflare R2 -1 2026-05-01 12:09:55 -1 mihon -1 2026-05-01 12:20:14 -1 mistn -1 2026-01-19 13:10:05 -1 onedrive -1 2026-05-01 12:21:05 -1 private -1 2026-01-19 13:31:07 -1 quarkPS C:\Users\Sakura>2. 创建 Rime 备份脚本
ps1脚本,按需自行修改
$ErrorActionPreference = "Stop"
# Paths$Source = "C:\Users\xxx\AppData\Roaming\Rime"$TempDir = "C:\Users\xxx\rclone-temp"$LogDir = "C:\Users\xxx\rclone-logs"$RemoteDir = "list:backup/rime-archives"
# File names$Time = Get-Date -Format "yyyyMMdd-HHmmss"$ZipName = "rime-$Time.zip"$ZipPath = Join-Path $TempDir $ZipName
# Logs$LogFile = Join-Path $LogDir "rime-backup.log"$RcloneLogFile = Join-Path $LogDir "rime-rclone.log"
# Create foldersNew-Item -ItemType Directory -Force -Path $TempDir | Out-NullNew-Item -ItemType Directory -Force -Path $LogDir | Out-Null
function Write-Log { param([string]$Message)
$Now = Get-Date -Format "yyyy-MM-dd HH:mm:ss" "[$Now] $Message" | Out-File -FilePath $LogFile -Append -Encoding utf8}
function Write-Step { param([string]$Message)
$Now = Get-Date -Format "HH:mm:ss" Write-Host "[$Now] $Message" -ForegroundColor Cyan Write-Log $Message}
function Write-Ok { param([string]$Message)
$Now = Get-Date -Format "HH:mm:ss" Write-Host "[$Now] OK: $Message" -ForegroundColor Green Write-Log "OK: $Message"}
function Write-Warn { param([string]$Message)
$Now = Get-Date -Format "HH:mm:ss" Write-Host "[$Now] WARN: $Message" -ForegroundColor Yellow Write-Log "WARN: $Message"}
function Write-Fail { param([string]$Message)
$Now = Get-Date -Format "HH:mm:ss" Write-Host "[$Now] ERROR: $Message" -ForegroundColor Red Write-Log "ERROR: $Message"}
function Show-TextProgress { param( [string]$Title, [int]$Current, [int]$Total )
if ($Total -le 0) { return }
$Percent = [math]::Floor(($Current / $Total) * 100) $BarLength = 30 $FilledLength = [math]::Floor($Percent * $BarLength / 100) $EmptyLength = $BarLength - $FilledLength
$Bar = ("#" * $FilledLength) + ("-" * $EmptyLength) Write-Host "`r$Title [$Bar] $Percent% ($Current/$Total files)" -NoNewline -ForegroundColor Yellow}
function Compress-FolderWithTextProgress { param( [string]$SourceDir, [string]$DestinationZip )
Add-Type -AssemblyName System.IO.Compression Add-Type -AssemblyName System.IO.Compression.FileSystem
if (Test-Path $DestinationZip) { Remove-Item $DestinationZip -Force }
$Files = Get-ChildItem -Path $SourceDir -Recurse -File -Force $Total = $Files.Count
if ($Total -eq 0) { throw "No files found in source folder: $SourceDir" }
$Zip = [System.IO.Compression.ZipFile]::Open($DestinationZip, [System.IO.Compression.ZipArchiveMode]::Create)
try { $Index = 0
foreach ($File in $Files) { $Index++
$RelativePath = $File.FullName.Substring($SourceDir.Length).TrimStart("\") $EntryName = $RelativePath -replace "\\", "/"
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile( $Zip, $File.FullName, $EntryName, [System.IO.Compression.CompressionLevel]::Optimal ) | Out-Null
if (($Index % 5 -eq 0) -or ($Index -eq $Total)) { Show-TextProgress -Title "Compressing" -Current $Index -Total $Total } }
Write-Host "" } finally { $Zip.Dispose() }}
Clear-HostWrite-Host ""Write-Host "========================================" -ForegroundColor DarkCyanWrite-Host " Rime Backup to Cloud Storage - Keep 3" -ForegroundColor CyanWrite-Host "========================================" -ForegroundColor DarkCyanWrite-Host ""
Write-Log "========== Rime backup start =========="Write-Log "Source: $Source"Write-Log "Remote: $RemoteDir"Write-Log "ZipPath: $ZipPath"Write-Log "RcloneLog: $RcloneLogFile"
if (!(Test-Path $Source)) { Write-Fail "Rime folder not found: $Source" throw "Rime folder not found: $Source"}
# Detect WeaselServer path$WeaselPath = $null$WeaselProcess = Get-Process -Name "WeaselServer" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($WeaselProcess -and $WeaselProcess.Path) { $WeaselPath = $WeaselProcess.Path Write-Log "Found WeaselServer: $WeaselPath"}
# Fallback pathsif (!$WeaselPath) { $PossiblePaths = @( "C:\Program Files\Rime\weasel-0.17.4\WeaselServer.exe", "C:\Program Files\Rime\weasel-0.16.3\WeaselServer.exe", "C:\Program Files (x86)\Rime\weasel-0.17.4\WeaselServer.exe", "C:\Program Files (x86)\Rime\weasel-0.16.3\WeaselServer.exe", "C:\Program Files\Rime\WeaselServer.exe", "C:\Program Files (x86)\Rime\WeaselServer.exe" )
foreach ($Path in $PossiblePaths) { if (Test-Path $Path) { $WeaselPath = $Path Write-Log "Using fallback WeaselServer path: $WeaselPath" break } }}
$UploadSucceeded = $false
try { Write-Step "[1/6] Stopping Weasel processes..." Get-Process -Name "WeaselServer" -ErrorAction SilentlyContinue | Stop-Process -Force Get-Process -Name "WeaselDeployer" -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 2 Write-Ok "Weasel processes stopped"
Write-Step "[2/6] Preparing temporary zip..." if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force Write-Log "Old temp zip removed" } Write-Ok "Temp path ready"
Write-Step "[3/6] Compressing Rime folder..." Compress-FolderWithTextProgress -SourceDir $Source -DestinationZip $ZipPath
$ZipSizeMB = [math]::Round((Get-Item $ZipPath).Length / 1MB, 2) Write-Ok "Compress done: $ZipName ($ZipSizeMB MB)"
Write-Step "[4/6] Uploading backup to cloud storage..." Write-Host "File size: $ZipSizeMB MB" -ForegroundColor DarkGray Write-Host ""
rclone copy $ZipPath $RemoteDir ` --progress ` --stats 1s ` --stats-one-line ` --log-file $RcloneLogFile ` --log-level INFO
if ($LASTEXITCODE -ne 0) { throw "rclone upload failed with exit code $LASTEXITCODE" }
$UploadSucceeded = $true Write-Ok "Upload done"
Write-Step "[5/6] Keeping only latest 3 backups..." $Files = rclone lsf $RemoteDir --files-only | Where-Object { $_ -like "rime-*.zip" } | Sort-Object -Descending
if ($LASTEXITCODE -ne 0) { throw "rclone lsf failed with exit code $LASTEXITCODE" }
$FilesToDelete = $Files | Select-Object -Skip 3
if ($FilesToDelete.Count -eq 0) { Write-Ok "No old backups need deleting" } else { foreach ($File in $FilesToDelete) { Write-Warn "Deleting old backup: $File"
rclone deletefile "$RemoteDir/$File" ` --log-file $RcloneLogFile ` --log-level INFO
if ($LASTEXITCODE -ne 0) { throw "rclone deletefile failed for $File with exit code $LASTEXITCODE" } }
Write-Ok "Old backups deleted" }
Write-Step "[6/6] Cleaning local temp file..."
if ($UploadSucceeded -and (Test-Path $ZipPath)) { Remove-Item $ZipPath -Force Write-Ok "Local temp zip removed" } else { Write-Warn "Upload failed, local zip kept: $ZipPath" }
Write-Host "" Write-Host "Backup success!" -ForegroundColor Green Write-Host "Remote folder: $RemoteDir" -ForegroundColor DarkGray Write-Host "Script log: $LogFile" -ForegroundColor DarkGray Write-Host "Rclone log: $RcloneLogFile" -ForegroundColor DarkGray Write-Host ""
Write-Log "========== Rime backup success =========="}catch { Write-Fail "Backup failed: $($_.Exception.Message)" Write-Log "BACKUP FAILED: $($_.Exception.Message)"
if (Test-Path $ZipPath) { Write-Warn "Local zip kept for retry: $ZipPath" Write-Log "Local zip kept for retry: $ZipPath" }
throw}finally { if ($WeaselPath -and (Test-Path $WeaselPath)) { Write-Step "Restarting WeaselServer..." Start-Process $WeaselPath Write-Ok "WeaselServer started" } else { Write-Warn "WeaselServer path not found, skipped restart" }
Write-Log "========== Rime backup end =========="}3. 设置 windows 定时备份任务
每周日 22
跑一次,错过后下次开机补跑,失败后每 10 分钟重试一次最多 3 次,超过 1 小时停止,不启动重复实例$TaskName = "Backup Rime to R2 Keep3"$ScriptPath = "C:\Users\Sakura\Scripts\backup_rime_to_r2_keep3.ps1"
# Delete old task if existsschtasks /Delete /TN $TaskName /F 2>$null
# Create action$Action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`""
# Run every Sunday at 22:10$Trigger = New-ScheduledTaskTrigger ` -Weekly ` -DaysOfWeek Sunday ` -At 22:10
# Settings$Settings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 10) ` -ExecutionTimeLimit (New-TimeSpan -Hours 1) ` -MultipleInstances IgnoreNew
# Register taskRegister-ScheduledTask ` -TaskName $TaskName ` -Action $Action ` -Trigger $Trigger ` -Settings $Settings ` -Description "Backup Rime config to Cloudflare R2 weekly, keep latest 3 archives." ` -Force检查
schtasks /Query /TN "Backup Rime to R2 Keep3" /V /FO LIST4. Rime 具体配置
4.1 使用 [ / ] 切换候选项
配置文件路径:
C:\Users\Sakura\AppData\Roaming\Rime\default.custom.yaml如果没有 default.custom.yaml 就新建一个。不要直接改 default.yaml,更新方案时更容易被覆盖。
patch: # 原本 [ ] 被 Lua 的“以词定字”占用 # 如果要用 [ ] 翻页,需要先把“以词定字”改成别的快捷键 key_binder/select_first_character: "Control+bracketleft" key_binder/select_last_character: "Control+bracketright"
key_binder/bindings/+: - { when: has_menu, accept: bracketleft, send: Page_Up } - { when: has_menu, accept: bracketright, send: Page_Down }4.2 小狼毫皮肤配置

配置文件路径:
C:\Users\Sakura\AppData\Roaming\Rime\weasel.custom.yaml如果没有 weasel.custom.yaml 就新建一个。不要直接改 weasel.yaml,更新配置时更容易被覆盖。
patch: style/color_scheme: purity_of_form_custom
preset_color_schemes/purity_of_form_custom: name: "uo" author: "miuo"
# 小狼毫颜色是 BGR,不是网页常见的 RGB;黑白灰不受影响。 text_color: 0x000000 back_color: 0xFFFFFF border_color: 0xE5E5E5 shadow_color: 0x20000000
label_color: 0x666666 candidate_text_color: 0x333333 comment_text_color: 0x888888
hilited_text_color: 0x000000 hilited_candidate_text_color: 0x000000 hilited_candidate_label_color: 0x333333 hilited_comment_text_color: 0x666666 hilited_candidate_back_color: 0xFFFFFF hilited_candidate_border_color: 0xFFFFFF
# 左侧高亮标记,0xD47800 对应接近 Windows 蓝色。 hilited_mark_color: 0xD47800重新部署即可
5. 雾凇词库更新
git statusgit pull重新部署即可
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。