Portability: python/python3 note + cross-shell lab commands (#31,#32) (#62)

Co-authored-by: claude <claude@jpaul.io>
Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #62.
This commit is contained in:
2026-06-22 17:01:27 -04:00
committed by Claude (agent)
parent 90012ca711
commit c34052665f
4 changed files with 40 additions and 2 deletions
@@ -137,6 +137,12 @@ purpose** so you recognize it later.
- Python 3.10 or newer (`python --version` or `python3 --version` to check). - Python 3.10 or newer (`python --version` or `python3 --version` to check).
- Your usual AI chat assistant, open in a browser tab. - Your usual AI chat assistant, open in a browser tab.
> **One command name, the whole course through:** whichever of `python` / `python3` just printed a
> 3.10+ version is the command to use in *every* lab from here on. The labs are written with
> `python`; if that's "command not found" on your machine — common on current macOS and default
> Debian/Ubuntu, where Python is installed only as `python3` — read it as `python3` (and `pip3`
> wherever a lab uses `pip`). This note holds course-wide; we won't repeat it.
### Get the course materials ### Get the course materials
Everything you'll run in this course lives in one repo. Grab it once, up front — no tools required Everything you'll run in this course lives in one repo. Grab it once, up front — no tools required
@@ -170,6 +176,10 @@ You now have every module's files locally, including this one's under
(Copy them however you like — drag-and-drop in your editor's file explorer is fine.) (Copy them however you like — drag-and-drop in your editor's file explorer is fine.)
> **On Windows:** these labs' shell snippets are written for bash — run them from **Git Bash** or
> **WSL** and they work as-is. In native PowerShell a few POSIX-only commands differ; here, `mkdir
> -p` becomes `New-Item -ItemType Directory -Force`.
2. Open the folder in your editor (`code .` if you're using VS Code, or File → Open Folder). 2. Open the folder in your editor (`code .` if you're using VS Code, or File → Open Folder).
3. Run it in your terminal to confirm it works: 3. Run it in your terminal to confirm it works:
@@ -305,6 +305,10 @@ do them once on purpose now.
git log --oneline # the bad merge is STILL there, with a revert after it git log --oneline # the bad merge is STILL there, with a revert after it
``` ```
> **On Windows:** `rm -f` is bash. Run this lab from Git Bash or WSL (it works as-is), or use
> PowerShell's `Remove-Item -Force tasks.json`. Every other command here is Git, identical across
> shells.
That last point is the whole lesson: you undid the effect **without rewriting history**. Anyone who That last point is the whole lesson: you undid the effect **without rewriting history**. Anyone who
pulled the bad merge just pulls your revert on top and they're fine. pulled the bad merge just pulls your revert on top and they're fine.
@@ -226,10 +226,14 @@ containerize and run the app you already have.
image (that keeps it lean; see *Where it breaks*): image (that keeps it lean; see *Where it breaks*):
```bash ```bash
docker run --rm -v "$PWD":/app -w /app python:3.12-slim \ docker run --rm -v "${PWD}:/app" -w /app python:3.12-slim \
python -m unittest python -m unittest
``` ```
> **On Windows:** this step bind-mounts your code, so the host path matters. Run it from WSL (or
> Git Bash), or from PowerShell — `${PWD}` resolves correctly in each. The other `docker run`
> commands mount nothing of yours and are identical everywhere.
This is, in miniature, exactly what containerized CI does. If it passes here, it passes the same This is, in miniature, exactly what containerized CI does. If it passes here, it passes the same
way on any machine with the engine — your laptop's local Python version is now irrelevant. way on any machine with the engine — your laptop's local Python version is now irrelevant.
@@ -15,6 +15,26 @@ set -u
line() { printf '\n=== %s ===\n' "$1"; } line() { printf '\n=== %s ===\n' "$1"; }
# Try a TCP connect to host:port with a ~2s deadline, portably.
# GNU `timeout` (Linux) or Homebrew's `gtimeout` are used when present; otherwise a bash-native
# background-and-kill fallback keeps this working on stock macOS, which ships no GNU `timeout`.
tcp_probe() {
host="$1"; port="$2"
if command -v timeout >/dev/null 2>&1; then
timeout 2 bash -c ">/dev/tcp/${host}/${port}" 2>/dev/null
elif command -v gtimeout >/dev/null 2>&1; then
gtimeout 2 bash -c ">/dev/tcp/${host}/${port}" 2>/dev/null
else
bash -c ">/dev/tcp/${host}/${port}" 2>/dev/null &
probe_pid=$!
( sleep 2; kill "$probe_pid" 2>/dev/null ) >/dev/null 2>&1 &
killer_pid=$!
rc=0; wait "$probe_pid" 2>/dev/null || rc=$?
kill "$killer_pid" 2>/dev/null
return "$rc"
fi
}
line "WHO AND WHERE" line "WHO AND WHERE"
echo "hostname : $(hostname 2>/dev/null)" echo "hostname : $(hostname 2>/dev/null)"
echo "user : $(whoami 2>/dev/null) (root? $( [ "$(id -u 2>/dev/null)" = 0 ] && echo YES || echo no ))" echo "user : $(whoami 2>/dev/null) (root? $( [ "$(id -u 2>/dev/null)" = 0 ] && echo YES || echo no ))"
@@ -62,7 +82,7 @@ line "PRIVATE NETWORK REACH (the reason you self-host — and the reason it's da
PROBES=( "192.168.0.1:80" "192.168.1.1:80" "10.0.0.1:80" ) PROBES=( "192.168.0.1:80" "192.168.1.1:80" "10.0.0.1:80" )
for hp in "${PROBES[@]}"; do for hp in "${PROBES[@]}"; do
host="${hp%%:*}"; port="${hp##*:}" host="${hp%%:*}"; port="${hp##*:}"
if timeout 2 bash -c ">/dev/tcp/${host}/${port}" 2>/dev/null; then if tcp_probe "$host" "$port"; then
echo " REACHABLE: ${host}:${port}" echo " REACHABLE: ${host}:${port}"
fi fi
done done