Skip to content

Commit 678fddb

Browse files
gate Ollama install/start/pull/launch behind y/N prompts (#7)
* gate Ollama install/start/pull/launch behind y/N prompts install.sh now detects missing Ollama, a stopped daemon, and a missing default model, and prompts y/N before each system-touching step. The default answer is "no" so pressing Enter never installs a binary. Accepted yes responses: y, Y, yes, Yes, YES. run.sh mirrors the prompt helper and offers to start "ollama serve" when it sees the binary but not the daemon. CI/unattended controls: TUTOR_NONINTERACTIVE=1 / PYTHON_TUTOR_NONINTERACTIVE=1 -> auto-no PYTHON_TUTOR_ASSUME_YES=1 -> auto-yes PYTHON_TUTOR_AUTOLAUNCH=1 -> exec run.sh After install, the script also asks "Launch the tutor now?" and execs ./run.sh on yes (ASSUME_YES/AUTOLAUNCH auto-accept). Adds scripts/smoke_prompts.sh covering the noninteractive path, auto-no marker, the PYTHON_TUTOR_NONINTERACTIVE alias, and the ASSUME_YES auto-launch via a stubbed run.sh. CI runs it after the existing install/run smoke jobs. Updates README quick start + troubleshooting and docs/install-runtime-workflow.md (new mermaid flow) to document the opt-in behaviour and env vars. * shellcheck: use '_' for unused loop counters in start_ollama_now --------- Co-authored-by: Claude Code <claude-code@anthropic.com>
1 parent bac79ad commit 678fddb

6 files changed

Lines changed: 522 additions & 88 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,12 @@ jobs:
124124
else
125125
echo "scripts/smoke_run.sh missing; skipping run.sh smoke"
126126
fi
127+
128+
- name: Smoke-test install.sh y/N prompts (noninteractive)
129+
run: |
130+
if [ -f scripts/smoke_prompts.sh ]; then
131+
chmod +x scripts/smoke_prompts.sh
132+
./scripts/smoke_prompts.sh
133+
else
134+
echo "scripts/smoke_prompts.sh missing; skipping prompt smoke"
135+
fi

README.md

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,36 +81,76 @@ first look at the UI; required for chat replies and code evaluation.
8181
```bash
8282
gh repo clone StewAlexander-com/python-tutor
8383
cd python-tutor
84-
./install.sh # creates venv, installs deps, pulls model if Ollama is up
84+
./install.sh # creates venv, installs deps, then prompts y/N for
85+
# any host-level setup (install Ollama, start daemon,
86+
# pull model, launch app)
8587
./run.sh # serves UI + API on http://localhost:8001/
8688
```
8789

8890
Then open <http://localhost:8001/> in your browser. You'll see the
8991
lesson list, the inline code lab (Run / Evaluate), and the floating
9092
"Ask tutor" chat panel.
9193

94+
### Opt-in install prompts
95+
96+
`install.sh` does the Python-side setup (venv + pip) without asking —
97+
those changes live inside the repository. Anything that touches the
98+
host system or your home directory is **opt-in** via a y/N prompt:
99+
100+
| Detected condition | Prompt | Default |
101+
| --------------------------------------------- | -------------------------------------------------------- | ------- |
102+
| Ollama binary missing (macOS or Linux) | "Install Ollama now? (will run the official installer)" | **No** |
103+
| Ollama installed but daemon not on :11434 | "Start 'ollama serve' in the background now?" | **No** |
104+
| Default model (`gemma3:4b`) missing locally | "Pull '<model>' now? (this can take several minutes)" | **No** |
105+
| Install finished | "Launch the tutor now (./run.sh)?" | **No** |
106+
107+
Accepted "yes" answers are `y`, `Y`, `yes`, `Yes`, `YES`. Anything else
108+
— including pressing Enter on an empty line — is treated as "no". The
109+
upstream Ollama installer on Linux (`curl https://ollama.com/install.sh | sh`)
110+
may itself invoke `sudo`; that is the documented upstream path.
111+
112+
### Non-interactive / CI usage
113+
114+
The same flow runs unattended:
115+
116+
```bash
117+
# CI: do not touch Ollama, do nothing system-level.
118+
TUTOR_SKIP_OLLAMA=1 TUTOR_NONINTERACTIVE=1 ./install.sh
119+
120+
# Unattended local install where the operator has pre-approved everything.
121+
PYTHON_TUTOR_ASSUME_YES=1 ./install.sh
122+
```
123+
124+
Relevant env vars:
125+
126+
| Variable | Effect |
127+
| ------------------------------ | ------------------------------------------------------------------- |
128+
| `TUTOR_NONINTERACTIVE=1` | Never prompt; answer **no** to every install/start/pull/launch ask. |
129+
| `PYTHON_TUTOR_NONINTERACTIVE=1`| Alias for the above. |
130+
| `PYTHON_TUTOR_ASSUME_YES=1` | Never prompt; answer **yes** (use only if you trust the host). |
131+
| `PYTHON_TUTOR_AUTOLAUNCH=1` | After install, `exec ./run.sh` without asking. |
132+
| `TUTOR_SKIP_OLLAMA=1` | Skip every Ollama probe entirely. |
133+
| `TUTOR_SKIP_MODEL_PULL=1` | Skip the `ollama pull` step (binary/daemon checks still run). |
134+
| `TUTOR_MODEL=<tag>` | Override the default `gemma3:4b`. |
135+
92136
### Expected behaviour when Ollama is not running
93137

94138
- The web UI loads normally — you can read lessons and run code locally
95139
(`POST /api/run` does not need the LLM).
96140
- `/api/health` reports `status: "degraded"` and `ollama_reachable: false`.
97141
- `Evaluate` and the chat panel return a clear 503 — they don't hang.
98142
- As soon as you start `ollama serve`, everything works without a restart.
143+
- `run.sh` will offer to start `ollama serve` for you if it sees the
144+
binary but not the daemon. Decline and it continues with the
145+
degraded-mode warning.
99146

100147
### What if Ollama isn't installed?
101148

102-
`install.sh` and `run.sh` **never** install system binaries on your
103-
behalf. If Ollama is missing, they print exactly what to run:
104-
105-
```bash
106-
# macOS
107-
brew install ollama && ollama serve &
108-
109-
# Linux
110-
curl -fsSL https://ollama.com/install.sh | sh && ollama serve &
111-
```
112-
113-
Then `./install.sh` again to pull `gemma3:4b`.
149+
`install.sh` will offer to install it for you on macOS (`brew install
150+
ollama`) or Linux (`curl -fsSL https://ollama.com/install.sh | sh`).
151+
Answer `y` to proceed, anything else to skip. If you skip, the same
152+
commands are printed for you to run by hand, then re-run `./install.sh`
153+
to pull the model.
114154

115155
### Troubleshooting
116156

@@ -122,6 +162,8 @@ Then `./install.sh` again to pull `gemma3:4b`.
122162
| Model missing in chat replies | `ollama pull gemma3:4b` (or set `TUTOR_MODEL` to your model) |
123163
| Service worker shows stale UI | Hard-refresh the browser (Cmd/Ctrl-Shift-R) |
124164
| `install.sh` failed mid-`pip install` | Re-run it — it's idempotent and reuses the venv |
165+
| Prompts fire in CI / a script | Set `TUTOR_NONINTERACTIVE=1` (defaults to "no") or `PYTHON_TUTOR_ASSUME_YES=1` |
166+
| Ollama installer prompts for sudo | That's the upstream Linux installer; decline the prompt or install via your package manager |
125167

126168
For the design rationale behind the two-script flow (and the five flows
127169
we evaluated), see

docs/install-runtime-workflow.md

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -154,47 +154,89 @@ flowchart TD
154154

155155
## Decision
156156

157-
We ship **Candidate D, blended with one ergonomic touch from C**.
157+
We ship **Candidate D, blended with the consent-gated ergonomics of C**.
158158

159159
- **From D**: two-script split (`install.sh`, `run.sh`); we *detect*
160-
Ollama rather than installing it; we never start daemons in
161-
`install.sh`; the run-time server still launches if Ollama is down so
162-
the UI is usable and the failure is observable in the chat panel.
163-
- **From C**: in `install.sh`, if Ollama *is* present, we offer to
164-
`ollama pull` the default model on the user's behalf — gated by
165-
`TUTOR_SKIP_MODEL_PULL` and skippable in CI. Pulling a model the user
166-
already chose to have Ollama for is low-risk and saves a step.
160+
Ollama and the model rather than installing them silently; the
161+
run-time server still launches if Ollama is down so the UI is usable
162+
and the failure is observable in the chat panel.
163+
- **From C**: when something host-level is missing — the Ollama binary,
164+
the `ollama serve` daemon, or the default model `install.sh`
165+
*offers* to handle it. The user types `y` to accept. The default
166+
answer is **no**, so pressing Enter never installs a system binary.
167167

168168
This blend has:
169169

170170
- 2 commands typed (`./install.sh`, `./run.sh`) — same as B/D/E.
171-
- Zero hidden system-level installs.
172-
- One actionable error message if Ollama is missing (we print the
173-
install command for the user's platform).
171+
- Zero hidden system-level installs. Every system-touching action is
172+
preceded by an explicit y/N confirmation.
173+
- A single non-interactive entry-point for CI
174+
(`TUTOR_NONINTERACTIVE=1` defaults all prompts to "no";
175+
`PYTHON_TUTOR_ASSUME_YES=1` defaults them to "yes" for pre-approved
176+
unattended setup).
174177
- A web UI that loads even when the LLM is unreachable — so the learner
175178
always gets *something* to interact with.
176179

180+
```mermaid
181+
flowchart TD
182+
Clone[git clone repo] --> Install[./install.sh]
183+
Install --> Py[Python 3.10+ check]
184+
Py --> Venv[Create / reuse backend/.venv]
185+
Venv --> Pip[pip install backend deps]
186+
Pip --> HasOllama{ollama on PATH?}
187+
HasOllama -- no --> AskInstall{Install Ollama now? y/N}
188+
AskInstall -- y --> RunInstaller[Run upstream installer]
189+
AskInstall -- N --> HintInstall[Print manual install hint]
190+
RunInstaller --> Daemon{Daemon on :11434?}
191+
HasOllama -- yes --> Daemon
192+
HintInstall --> NextBanner
193+
Daemon -- yes --> Model{Model present?}
194+
Daemon -- no --> AskStart{Start 'ollama serve' now? y/N}
195+
AskStart -- y --> Spawn[nohup ollama serve & probe up to 10s]
196+
AskStart -- N --> HintStart[Print 'run: ollama serve' hint]
197+
Spawn --> Model
198+
HintStart --> NextBanner
199+
Model -- yes --> NextBanner
200+
Model -- no --> AskPull{"Pull model? y/N"}
201+
AskPull -- y --> Pull[ollama pull TUTOR_MODEL]
202+
AskPull -- N --> HintPull[Print 'ollama pull' hint]
203+
Pull --> NextBanner
204+
HintPull --> NextBanner
205+
NextBanner --> AskLaunch{Launch ./run.sh? y/N}
206+
AskLaunch -- y --> Run[./run.sh → uvicorn]
207+
AskLaunch -- N --> Done[Print next-step banner]
208+
Run --> Browser[Open http://localhost:8001]
209+
```
210+
177211
## How the scripts behave
178212

179213
### `install.sh`
180214

181215
1. Detect Python ≥3.10. If missing or too old, print install command,
182216
exit 1.
183217
2. Create `backend/.venv` if it doesn't exist; otherwise reuse it.
184-
3. `pip install -r backend/requirements-dev.txt` (idempotent).
185-
4. Check `ollama` on `PATH`. If missing, print install command and exit
186-
0 (success — the Python side is set up). User can re-run install
187-
later, or just run.
188-
5. If `ollama` is present, probe `http://localhost:11434/api/tags`. If
189-
the daemon is up, pull the default model (skippable via
190-
`TUTOR_SKIP_MODEL_PULL=1`). If the daemon is down, print
191-
`ollama serve &` and continue.
192-
6. Print next-step banner: `./run.sh`.
218+
3. `pip install -r backend/requirements-dev.txt` (idempotent;
219+
not gated by a prompt — those changes live inside the repo).
220+
4. Check `ollama` on `PATH`. If missing, **prompt** to install via the
221+
OS-appropriate upstream installer (`brew install ollama` on macOS,
222+
`curl https://ollama.com/install.sh | sh` on Linux). Decline and
223+
the script prints the manual command and continues.
224+
5. If `ollama` is present, probe `http://localhost:11434/api/tags`.
225+
If the daemon is down, **prompt** to start `ollama serve` in the
226+
background (`nohup`, logged to `/tmp/ollama-serve.log`).
227+
6. If the daemon is up, check for the model
228+
(`TUTOR_MODEL`, default `gemma3:4b`). If absent, **prompt** to
229+
`ollama pull` it.
230+
7. After setup, **prompt** "Launch the tutor now (./run.sh)?".
231+
`PYTHON_TUTOR_AUTOLAUNCH=1` or `PYTHON_TUTOR_ASSUME_YES=1` answers
232+
yes without asking.
193233

194234
### `run.sh`
195235

196-
1. Ensure venv exists (re-run `install.sh` if not).
197-
2. Probe Ollama; warn if unreachable but continue.
236+
1. Ensure venv exists (re-run `install.sh` in non-interactive,
237+
skip-Ollama mode if not — this never installs system binaries).
238+
2. Probe Ollama; if installed but the daemon is down, **prompt** to
239+
start `ollama serve`. If declined, warn and continue.
198240
3. Launch uvicorn with `TUTOR_SERVE_FRONTEND=1` so the backend serves
199241
the static frontend on the same port.
200242
4. Print the URL: `http://localhost:8001/`.
@@ -206,25 +248,34 @@ This blend has:
206248
- `TUTOR_MODEL` — Ollama model tag (default `gemma3:4b`).
207249
- `TUTOR_SKIP_OLLAMA=1` — skip every Ollama probe (CI/offline-dev).
208250
- `TUTOR_SKIP_MODEL_PULL=1` — skip `ollama pull` in install.
209-
- `TUTOR_NONINTERACTIVE=1` — never prompt; assume defaults.
251+
- `TUTOR_NONINTERACTIVE=1` — never prompt; auto-answer **no**.
252+
- `PYTHON_TUTOR_NONINTERACTIVE=1` — alias for the above.
253+
- `PYTHON_TUTOR_ASSUME_YES=1` — never prompt; auto-answer **yes**.
254+
- `PYTHON_TUTOR_AUTOLAUNCH=1``exec ./run.sh` after install
255+
without asking.
210256

211257
## What the user does
212258

259+
Default interactive flow:
260+
213261
```bash
214262
gh repo clone StewAlexander-com/python-tutor
215263
cd python-tutor
216-
./install.sh # ~2 min cold; reuses cache on re-run
217-
./run.sh # opens at http://localhost:8001/
264+
./install.sh # ~2 min cold; prompts y/N for each system action
265+
# answer 'y' to install Ollama, start the daemon,
266+
# pull the model, and launch the app
218267
```
219268

220-
If Ollama is missing, `install.sh` will tell them exactly what to type:
269+
If you'd rather drive it yourself, decline every prompt and the script
270+
still finishes successfully — only the Python side is set up, with
271+
clear hints for what to run next.
272+
273+
Unattended:
221274

222275
```bash
223-
# macOS
224-
brew install ollama && ollama serve &
276+
# Pre-approved: install Ollama, start it, pull the model, exec run.sh.
277+
PYTHON_TUTOR_ASSUME_YES=1 ./install.sh
225278

226-
# Linux
227-
curl -fsSL https://ollama.com/install.sh | sh && ollama serve &
279+
# CI: do not touch Ollama at all.
280+
TUTOR_SKIP_OLLAMA=1 TUTOR_NONINTERACTIVE=1 ./install.sh
228281
```
229-
230-
Then `./install.sh && ./run.sh` again.

0 commit comments

Comments
 (0)