#Requires -Version 5.1 <# .SYNOPSIS Installs tmux (terminal multiplexer) and deploys .tmux.conf. .DESCRIPTION Installs tmux via winget (arndawg.tmux-windows) and deploys a customized .tmux.conf to the current user's home directory. Supported platforms: - Windows x64 (AMD64 / Intel 64bit) - Windows ARM64 Prerequisites: - winget (Windows Package Manager) — built into Windows 10 1709+ .EXAMPLE .\install-tmux.ps1 # One-liner install from remote (works on Windows 10+): powershell -ExecutionPolicy Bypass -c "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; $t=\"$env:TEMP\install-tmux.ps1\"; iwr -useb 'https://gl.isitsoft.kr/publicproj/install-sh/-/raw/main/install-tmux.ps1' -OutFile $t; & $t" .NOTES If execution policy error occurs: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser #> [CmdletBinding()] param() Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Force TLS 1.2+ for HTTPS downloads (required on Windows 10 / PowerShell 5.1) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # ──────────────────────────────────────────── # Utility functions # ──────────────────────────────────────────── function Info { Write-Host "[INFO] $args" -ForegroundColor Cyan } function Warn { Write-Host "[WARN] $args" -ForegroundColor Yellow } function Success { Write-Host "[OK] $args" -ForegroundColor Green } function ErrExit { Write-Host "[ERROR] $args" -ForegroundColor Red; exit 1 } # ──────────────────────────────────────────── # Constants # ──────────────────────────────────────────── $ConfFile = Join-Path $env:USERPROFILE '.tmux.conf' $LocalBin = Join-Path $env:USERPROFILE '.local\bin' # Ensure $LocalBin directory exists if (-not (Test-Path $LocalBin)) { New-Item -ItemType Directory -Path $LocalBin -Force | Out-Null } # ──────────────────────────────────────────── # Step 0: Ensure OpenSSH Server (비활성화됨) # ──────────────────────────────────────────── # Info "Checking OpenSSH Server installation status..." # $sshService = Get-Service -Name "sshd" -ErrorAction SilentlyContinue # # if (-not $sshService) { # Info "OpenSSH Server is not installed. Attempting installation..." # # # OpenSSH Server 기능 추가 (관리자 권한 필요) # try { # $capability = Get-WindowsCapability -Online -Name "OpenSSH.Server~~~~0.0.1.0" # if ($capability.State -ne "Installed") { # Info "Installing OpenSSH Server via Windows Optional Features..." # Add-WindowsCapability -Online -Name "OpenSSH.Server~~~~0.0.1.0" > $null # Success "OpenSSH Server feature added successfully." # } # # # sshd 서비스 시작 및 자동 실행 설정 # Set-Service -Name "sshd" -StartupType Automatic # Start-Service -Name "sshd" # Success "OpenSSH Server (sshd) service started and configured as Automatic startup." # } catch { # Warn "Failed to auto-install OpenSSH Server: $_" # Warn "This operation may require Administrator privileges." # } # } else { # Success "OpenSSH Server is already installed." # } # ──────────────────────────────────────────── # Step 1: Install tmux (psmux) and copy *.exe to ~/.local/bin # ──────────────────────────────────────────── Info "Installing tmux (psmux)..." $localTmux = Join-Path $LocalBin 'tmux.exe' $localPsmux = Join-Path $LocalBin 'psmux.exe' $needsInstall = $true if (Test-Path $localTmux) { $currentVer = & $localTmux -V 2>$null if (-not $currentVer) { $currentVer = & $localTmux --version 2>$null } if ($currentVer) { Info "tmux is already installed in $LocalBin ($currentVer)." $needsInstall = $false } } if ($needsInstall) { $wingetAvailable = [bool](Get-Command winget -ErrorAction SilentlyContinue) $installed = $false if ($wingetAvailable) { try { Info "Running winget install psmux..." winget install psmux --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null Success "tmux (psmux) installed via winget" $installed = $true } catch { Warn "winget install failed: $_" } } # Fallback to direct download from GitHub releases if winget is missing or failed if (-not $installed) { Info "Downloading psmux directly from GitHub releases..." $arch = $env:PROCESSOR_ARCHITECTURE $psmuxZipAsset = if ($arch -eq "ARM64") { "psmux-v3.3.8-windows-arm64.zip" } else { "psmux-v3.3.8-windows-x64.zip" } $downloadUrl = "https://github.com/psmux/psmux/releases/download/v3.3.8/$psmuxZipAsset" try { $latestRel = Invoke-RestMethod -Uri "https://api.github.com/repos/psmux/psmux/releases/latest" -UseBasicParsing -TimeoutSec 5 $assetPattern = if ($arch -eq "ARM64") { "psmux-*-windows-arm64.zip" } else { "psmux-*-windows-x64.zip" } $matchedAsset = $latestRel.assets | Where-Object { $_.name -like $assetPattern } | Select-Object -First 1 if ($matchedAsset) { $downloadUrl = $matchedAsset.browser_download_url } } catch { # Use fallback URL } $tmpZip = Join-Path $env:TEMP "psmux_$(Get-Random).zip" try { $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri $downloadUrl -OutFile $tmpZip -UseBasicParsing $ProgressPreference = 'Continue' Add-Type -AssemblyName System.IO.Compression.FileSystem $archive = [System.IO.Compression.ZipFile]::OpenRead($tmpZip) foreach ($entry in $archive.Entries) { if ($entry.Name.EndsWith(".exe", [System.StringComparison]::OrdinalIgnoreCase)) { $destPath = Join-Path $LocalBin $entry.Name [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destPath, $true) Success "Extracted $($entry.Name) to $LocalBin" } } $archive.Dispose() $installed = $true } catch { Warn "Failed to download psmux archive: $_" } finally { if (Test-Path $tmpZip) { Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue } } } # Search for winget-installed *.exe files and copy them to $LocalBin $searchDirs = @( (Join-Path $env:LOCALAPPDATA 'Programs\psmux'), (Join-Path $env:LOCALAPPDATA 'Programs\tmux-windows'), (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Links'), 'C:\Program Files\psmux', 'C:\Program Files\tmux-windows', 'C:\Program Files (x86)\psmux', 'C:\Program Files (x86)\tmux-windows' ) $wingetPackages = Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages' if (Test-Path $wingetPackages) { $pkgDirs = Get-ChildItem -Path $wingetPackages -Directory -Filter "*psmux*" -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName if ($pkgDirs) { $searchDirs += $pkgDirs } } foreach ($dir in $searchDirs) { if (Test-Path $dir) { $candidateExes = Get-ChildItem -Path $dir -Filter "*.exe" -ErrorAction SilentlyContinue if ($candidateExes) { foreach ($exe in $candidateExes) { if ($exe.Name -in @('tmux.exe', 'psmux.exe', 'pmux.exe')) { Copy-Item -Path $exe.FullName -Destination (Join-Path $LocalBin $exe.Name) -Force Success "Copied $($exe.Name) to $LocalBin" } } } } if (Test-Path $localTmux) { break } } # If tmux.exe is still not in $LocalBin but psmux.exe is, create tmux.exe copy if (-not (Test-Path $localTmux) -and (Test-Path $localPsmux)) { Copy-Item -Path $localPsmux -Destination $localTmux -Force Success "Created tmux.exe alias from psmux.exe in $LocalBin" } } # ──────────────────────────────────────────── # Verify tmux in $LocalBin and add $LocalBin to PATH permanently # ──────────────────────────────────────────── $tmuxBin = $null if (Test-Path $localTmux) { $tmuxBin = $localTmux } elseif (Test-Path $localPsmux) { $tmuxBin = $localPsmux } # Add $LocalBin to current session PATH if ($env:Path -notlike "*$LocalBin*") { $env:Path = "$LocalBin;$env:Path" Info "Added $LocalBin to PATH for this session" } # Permanently add $LocalBin to User PATH if not already present $userPath = [System.Environment]::GetEnvironmentVariable('Path', 'User') if (-not $userPath) { $userPath = "" } $pathParts = $userPath -split ';' | Where-Object { $_ -ne '' } $normalizedLocalBin = $LocalBin.TrimEnd('\') $alreadyInUserPath = $false foreach ($p in $pathParts) { if ($p.Trim().TrimEnd('\') -eq $normalizedLocalBin) { $alreadyInUserPath = $true break } } if (-not $alreadyInUserPath) { $newUserPath = if ($userPath) { "$normalizedLocalBin;$userPath" } else { $normalizedLocalBin } [System.Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User') Success "Added $LocalBin to User PATH permanently" } else { Info "$LocalBin is already in User PATH" } if ($tmuxBin) { Success "tmux is available: $tmuxBin" } else { Warn "tmux was not found in $LocalBin after installation." Warn "Please check if winget or manual installation succeeded." } # ──────────────────────────────────────────── # Step 2: Deploy .tmux.conf # ──────────────────────────────────────────── if (Test-Path $ConfFile) { $timestamp = Get-Date -Format 'yyyyMMddHHmmss' $backup = "${ConfFile}.bak.${timestamp}" Copy-Item $ConfFile $backup Warn "Existing .tmux.conf found at $ConfFile" Info "Backup saved to $backup" } $tmuxConf = @' # .tmux.conf - tmux-windows compatible # # Based on: https://github.com/arndawg/tmux-windows/blob/master/example_tmux.conf # Prefix key (default is Ctrl-b, changed to Ctrl-a) set -g prefix C-a unbind C-b bind C-a send-prefix # Ctrl + k를 누르면 화면을 깨끗이 지우고 tmux 이전 버퍼(히스토리)도 완전히 삭제 bind -n C-k clear-history # 터미널 설정 (색상 설정 비활성화) #set -g default-terminal "tmux-256color" #set-option -sa terminal-features ",xterm*:RGB" # No bells set -g bell-action none # Split panes with current path bind | split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" unbind '"' unbind % # Pane navigation (vim-style) bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # Window navigation bind -r C-h previous-window bind -r C-l next-window # Layout bind Space next-layout bind o rotate-window # Window management bind , command-prompt "rename-window %%" bind & kill-window # Reload config bind r source-file ~/.tmux.conf \; display "Reloaded!" # 상태 표시줄 (색상 설정 비활성화) #set -g status-bg colour235 #set -g status-fg colour136 set -g status-left-length 40 set -g status-right-length 40 set -g status-interval 5 set -g history-limit 10000 # 상태 표시줄 텍스트 색상 비활성화 #set -g status-left '#[fg=green]#S #[fg=yellow]#H' #set -g status-right '#[fg=cyan]%Y-%m-%d #[fg=green]%H:%M:%S' # 패널 테두리 색상 (색상 설정 비활성화) #set-option -g pane-border-style 'fg=colour244' #set-option -g pane-active-border-style 'fg=colour77' # Copy mode with vi keys setw -g mode-keys vi # Mouse support (without copy mode dragging) set -g mouse on unbind -n MouseDrag1Pane unbind -Tcopy-mode MouseDrag1Pane # for pi set -g extended-keys on '@ [System.IO.File]::WriteAllText($ConfFile, $tmuxConf, [System.Text.UTF8Encoding]::new($false)) Success ".tmux.conf deployed to $ConfFile" # ──────────────────────────────────────────── # Step 3: Post-install notes # ──────────────────────────────────────────── Info "" Info "==================================================" Success "tmux installation complete!" Info "==================================================" if ($tmuxBin) { Info "" Info "tmux binary: $tmuxBin" } Info "" Info "IMPORTANT: Close this terminal and open a new one to use tmux." Info "PATH changes require a new terminal session to take effect." Info "" Info "RECOMMENDED: On Windows 10, use Windows Terminal instead of CMD.exe." Info "Install it from: winget install Microsoft.WindowsTerminal" Info "Windows Terminal supports true color, Unicode, and tmux panes properly." Info "" Info "Getting started:" Info " tmux - Start a new session" Info " tmux new -s - Start a named session" Info " tmux attach -t - Attach to a session" Info " tmux ls - List sessions" Info "" Info "Key bindings (customized via .tmux.conf):" Info " Ctrl-a | - Split horizontally" Info " Ctrl-a - - Split vertically" Info " Ctrl-a h/j/k/l - Navigate panes (vim-style)" Info " Ctrl-a r - Reload .tmux.conf" Info "" Info "Config file: $ConfFile"