Skip to content

Commit 8879917

Browse files
committed
feat(windows): add install.ps1 / run.ps1 + Windows CI smoke
Brings the offline Python tutor to Windows. install.ps1 and run.ps1 are PowerShell counterparts to install.sh / run.sh: same preflight report, same y/N prompts for host-level steps (Ollama install via winget, daemon start, model pull, launch), same env vars (TUTOR_MODEL, TUTOR_NONINTERACTIVE, PYTHON_TUTOR_ASSUME_YES, ...). Defaults to "no" on every host-changing step; never installs silently. A new windows-latest CI job parses both .ps1 files, exercises -Help, runs install.ps1 -NoLaunch with -SkipOllama / -SkipModelPull, and boots run.ps1 long enough to verify /api/health returns 200. README and site/index.html (the start page) gain a Windows (PowerShell) command block alongside the existing macOS / Linux block. The site checks (scripts/check_site.sh) now assert the Windows commands and copy-button anchors are present. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ddd8257 commit 8879917

7 files changed

Lines changed: 1017 additions & 16 deletions

File tree

.github/workflows/ci.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,111 @@ jobs:
153153
else
154154
echo "scripts/smoke_flags.sh missing; skipping flags smoke"
155155
fi
156+
157+
windows:
158+
name: Windows PowerShell install + run smoke
159+
runs-on: windows-latest
160+
steps:
161+
- uses: actions/checkout@v4
162+
163+
- name: Set up Python
164+
uses: actions/setup-python@v5
165+
with:
166+
python-version: "3.12"
167+
168+
- name: PowerShell syntax check (install.ps1 / run.ps1)
169+
shell: pwsh
170+
run: |
171+
$ErrorActionPreference = 'Stop'
172+
foreach ($f in @('install.ps1','run.ps1')) {
173+
if (-not (Test-Path $f)) { throw "missing $f" }
174+
$tokens = $null; $parseErrors = $null
175+
[System.Management.Automation.Language.Parser]::ParseFile(
176+
(Resolve-Path $f), [ref]$tokens, [ref]$parseErrors) | Out-Null
177+
if ($parseErrors -and $parseErrors.Count -gt 0) {
178+
$parseErrors | ForEach-Object { Write-Host $_ }
179+
throw "parse errors in $f"
180+
}
181+
Write-Host "ok $f"
182+
}
183+
184+
- name: Help text (-Help) for both scripts
185+
shell: pwsh
186+
run: |
187+
$ErrorActionPreference = 'Stop'
188+
.\install.ps1 -Help | Select-Object -First 5
189+
.\run.ps1 -Help | Select-Object -First 5
190+
191+
- name: Smoke-test install.ps1 (noninteractive, skip Ollama, skip pull)
192+
shell: pwsh
193+
env:
194+
TUTOR_SKIP_OLLAMA: "1"
195+
TUTOR_SKIP_MODEL_PULL: "1"
196+
TUTOR_NONINTERACTIVE: "1"
197+
run: |
198+
$ErrorActionPreference = 'Stop'
199+
.\install.ps1 -NoLaunch
200+
if (-not (Test-Path 'backend\.venv\Scripts\python.exe')) {
201+
throw 'venv was not created'
202+
}
203+
& 'backend\.venv\Scripts\python.exe' -c "import fastapi, uvicorn, httpx, pydantic; print('imports ok')"
204+
205+
- name: Smoke-test run.ps1 -NoLaunch (preflight only, skip Ollama)
206+
shell: pwsh
207+
env:
208+
TUTOR_SKIP_OLLAMA: "1"
209+
TUTOR_PORT: "8801"
210+
run: |
211+
$ErrorActionPreference = 'Stop'
212+
.\run.ps1 -NoLaunch -SkipOllama -Port 8801
213+
214+
- name: Smoke-test run.ps1 actually serves /api/health (no Ollama)
215+
shell: pwsh
216+
env:
217+
TUTOR_SKIP_OLLAMA: "1"
218+
run: |
219+
$ErrorActionPreference = 'Stop'
220+
$job = Start-Job -ScriptBlock {
221+
param($root)
222+
Set-Location $root
223+
$env:TUTOR_SKIP_OLLAMA = '1'
224+
& .\run.ps1 -SkipOllama -Port 8802
225+
} -ArgumentList (Get-Location).Path
226+
try {
227+
$ok = $false
228+
for ($i = 0; $i -lt 60; $i++) {
229+
Start-Sleep -Seconds 1
230+
try {
231+
$r = Invoke-WebRequest -Uri 'http://127.0.0.1:8802/api/health' `
232+
-UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
233+
if ($r.StatusCode -eq 200) { $ok = $true; break }
234+
} catch { }
235+
}
236+
if (-not $ok) {
237+
Write-Host '--- background job output ---'
238+
Receive-Job $job
239+
throw '/api/health did not return 200 within 60s'
240+
}
241+
Write-Host 'ok /api/health -> 200'
242+
} finally {
243+
Stop-Job $job -ErrorAction SilentlyContinue
244+
Remove-Job $job -Force -ErrorAction SilentlyContinue
245+
}
246+
247+
- name: Reject unknown parameter
248+
shell: pwsh
249+
run: |
250+
$ErrorActionPreference = 'Continue'
251+
$err = $null
252+
try {
253+
& .\install.ps1 -DoesNotExist 2>&1 | Out-Null
254+
} catch {
255+
$err = $_
256+
}
257+
# PowerShell raises a ParameterBindingException before the script
258+
# body runs; either $err is set or the call wrote a non-terminating
259+
# error to $Error[0].
260+
if (-not $err -and -not $Error[0]) {
261+
throw 'unknown parameter should have been rejected'
262+
}
263+
Write-Host 'ok unknown parameter rejected'

README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ never claims code works without running it.
163163

164164
## Quick start
165165

166-
Two commands. macOS or Linux. Python 3.10+.
166+
Two commands. macOS, Linux, or Windows. Python 3.10+.
167+
168+
**macOS / Linux**
167169

168170
```bash
169171
gh repo clone StewAlexander-com/python-tutor
@@ -172,17 +174,33 @@ cd python-tutor
172174
./run.sh # serves UI + API at http://localhost:8001/
173175
```
174176

177+
**Windows (PowerShell 5.1+ or PowerShell 7)**
178+
179+
```powershell
180+
gh repo clone StewAlexander-com/python-tutor
181+
cd python-tutor
182+
.\install.ps1 # sets up venv, then prompts y/N for any host-level step
183+
.\run.ps1 # serves UI + API at http://localhost:8001/
184+
```
185+
186+
> If PowerShell blocks the script with an execution-policy error, run it once
187+
> with: `powershell -ExecutionPolicy Bypass -File .\install.ps1` (or set the
188+
> per-user policy: `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`).
189+
175190
Open <http://localhost:8001/> — you'll land on the lesson list with the code lab
176191
and floating "Ask tutor" panel.
177192

178-
> `install.sh` only touches the repo on its own. **Installing Ollama, starting
179-
> the daemon, pulling the model, or launching the app are all opt-in y/N
180-
> prompts.** Press Enter and nothing changes on your host.
193+
> `install.sh` / `install.ps1` only touches the repo on its own. **Installing
194+
> Ollama, starting the daemon, pulling the model, or launching the app are all
195+
> opt-in y/N prompts.** Press Enter and nothing changes on your host. On
196+
> Windows the Ollama install path uses `winget` (App Installer) when you say
197+
> yes; otherwise a manual <https://ollama.com/download/windows> link is shown.
181198
182-
Run `./install.sh --help` or `./run.sh --help` for every option. The most
183-
common shapes:
199+
Run `./install.sh --help` or `.\install.ps1 -Help` (and the matching `run`
200+
script) for every option. The most common shapes:
184201

185202
```bash
203+
# macOS / Linux
186204
./install.sh --yes # trusted host: install Ollama, pull model, launch
187205
./install.sh --noninteractive # CI: never prompt, default everything to "no"
188206
./install.sh --skip-ollama # set up Python only; skip every Ollama probe
@@ -191,6 +209,16 @@ common shapes:
191209
./run.sh --open-browser # open the URL once /api/health is green
192210
```
193211

212+
```powershell
213+
# Windows
214+
.\install.ps1 -Yes # trusted host: install Ollama, pull model, launch
215+
.\install.ps1 -NonInteractive # CI: never prompt, default everything to "no"
216+
.\install.ps1 -SkipOllama # set up Python only; skip every Ollama probe
217+
.\install.ps1 -Model llama3.1:8b # use a different model than gemma3:4b
218+
.\run.ps1 -Port 8042 # choose a different port
219+
.\run.ps1 -OpenBrowser # open the URL once /api/health is green
220+
```
221+
194222
The classic env vars (`TUTOR_NONINTERACTIVE`, `PYTHON_TUTOR_ASSUME_YES`,
195223
`TUTOR_SKIP_OLLAMA`, `TUTOR_MODEL`, `TUTOR_PORT`, …) still work — the flags
196224
are sugar on top of them.
@@ -207,7 +235,8 @@ Full env-var list and design rationale:
207235

208236
| Symptom | What to do |
209237
| --------------------------------------------- | ----------------------------------------------- |
210-
| "Python 3.10+ is required and was not found" | `brew install python@3.12` / `apt install python3.12` and re-run. |
238+
| "Python 3.10+ is required and was not found" | `brew install python@3.12` / `apt install python3.12` / `winget install -e --id Python.Python.3.12` and re-run. |
239+
| Windows: "running scripts is disabled on this system" | One-shot: `powershell -ExecutionPolicy Bypass -File .\install.ps1`. Persistent (recommended): `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. |
211240
| `pip install` fails on DNS / proxy / pypi | The script detects this and prints offline/proxy/wheelhouse recipes. See [install-audit.md](docs/install-audit.md#pip-install-fails-on-a-network-you-dont-control). |
212241
| "Port 8001 is already in use" | `./run.sh --port 8002` (probe uses `/dev/tcp`, no `lsof` needed). |
213242
| Ollama installed but daemon down on `:11434` | Answer `y` to "Start `ollama serve` now?" or run it yourself in another Terminal. |

0 commit comments

Comments
 (0)