Add Windows support: linker, oh-my-posh installer, README

- link-dotfiles.ps1: stow-equivalent for Windows using junctions/symlinks
  (no admin required), idempotent with .bak backups and -DryRun
- lk-installers/install-ohmyposh.ps1: winget-based install with fallback
- README.md: document Linux/macOS (stow) and Windows flows
- .gitignore: resolve leftover merge conflict markers, ignore *.bak
- ohmyposh/lkraven.toml: oh-my-posh v3 auto-migrated this on load (same
  theme, upgraded schema)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
lkrav
2026-06-08 13:22:33 -07:00
co-authored by Claude Opus 4.8
parent 1b1a1d4bf9
commit 77a8d25488
5 changed files with 281 additions and 78 deletions
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Windows equivalent of ./link-dotfiles (the GNU stow script).
.DESCRIPTION
stow can't run natively on Windows, so this mirrors what it does:
home_root/* -> ~\<name> (e.g. ~\.zshrc)
home_config/* -> ~\.config\<name> (e.g. ~\.config\ohmyposh)
Directories are linked with junctions and files with symlinks/hardlinks,
none of which need administrator rights (junctions and hardlinks never do;
symlinks are attempted first in case Developer Mode is on, then we fall
back). Re-running is safe: correct links are skipped, and any real file or
directory that would be clobbered is moved aside to <name>.bak first.
.EXAMPLE
pwsh -File .\link-dotfiles.ps1
#>
[CmdletBinding()]
param(
# Print what would happen without changing anything.
[switch]$DryRun
)
$ErrorActionPreference = 'Stop'
$repo = $PSScriptRoot
$home_ = $HOME
# stow metadata files we must never link into the home tree.
$skip = @('.stow-local-ignore', '.stow-global-ignore')
function Get-LinkTarget {
param([string]$Path)
$item = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
if ($item -and $item.LinkType) { return $item.Target }
return $null
}
function Link-Item {
param(
[string]$Source, # absolute path inside the repo
[string]$Dest # absolute path under the home tree
)
$isDir = (Get-Item -LiteralPath $Source -Force).PSIsContainer
# Already linked to the right place? Nothing to do.
$existingTarget = Get-LinkTarget $Dest
if ($existingTarget -and ($existingTarget -ieq $Source)) {
Write-Host " ok $Dest" -ForegroundColor DarkGray
return
}
if (Test-Path -LiteralPath $Dest) {
if ($existingTarget) {
# A link, but pointing elsewhere -> replace it.
if ($DryRun) { Write-Host " relink $Dest (was -> $existingTarget)" -ForegroundColor Yellow; return }
Remove-Item -LiteralPath $Dest -Force -Recurse
} else {
# A real file/dir -> back it up rather than destroy it.
$bak = "$Dest.bak"
Write-Host " backup $Dest -> $bak" -ForegroundColor Yellow
if (-not $DryRun) { Move-Item -LiteralPath $Dest -Destination $bak -Force }
}
}
if ($DryRun) { Write-Host " link $Dest -> $Source" -ForegroundColor Cyan; return }
$parent = Split-Path $Dest -Parent
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
if ($isDir) {
New-Item -ItemType Junction -Path $Dest -Target $Source | Out-Null
Write-Host " junc $Dest -> $Source" -ForegroundColor Green
} else {
# Prefer a real symlink (needs admin or Developer Mode); fall back to a
# hardlink (same volume, no admin); finally just copy.
try {
New-Item -ItemType SymbolicLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null
Write-Host " link $Dest -> $Source" -ForegroundColor Green
} catch {
try {
New-Item -ItemType HardLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null
Write-Host " hard $Dest -> $Source" -ForegroundColor Green
} catch {
Copy-Item -LiteralPath $Source -Destination $Dest -Force
Write-Host " copy $Dest (could not link; copied)" -ForegroundColor Magenta
}
}
}
}
Write-Host "home_root -> $home_"
Get-ChildItem -LiteralPath (Join-Path $repo 'home_root') -Force |
Where-Object { $skip -notcontains $_.Name } |
ForEach-Object { Link-Item $_.FullName (Join-Path $home_ $_.Name) }
$configDir = Join-Path $home_ '.config'
Write-Host "home_config -> $configDir"
Get-ChildItem -LiteralPath (Join-Path $repo 'home_config') -Force |
Where-Object { $skip -notcontains $_.Name } |
ForEach-Object { Link-Item $_.FullName (Join-Path $configDir $_.Name) }
Write-Host "Done." -ForegroundColor Green