docker-install.ps1 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <#
  2. .SYNOPSIS
  3. BamBuddy Docker installation script for Windows (Docker Desktop).
  4. .DESCRIPTION
  5. Mirrors install/docker-install.sh for Windows. Verifies Docker Desktop is
  6. installed and running, downloads docker-compose.yml, rewrites it for
  7. Docker Desktop (no host networking), writes a .env, and starts the
  8. container.
  9. .PARAMETER InstallPath
  10. Installation directory. Default: $env:USERPROFILE\bambuddy
  11. .PARAMETER Port
  12. Port to expose. Default: 8000
  13. .PARAMETER TimeZone
  14. IANA timezone string (e.g. Europe/Berlin). Default: derived from
  15. Get-TimeZone or UTC.
  16. .PARAMETER Build
  17. Build the image from source instead of pulling the pre-built image.
  18. .PARAMETER Yes
  19. Non-interactive mode; accept defaults.
  20. .EXAMPLE
  21. Interactive install (cmd or PowerShell):
  22. powershell -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/maziggy/bambuddy/main/install/docker-install.ps1 -OutFile docker-install.ps1; .\docker-install.ps1"
  23. .EXAMPLE
  24. Unattended install:
  25. .\docker-install.ps1 -InstallPath C:\bambuddy -Port 8080 -TimeZone Europe/Berlin -Yes
  26. #>
  27. [CmdletBinding()]
  28. param(
  29. [string]$InstallPath,
  30. [int]$Port,
  31. [string]$TimeZone,
  32. [switch]$Build,
  33. [switch]$Yes,
  34. [switch]$Help
  35. )
  36. $ErrorActionPreference = 'Stop'
  37. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  38. # --- Helpers --------------------------------------------------------------
  39. function Write-Banner {
  40. Write-Host ''
  41. Write-Host '==========================================================' -ForegroundColor Cyan
  42. Write-Host ' Bambuddy - Docker Installation (Windows)' -ForegroundColor Cyan
  43. Write-Host '==========================================================' -ForegroundColor Cyan
  44. Write-Host ''
  45. }
  46. function Info { param($m) Write-Host "[INFO] $m" -ForegroundColor Blue }
  47. function Ok { param($m) Write-Host "[OK] $m" -ForegroundColor Green }
  48. function Warn { param($m) Write-Host "[WARN] $m" -ForegroundColor Yellow }
  49. function Fail { param($m) Write-Host "[ERR] $m" -ForegroundColor Red }
  50. function Read-Default {
  51. param([string]$Prompt, [string]$Default)
  52. if ($Yes) { return $Default }
  53. $val = Read-Host "$Prompt [$Default]"
  54. if ([string]::IsNullOrWhiteSpace($val)) { return $Default }
  55. return $val
  56. }
  57. function Read-YesNo {
  58. param([string]$Prompt, [string]$Default = 'y')
  59. if ($Yes) { return ($Default -eq 'y') }
  60. $hint = if ($Default -eq 'y') { '[Y/n]' } else { '[y/N]' }
  61. while ($true) {
  62. $val = Read-Host "$Prompt $hint"
  63. if ([string]::IsNullOrWhiteSpace($val)) { $val = $Default }
  64. switch -Regex ($val.Trim().ToLower()) {
  65. '^(y|yes)$' { return $true }
  66. '^(n|no)$' { return $false }
  67. default { Write-Host 'Please answer yes or no.' }
  68. }
  69. }
  70. }
  71. function Show-Help {
  72. Get-Help $PSCommandPath -Full
  73. exit 0
  74. }
  75. # --- Detection ------------------------------------------------------------
  76. function Test-Docker {
  77. if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
  78. Fail 'Docker is not installed.'
  79. Write-Host ''
  80. Write-Host ' Install Docker Desktop for Windows:' -ForegroundColor Yellow
  81. Write-Host ' https://www.docker.com/products/docker-desktop' -ForegroundColor Cyan
  82. Write-Host ''
  83. Write-Host ' After install, launch Docker Desktop and re-run this script.' -ForegroundColor Yellow
  84. exit 1
  85. }
  86. Info 'Docker found, checking daemon...'
  87. try {
  88. docker info --format '{{.ServerVersion}}' *> $null
  89. if ($LASTEXITCODE -ne 0) { throw 'docker info failed' }
  90. } catch {
  91. Fail 'Docker daemon is not reachable. Is Docker Desktop running?'
  92. Write-Host ''
  93. Write-Host ' Open Docker Desktop, wait for the whale icon to settle,' -ForegroundColor Yellow
  94. Write-Host ' then re-run this script.' -ForegroundColor Yellow
  95. exit 1
  96. }
  97. Ok 'Docker daemon is running'
  98. docker compose version *> $null
  99. if ($LASTEXITCODE -eq 0) {
  100. $script:DockerCompose = 'docker compose'
  101. Ok 'Found Docker Compose v2'
  102. } elseif (Get-Command docker-compose -ErrorAction SilentlyContinue) {
  103. $script:DockerCompose = 'docker-compose'
  104. Ok 'Found Docker Compose v1'
  105. } else {
  106. Fail 'Docker Compose not found. Install Docker Desktop 4.x+ (ships compose v2).'
  107. exit 1
  108. }
  109. }
  110. function Get-DefaultTimeZone {
  111. if ($TimeZone) { return $TimeZone }
  112. try {
  113. $tz = (Get-TimeZone).Id
  114. # Get-TimeZone returns Windows IDs ("W. Europe Standard Time"). Docker
  115. # expects IANA. Fall back to UTC if we can't translate.
  116. $iana = ConvertTo-IanaTimeZone $tz
  117. if ($iana) { return $iana }
  118. } catch {}
  119. return 'UTC'
  120. }
  121. function ConvertTo-IanaTimeZone {
  122. param([string]$WindowsId)
  123. # Minimal mapping of common Windows IDs to IANA. Users can override via -TimeZone.
  124. $map = @{
  125. 'UTC' = 'UTC'
  126. 'GMT Standard Time' = 'Europe/London'
  127. 'W. Europe Standard Time' = 'Europe/Berlin'
  128. 'Central European Standard Time' = 'Europe/Warsaw'
  129. 'Romance Standard Time' = 'Europe/Paris'
  130. 'Russian Standard Time' = 'Europe/Moscow'
  131. 'Eastern Standard Time' = 'America/New_York'
  132. 'Central Standard Time' = 'America/Chicago'
  133. 'Mountain Standard Time' = 'America/Denver'
  134. 'Pacific Standard Time' = 'America/Los_Angeles'
  135. 'AUS Eastern Standard Time' = 'Australia/Sydney'
  136. 'Tokyo Standard Time' = 'Asia/Tokyo'
  137. 'China Standard Time' = 'Asia/Shanghai'
  138. 'India Standard Time' = 'Asia/Kolkata'
  139. }
  140. return $map[$WindowsId]
  141. }
  142. function Get-LanIp {
  143. try {
  144. $ip = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop |
  145. Where-Object {
  146. $_.IPAddress -notmatch '^(127\.|169\.254\.)' -and
  147. $_.PrefixOrigin -ne 'WellKnown' -and
  148. $_.InterfaceAlias -notmatch '^(vEthernet|Loopback)'
  149. } | Select-Object -First 1
  150. if ($ip) { return $ip.IPAddress }
  151. } catch {}
  152. return '<your-ip>'
  153. }
  154. # --- Steps ----------------------------------------------------------------
  155. function Initialize-InstallDir {
  156. Info "Creating installation directory: $InstallPath"
  157. if (-not (Test-Path $InstallPath)) {
  158. New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
  159. }
  160. Set-Location $InstallPath
  161. Ok "Directory ready: $InstallPath"
  162. }
  163. function Get-ComposeFile {
  164. Info 'Downloading docker-compose.yml...'
  165. if ($Build) {
  166. if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
  167. Fail 'git is required for --Build but was not found. Install Git for Windows: https://git-scm.com/download/win'
  168. exit 1
  169. }
  170. if (Test-Path '.git') {
  171. Info 'Existing repository found, updating...'
  172. git fetch origin
  173. git reset --hard origin/main
  174. } else {
  175. git clone https://github.com/maziggy/bambuddy.git .
  176. }
  177. } else {
  178. Invoke-WebRequest `
  179. -Uri 'https://raw.githubusercontent.com/maziggy/bambuddy/main/docker-compose.yml' `
  180. -OutFile 'docker-compose.yml' `
  181. -UseBasicParsing
  182. }
  183. Ok 'docker-compose.yml ready'
  184. }
  185. function Write-EnvFile {
  186. Info 'Writing .env...'
  187. $envBody = @"
  188. # BamBuddy Docker Configuration
  189. # Generated by docker-install.ps1 on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
  190. PORT=$Port
  191. TZ=$TimeZone
  192. "@
  193. [System.IO.File]::WriteAllText((Join-Path $InstallPath '.env'), $envBody)
  194. Ok '.env written'
  195. }
  196. function Update-ComposeForDockerDesktop {
  197. # Docker Desktop on Windows does not support network_mode: host. Comment
  198. # it out and uncomment the port mappings. Mirrors what the bash script
  199. # does on macOS via sed.
  200. $path = Join-Path $InstallPath 'docker-compose.yml'
  201. Info 'Rewriting compose for Docker Desktop (no host networking)...'
  202. $content = [System.IO.File]::ReadAllText($path)
  203. Copy-Item $path "$path.bak" -Force
  204. $content = $content -replace '(?m)^(\s*)network_mode: host', '#$1network_mode: host'
  205. $content = $content -replace '(?m)^(\s*)#ports:', '$1ports:'
  206. $content = $content -replace '(?m)^(\s*)#(\s*)- "\$\{PORT:-8000\}:8000"', '$1$2- "$${PORT:-8000}:8000"'
  207. [System.IO.File]::WriteAllText($path, $content)
  208. Warn 'Printer auto-discovery (SSDP) does NOT work on Docker Desktop. Add printers manually by IP.'
  209. Warn 'Virtual Printer ports (322, 990, 2024-2026, 3000/3002, 6000, 8883, 50000-50100) stay commented out.'
  210. Warn 'If you plan to use a Virtual Printer, edit docker-compose.yml and uncomment the relevant `- "PORT:PORT"` lines under `ports:`.'
  211. }
  212. function Start-Bambuddy {
  213. Info 'Starting Bambuddy container...'
  214. if ($Build) {
  215. & cmd /c "$DockerCompose up -d --build"
  216. } else {
  217. & cmd /c "$DockerCompose up -d"
  218. }
  219. if ($LASTEXITCODE -ne 0) {
  220. Fail 'Container start failed. Inspect logs with:'
  221. Write-Host " cd $InstallPath" -ForegroundColor Yellow
  222. Write-Host " $DockerCompose logs bambuddy" -ForegroundColor Yellow
  223. exit 1
  224. }
  225. Info 'Waiting for container to be Up...'
  226. $attempts = 0
  227. while ($attempts -lt 15) {
  228. Start-Sleep -Seconds 2
  229. $ps = & cmd /c "$DockerCompose ps" 2>&1
  230. if ($ps -match 'Up') { Ok 'Bambuddy container is running'; return }
  231. if ($ps -match 'Exited') {
  232. Fail 'Container exited unexpectedly.'
  233. Write-Host " $DockerCompose logs bambuddy" -ForegroundColor Yellow
  234. exit 1
  235. }
  236. $attempts++
  237. }
  238. Warn "Container may still be starting. Check with: $DockerCompose ps"
  239. }
  240. # --- Main -----------------------------------------------------------------
  241. if ($Help) { Show-Help }
  242. Write-Banner
  243. Info 'Detecting environment...'
  244. Test-Docker
  245. # Defaults
  246. if (-not $InstallPath) { $InstallPath = Read-Default 'Installation directory' (Join-Path $env:USERPROFILE 'bambuddy') }
  247. if (-not $Port) { $Port = [int](Read-Default 'Port to expose' '8000') }
  248. if (-not $TimeZone) {
  249. $detected = Get-DefaultTimeZone
  250. $TimeZone = Read-Default 'Timezone (IANA)' $detected
  251. }
  252. if (-not $Build -and -not $Yes) {
  253. if (Read-YesNo 'Build from source? (No = use pre-built image)' 'n') { $Build = $true }
  254. }
  255. Write-Host ''
  256. Write-Host 'Installation Summary' -ForegroundColor Cyan
  257. Write-Host '-----------------------------------------'
  258. Write-Host " Install path: $InstallPath"
  259. Write-Host " Port: $Port"
  260. Write-Host " Timezone: $TimeZone"
  261. Write-Host " Build source: $Build"
  262. Write-Host ''
  263. if (-not (Read-YesNo 'Proceed with installation?' 'y')) {
  264. Write-Host 'Cancelled.' -ForegroundColor Yellow
  265. exit 0
  266. }
  267. Initialize-InstallDir
  268. Get-ComposeFile
  269. Write-EnvFile
  270. Update-ComposeForDockerDesktop
  271. Start-Bambuddy
  272. $lanIp = Get-LanIp
  273. Write-Host ''
  274. Write-Host '==========================================================' -ForegroundColor Green
  275. Write-Host ' Installation Complete!' -ForegroundColor Green
  276. Write-Host '==========================================================' -ForegroundColor Green
  277. Write-Host ''
  278. Write-Host " Access Bambuddy: http://localhost:$Port" -ForegroundColor Cyan
  279. Write-Host " http://${lanIp}:$Port (from other devices)" -ForegroundColor Cyan
  280. Write-Host ''
  281. Write-Host ' Manage container:'
  282. Write-Host " Status: cd `"$InstallPath`"; $DockerCompose ps"
  283. Write-Host " Logs: cd `"$InstallPath`"; $DockerCompose logs -f bambuddy"
  284. Write-Host " Stop: cd `"$InstallPath`"; $DockerCompose down"
  285. Write-Host " Start: cd `"$InstallPath`"; $DockerCompose up -d"
  286. Write-Host " Restart: cd `"$InstallPath`"; $DockerCompose restart"
  287. Write-Host ''
  288. Write-Host ' Update Bambuddy:'
  289. if ($Build) {
  290. Write-Host " cd `"$InstallPath`"; git pull; $DockerCompose up -d --build"
  291. } else {
  292. Write-Host " cd `"$InstallPath`"; $DockerCompose pull; $DockerCompose up -d"
  293. }
  294. Write-Host ''
  295. Write-Host ' Documentation: https://wiki.bambuddy.cool' -ForegroundColor Cyan
  296. Write-Host ''
  297. Write-Host ' Note: Printer discovery is unavailable on Docker Desktop.' -ForegroundColor Yellow
  298. Write-Host ' Add your printers manually by IP address in the UI.' -ForegroundColor Yellow
  299. Write-Host ''