Skip to content

Commit 256349c

Browse files
committed
fix(cli): improve panel password validation and docker error messaging
- Add unicode-aware password character counting in both shell and PowerShell scripts to match stitch-panel's validation (12-char minimum) - Validate password length before hashing and retry on TTY if too short or confirmation mismatches, preventing silent install failures - Capture and surface hash-password stderr to users instead of discarding it, enabling diagnosis of actual failures - Add plain-language Docker error explanations with OS-specific install links and startup steps for Mac, Linux, and Windows - Differentiate between docker missing, compose missing, and daemon not running with targeted guidance for each scenario - Filter docker stdout to extract only argon2 hash lines, ignoring warning text some Docker setups print - Suppress the success tip in hash-password when stdin is not a terminal (install scripts use pipes)
1 parent 5d4c386 commit 256349c

7 files changed

Lines changed: 323 additions & 25 deletions

File tree

.textile-monorepo-source

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
91134999768b376b01ab7022be8a43ba029dad41
1+
5e2592c55a6c3cae76a904c4c544fc7893a455cd

.textile-stitch-release-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.121
1+
0.1.122

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stitch-bot"
3-
version = "0.1.121"
3+
version = "0.1.122"
44
edition = "2021"
55
description = "Stitch — Textile filler-network operator bot; market-makes the filler order book with signed UniswapX limit orders."
66
license = "AGPL-3.0-or-later"

install-panel.ps1

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,50 @@ function Need-Value([string]$Current, [string]$Prompt, [string]$VarName, [switch
147147
return (Read-Host -Prompt $Prompt)
148148
}
149149

150+
# Unicode scalar count — matches stitch-panel's chars().count() (not UTF-16 .Length).
151+
function Get-PasswordCharCount([string]$Password) {
152+
$n = 0
153+
for ($i = 0; $i -lt $Password.Length; $i++) {
154+
$n++
155+
if (
156+
[char]::IsHighSurrogate($Password[$i]) -and
157+
($i + 1) -lt $Password.Length -and
158+
[char]::IsLowSurrogate($Password[$i + 1])
159+
) {
160+
$i++
161+
}
162+
}
163+
return $n
164+
}
165+
166+
function Need-Password([string]$Current, [string]$Prompt, [string]$VarName) {
167+
if ($Current) {
168+
$len = Get-PasswordCharCount $Current
169+
if ($len -lt 12) {
170+
Die "$VarName must be at least 12 characters (got $len)"
171+
}
172+
return $Current
173+
}
174+
if (-not [Environment]::UserInteractive) {
175+
Die "$VarName is not set and there is no interactive session to ask on. Set it and re-run."
176+
}
177+
while ($true) {
178+
$pw = Read-Secret $Prompt
179+
if (-not $pw) { Die 'a panel password is required' }
180+
$len = Get-PasswordCharCount $pw
181+
if ($len -lt 12) {
182+
Write-Warn "need at least 12 characters (got $len). Try again."
183+
continue
184+
}
185+
$again = Read-Secret 'Again'
186+
if ($pw -cne $again) {
187+
Write-Warn "those didn't match. Try again."
188+
continue
189+
}
190+
return $pw
191+
}
192+
}
193+
150194
function Fetch-File([string]$Url, [string]$Dest) {
151195
try {
152196
Invoke-WebRequest -Uri $Url -OutFile $Dest -UseBasicParsing
@@ -156,14 +200,61 @@ function Fetch-File([string]$Url, [string]$Dest) {
156200
}
157201

158202
# ---------------------------------------------------------------- preflight ---
203+
# Plain-language Docker help — many Windows operators have never used containers.
204+
# Use Write-Host (not Write-Error): $ErrorActionPreference=Stop would otherwise
205+
# abort before the "what to do" steps print.
206+
function Explain-Docker([ValidateSet('missing', 'compose', 'not_running')][string]$Reason) {
207+
Write-Host ''
208+
switch ($Reason) {
209+
'missing' {
210+
Write-Host 'error: Docker is not installed on this computer.'
211+
Write-Host ''
212+
Write-Host 'Stitch runs inside Docker — a free app that hosts the web panel.'
213+
Write-Host 'Install it once, then re-run the same install command.'
214+
Write-Host ''
215+
Write-Host 'What to do:'
216+
Write-Host ' 1. Download and install Docker Desktop for Windows:'
217+
Write-Host ' https://docs.docker.com/desktop/setup/install/windows-install/'
218+
Write-Host ' 2. Open Docker Desktop from the Start menu and wait until it says'
219+
Write-Host ' Docker is running (whale icon in the system tray is steady).'
220+
Write-Host ' 3. Open a new PowerShell window and re-run:'
221+
Write-Host ' irm https://raw.githubusercontent.com/textile-protocol/textile-stitch/main/install-panel.ps1 | iex'
222+
}
223+
'compose' {
224+
Write-Host 'error: Docker Compose v2 is missing.'
225+
Write-Host ''
226+
Write-Host 'The docker command is present, but docker compose does not work.'
227+
Write-Host 'Stitch needs Compose v2. Update or reinstall Docker Desktop, then re-run.'
228+
Write-Host ' https://docs.docker.com/desktop/setup/install/windows-install/'
229+
}
230+
'not_running' {
231+
Write-Host 'error: Docker is installed but not running.'
232+
Write-Host ''
233+
Write-Host 'The panel cannot start until Docker Desktop is up.'
234+
Write-Host ''
235+
Write-Host 'What to do:'
236+
Write-Host ' 1. Open Docker Desktop from the Start menu.'
237+
Write-Host ' 2. Wait until it finishes starting (system-tray whale icon steady /'
238+
Write-Host ' Docker Desktop is running).'
239+
Write-Host ' 3. Re-run the install command in PowerShell.'
240+
}
241+
}
242+
Write-Host ''
243+
exit 1
244+
}
245+
159246
Write-Step 'Checking Docker'
160247
if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
161-
Die 'Docker is not installed. Install Docker Desktop for Windows: https://docs.docker.com/desktop/setup/install/windows-install/'
248+
Explain-Docker missing
162249
}
163250
docker compose version | Out-Null
164-
Assert-LastExitOk 'Docker Compose v2 is missing. `docker compose version` has to work.'
251+
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) {
252+
Explain-Docker compose
253+
}
165254
docker info | Out-Null
166-
Assert-LastExitOk 'the Docker daemon is not reachable. Start Docker Desktop and re-run.'
255+
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) {
256+
Explain-Docker not_running
257+
}
167258
Write-Say 'Docker and Compose v2 are ready.'
168259

169260
# Windows Docker Desktop runs Linux containers. Tailscale server compose needs
@@ -287,11 +378,29 @@ if (-not $ReuseEnv) {
287378
}
288379

289380
function Add-Password([string]$Password, [string]$Label = 'password') {
290-
$hash = $Password | docker run --rm -i $PanelImage hash-password 2>$null
291-
Assert-LastExitOk 'hashing the password failed'
292-
if (-not $hash) { Die 'hashing the password failed' }
293-
$hash = ($hash | Select-Object -Last 1).ToString().Trim()
294-
if (-not $hash) { Die 'the panel returned an empty password hash' }
381+
$len = Get-PasswordCharCount $Password
382+
if ($len -lt 12) {
383+
Die "password must be at least 12 characters (got $len)"
384+
}
385+
386+
# Keep stderr: the binary's real reason used to disappear into $null.
387+
$errFile = [System.IO.Path]::GetTempFileName()
388+
try {
389+
$hash = $Password | docker run --rm -i $PanelImage hash-password 2>$errFile
390+
if ($null -ne $LASTEXITCODE -and $LASTEXITCODE -ne 0) {
391+
$msg = @(Get-Content -LiteralPath $errFile -ErrorAction SilentlyContinue |
392+
Where-Object { $_.Trim() } |
393+
ForEach-Object { $_ -replace '^Error:\s*', '' } |
394+
Select-Object -Last 1) -join ''
395+
if ($msg) { Die "hashing the password failed: $msg" }
396+
Die 'hashing the password failed'
397+
}
398+
$hashLine = @($hash) | Where-Object { $_ -match '^\$argon2' } | Select-Object -First 1
399+
if (-not $hashLine) { Die 'the panel returned no argon2 password hash' }
400+
$hash = $hashLine.ToString().Trim()
401+
} finally {
402+
Remove-Item -LiteralPath $errFile -Force -ErrorAction SilentlyContinue
403+
}
295404

296405
$lines = @()
297406
if (Test-Path -LiteralPath $EnvFile) {
@@ -313,8 +422,7 @@ if (-not (Get-EnvFileValue $EnvFile 'PANEL_PASSWORD_HASH')) {
313422
if (-not $env:PANEL_PASSWORD) {
314423
Write-Say 'This install is loopback-only. You log in with a password you choose now.'
315424
}
316-
$password = Need-Value $env:PANEL_PASSWORD 'Panel password (12+ characters, not shown)' 'PANEL_PASSWORD' -Secret
317-
if (-not $password) { Die 'a panel password is required for a local install' }
425+
$password = Need-Password $env:PANEL_PASSWORD 'Panel password (12+ characters, not shown)' 'PANEL_PASSWORD'
318426
Add-Password $password 'panel password'
319427
} elseif ($env:PANEL_PASSWORD) {
320428
Write-Step 'Updating the panel password'

0 commit comments

Comments
 (0)