diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml new file mode 100644 index 0000000..7898b87 --- /dev/null +++ b/.github/workflows/macos-latest.yml @@ -0,0 +1,87 @@ +name: Publish macos-latest (Wine 9) + +# Manually-triggered build of the macos-latest tag, pinned to Wine 9.x. +# +# Why a separate workflow: +# The default `latest` tag tracks Wine stable (currently Wine 10), which +# aborts under QEMU on 16 KB page-size hosts (Apple Silicon Macs via +# Docker Desktop, Asahi Linux). Wine 9 does not exhibit that bug, so the +# `macos-latest` tag stays pinned to Wine 9 until upstream #58084 ships. +# +# Tags published on every run: +# - devel-YYYYMMDD-wine- (immutable, audit trail) +# - macos-latest (rolling pointer, can be overridden) +# +# Run via the Actions tab → "Run workflow", optionally overriding the +# Wine version pin (e.g. to bump to a newer 9.x point release). + +on: + workflow_dispatch: + inputs: + wine_version: + description: "Debian package version pin for wine-stable (e.g. 9.0.0.0~bookworm-1)" + required: true + default: "9.0.0.0~bookworm-1" + rolling_tag: + description: "Rolling tag to publish alongside the dated devel tag" + required: true + default: "macos-latest" + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Compute tags + id: tags + run: | + DATE=$(date -u +%Y%m%d) + # 9.0.0.0~bookworm-1 → 9.0 + WINE_SHORT=$(echo "${{ inputs.wine_version }}" | cut -d. -f1-2) + DEVEL_TAG="devel-${DATE}-wine-${WINE_SHORT}" + IMAGE=aanas0sayed/docker-ltspice + echo "devel=${IMAGE}:${DEVEL_TAG}" >> "$GITHUB_OUTPUT" + echo "rolling=${IMAGE}:${{ inputs.rolling_tag }}" >> "$GITHUB_OUTPUT" + echo "Will publish: ${IMAGE}:${DEVEL_TAG} and ${IMAGE}:${{ inputs.rolling_tag }}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build image (load locally for smoke test) + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + load: true + tags: docker-ltspice:macos-ci + build-args: | + WINE_BRANCH=stable + WINE_VERSION=${{ inputs.wine_version }} + cache-to: type=gha,scope=ltspice-wine9,mode=max + + - name: Run simulation test + run: | + chmod +x test.sh + ./test.sh docker-ltspice:macos-ci + + - name: Push image with both tags + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64 + push: true + tags: | + ${{ steps.tags.outputs.devel }} + ${{ steps.tags.outputs.rolling }} + build-args: | + WINE_BRANCH=stable + WINE_VERSION=${{ inputs.wine_version }} + cache-from: type=gha,scope=ltspice-wine9 diff --git a/Dockerfile b/Dockerfile index d3d742c..24aa499 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,37 +55,70 @@ RUN apt-get update \ fi \ && rm -rf /var/lib/apt/lists/* -# ── 5. Wine env ──────────────────────────────────────────────────────────── -ENV WINEPREFIX=/root/.wine \ +# ── 5. Unprivileged user ─────────────────────────────────────────────────── +# Image runs as wineuser (uid 1000) by default. To also support +# --user=$(id -u):$(id -g) for arbitrary uid/gid, the prefix is shipped as +# a *template* (uid-agnostic) and the entrypoint copies it into a per-run +# location owned by the current uid. Wine refuses to use a prefix not +# owned by the running uid, so a simple chmod-only approach does not work. +RUN groupadd -g 1000 wineuser \ + && useradd -m -u 1000 -g 1000 -s /bin/bash wineuser + +# ── 6. Wine env ──────────────────────────────────────────────────────────── +# WINEPREFIX deliberately points into /tmp (tmpfs in callers) — the +# entrypoint materialises it from the on-image template on every fresh +# container start. The build-time prefix lives at /opt/wineprefix-template. +ENV HOME=/home/wineuser \ + WINEPREFIX=/tmp/wine-prefix \ WINEDEBUG=-all \ DISPLAY=:99 -# ── 6. Install LTspice ───────────────────────────────────────────────────── -# Xvfb must be running before wineboot/msiexec – both need a display. -# We start it in the same RUN layer, do everything, then kill it. -# wget and other build-only tools are removed at the end of this layer. +# ── 7. Install LTspice into a build-time prefix (as wineuser) ───────────── +USER wineuser +WORKDIR /home/wineuser RUN Xvfb :99 -screen 0 1024x768x24 & \ sleep 2 \ - && WINEDLLOVERRIDES="mscoree,mshtml=" wineboot --init \ - && wineserver --wait \ + && WINEPREFIX=/home/wineuser/.wine WINEDLLOVERRIDES="mscoree,mshtml=" \ + wineboot --init \ + && WINEPREFIX=/home/wineuser/.wine wineserver --wait \ && wget -q -O /tmp/LTspice64.msi https://ltspice.analog.com/software/LTspice64.msi \ - && wine msiexec /i /tmp/LTspice64.msi /quiet /norestart \ - && wineserver --wait \ + && WINEPREFIX=/home/wineuser/.wine wine msiexec /i /tmp/LTspice64.msi /quiet /norestart \ + && WINEPREFIX=/home/wineuser/.wine wineserver --wait \ && rm /tmp/LTspice64.msi \ && rm -rf /tmp/.wine-* /tmp/wine-* \ && kill %1 2>/dev/null || true -# ── 7. Remove build-only tools ──────────────────────────────────────────── +USER root + +# ── 8. Split LTspice install out of the prefix; turn the prefix into a +# uid-agnostic template ───────────────────────────────────────────── +# The 1.7 GB LTspice tree moves to /opt/ltspice (read-only at runtime), +# replaced by a relative symlink from inside the prefix so the registry +# entries written by msiexec still resolve. The slimmed-down prefix +# (~150 MB) becomes /opt/wineprefix-template — the entrypoint copies it +# per run into $WINEPREFIX, which makes the copy owned by the current +# uid and satisfies Wine's "prefix is not owned by you" check. +RUN mv "/home/wineuser/.wine/drive_c/Program Files/ADI/LTspice" /opt/ltspice \ + && ln -s /opt/ltspice "/home/wineuser/.wine/drive_c/Program Files/ADI/LTspice" \ + && mv /home/wineuser/.wine /opt/wineprefix-template \ + && find /opt/ltspice -type d -exec chmod a+rwx {} + \ + && find /opt/ltspice -type f -exec chmod a+rw {} + \ + && find /opt/wineprefix-template -type d -exec chmod a+rwx {} + \ + && find /opt/wineprefix-template -type f -exec chmod a+rw {} + \ + && chmod a+rwx /home/wineuser \ + && rm -rf /tmp/.X* /tmp/.wine-* /tmp/wine-* + +# ── 9. Remove build-only tools ──────────────────────────────────────────── RUN apt-get purge -y --auto-remove wget p7zip-full unzip \ && rm -rf /var/lib/apt/lists/* -# ── 8. Entrypoint ────────────────────────────────────────────────────────── +# ── 10. Entrypoint + wrapper ────────────────────────────────────────────── COPY entrypoint.sh /usr/local/bin/entrypoint -RUN chmod +x /usr/local/bin/entrypoint - -# ── 9. LTspice wrapper ───────────────────────────────────────────────────── COPY wrappers/ltspice /usr/local/bin/ltspice -RUN chmod +x /usr/local/bin/ltspice +RUN chmod +x /usr/local/bin/entrypoint /usr/local/bin/ltspice + +USER wineuser +WORKDIR /home/wineuser ENTRYPOINT ["/usr/local/bin/entrypoint"] -CMD ["/bin/bash"] \ No newline at end of file +CMD ["/bin/bash"] diff --git a/README.md b/README.md index 4eb456a..bfad05d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Run [LTspice](https://www.analog.com/en/resources/design-tools-and-calculators/l The image is based on `debian:bookworm-slim` with Wine (stable) and LTspice pre-installed. The entrypoint automatically handles Wine initialisation and starts Xvfb on `:99`, so simulations work out of the box. +The container runs as a non-root `wineuser` (uid 1000) by default and works under `--user=$(id -u):$(id -g)` for any host uid, so output files (`.log`, `.raw`) end up owned by you and the container can be run with `--cap-drop=ALL` (no `CAP_DAC_OVERRIDE`). + Pre-built images are available on [DockerHub](https://hub.docker.com/r/aanas0sayed/docker-ltspice). > [!IMPORTANT] @@ -39,9 +41,26 @@ docker run --rm \ The `ltspice` command is a thin wrapper around `wine LTspice.exe`. +### Sandboxed invocation (recommended on Linux) + +To run with the host user's uid (so output files belong to you) and with all Linux capabilities dropped: + +```bash +docker run --rm \ + --user "$(id -u):$(id -g)" \ + --cap-drop=ALL \ + --security-opt=no-new-privileges \ + --network=none \ + -v "$(pwd):/sim" \ + aanas0sayed/docker-ltspice \ + ltspice -b -run "Z:\\sim\\your_circuit.net" +``` + +The image creates a fresh Wine prefix at `/tmp/wine-prefix` on each container start, owned by whatever uid you pass to `--user`, so Wine's prefix-ownership check is satisfied without `CAP_CHOWN` or `CAP_DAC_OVERRIDE`. + ### CI example -See [test.sh](test.sh) and [.github/workflows/test.yml](.github/workflows/test.yml) for a working example that runs a simulation and validates `.meas` results from the output log. +See [test.sh](test.sh) and [.github/workflows/ci.yml](.github/workflows/ci.yml) for a working example that runs a simulation under the hardened invocation above and validates `.meas` results from the output log. --- @@ -99,6 +118,8 @@ docker run --rm -it \ - **Xvfb fails on first run (macOS):** The entrypoint prints `ERROR: Xvfb exited unexpectedly` on the first container start on macOS. This is a known issue — the container is still usable. Run `ltspice` from the shell and it will work normally. - **File not found / path errors:** LTspice runs inside Wine, so paths must use the Wine `Z:` drive (which maps to `/` on the container). For example, a netlist mounted at `/sim/circuit.net` should be passed as `Z:\\sim\\circuit.net`. +- **Permission denied writing the log/raw file:** the container is non-root (uid 1000 by default). If your bind-mount directory isn't writable by that uid, either pass `--user=$(id -u):$(id -g)` so the in-container uid matches the directory owner, or `chmod` the directory so uid 1000 can write to it. +- **Slower first call inside a container:** on every fresh `docker run`, the entrypoint copies the Wine prefix template into `/tmp/wine-prefix` (~150 MB). This is sub-second on tmpfs but adds a small fixed cost per container start. - **Interactive shell:** The entrypoint still runs (priming Wine and starting Xvfb) before handing off to your command: ```bash diff --git a/docs/wineprefix.md b/docs/wineprefix.md new file mode 100644 index 0000000..33a9d17 --- /dev/null +++ b/docs/wineprefix.md @@ -0,0 +1,47 @@ +# Wine prefix + +A Wine prefix is the directory Wine uses to emulate a Windows install for one user. It's a self-contained fake `C:\` plus a fake Windows registry, stored as regular Linux files in a single folder. + +## Layout + +``` +$WINEPREFIX/ +├── drive_c/ ← fake C: drive +│ ├── windows/ ← fake C:\Windows (system DLLs, fonts) +│ ├── Program Files/ ← where Windows apps install themselves +│ ├── users// ← fake C:\Users\ (Desktop, AppData, Temp) +│ └── ProgramData/ +├── dosdevices/ +│ ├── c: -> ../drive_c +│ └── z: -> / ← Z: maps to the Linux root, the trick that +│ lets `Z:\sim\foo.net` mean `/sim/foo.net` +├── system.reg ← fake HKEY_LOCAL_MACHINE +├── user.reg ← fake HKEY_CURRENT_USER +└── userdef.reg ← fake HKEY_USERS\.Default +``` + +## What it's for + +When Wine runs a Windows `.exe`, the `.exe` asks Windows things like "where is `C:\Program Files`?", "what's in the registry under `HKLM\Software\X`?", "give me `%TEMP%`." Wine answers by reading files inside the prefix. The prefix is the per-user state of "Windows." + +A prefix is created the first time you run `wineboot` (or any `wine` command) — Wine generates a default skeleton with system DLLs. When you install a Windows app via `wine msiexec /i installer.msi`, the installer writes files into `drive_c/Program Files/...` and registry entries into `system.reg` / `user.reg` **inside that prefix.** That on-disk state is what makes the app "installed." + +You can have many prefixes — `WINEPREFIX=/some/other/dir` gives you a separate fake Windows. Used to isolate finicky apps from each other. + +## Why this image cares + +Wine has a check at every invocation: `stat($WINEPREFIX).st_uid != getuid()` → hard refusal with `wine: '' is not owned by you`. The prefix must be owned by the running uid. + +That single check is the reason for the prefix-template design: + +- The image runs as `wineuser` (uid 1000) by default, but supports `docker run --user=$(id -u):$(id -g)` for arbitrary host uids. +- If we shipped a prefix at `/home/wineuser/.wine` owned by uid 1000, any `--user` value other than 1000 would fail Wine's owner check, and we can't `chown` at runtime under `--cap-drop=ALL`. +- So the build splits the prefix into a **uid-agnostic template** at `/opt/wineprefix-template`, and the entrypoint `cp -a --no-preserve=ownership` it into `/tmp/wine-prefix` on every container start. The copy inherits the running uid → owner check passes for any uid. + +## What's in this image's prefix + +- A fully installed LTspice — except its 1.7 GB install was moved out to `/opt/ltspice` and replaced with a relative symlink at `drive_c/Program Files/ADI/LTspice`. Wine still sees it at `C:\Program Files\ADI\LTspice`; the registry entries written by `msiexec` still resolve. +- The `Z: -> /` symlink under `dosdevices/`, which is what lets callers pass `Z:\sim\foo.net` to LTspice. +- The registry entries from the LTspice install (file associations, COM registration). + +The prefix at `/tmp/wine-prefix` is per-container — anything written into it during a run (Wine timestamp updates, LTspice config) is discarded when the container exits. For batch simulation that's the right default; for "develop interactively in the container," preferences reset on every start. diff --git a/entrypoint.sh b/entrypoint.sh index dbd7272..c3c9fe7 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,11 +1,17 @@ #!/bin/bash -# entrypoint.sh – runs before every command inside the container +# entrypoint.sh – runs before every command inside the container. # -# Responsibilities (mirrors what scottyhardy's entrypoint.sh does): -# 1. Start Xvfb on DISPLAY :99 so Wine has a virtual screen to render into -# 2. Wait until the X server is actually accepting connections -# 3. First-run: initialise WINEPREFIX (creates the fake C: drive) -# 4. exec the user's command (bash by default, or anything passed to docker run) +# Responsibilities: +# 1. Materialise the WINEPREFIX from the on-image template so it is +# owned by the current uid (required: Wine refuses to use a prefix +# not owned by the running uid). +# 2. Prime Wine so first-run service init completes without an X server. +# 3. Start Xvfb on DISPLAY :99 so subsequent Wine calls have a display. +# 4. exec the user's command (bash by default). +# +# Runs as the unprivileged wineuser (uid 1000) by default. Also tolerates +# `docker run --user=:` for arbitrary uid/gid — the prefix copy +# inherits the current uid and /tmp is world-writable on caller systems. set -e @@ -13,7 +19,32 @@ XVFB_DISPLAY="${DISPLAY:-:99}" DISPLAY_NUM="${XVFB_DISPLAY#:}" XVFB_RESOLUTION="${XVFB_RESOLUTION:-1024x768x16}" -# ── 1. Prime Wine (no X server yet) ─────────────────────────────────────── +# Defensive: an arbitrary --user uid often has no /etc/passwd entry, +# leaving HOME unset. Wine and other tools then fall back to "/" which +# is read-only. Force a sensible HOME. +export HOME="${HOME:-/home/wineuser}" +export WINEPREFIX="${WINEPREFIX:-/tmp/wine-prefix}" +# Several Wine code paths look up the username via getpwuid(); when the +# uid is not in /etc/passwd this returns NULL and Wine creates an +# unnamed users/ subdir. Pinning $LOGNAME/$USER avoids that and matches +# the username baked into the prefix template at build time. +export LOGNAME="${LOGNAME:-wineuser}" +export USER="${USER:-wineuser}" + +TEMPLATE=/opt/wineprefix-template + +# ── 1. Materialise the prefix ───────────────────────────────────────────── +if [ ! -d "$WINEPREFIX" ]; then + echo "[entrypoint] Materialising WINEPREFIX at ${WINEPREFIX} (uid $(id -u))" + # cp -a would try to preserve owner and fail when run as non-root. + # --no-preserve=ownership keeps mode/timestamps/symlinks but lets the + # copy inherit the running uid — which is exactly what Wine needs. + cp -a --no-preserve=ownership "$TEMPLATE" "$WINEPREFIX" +fi + +LTSPICE_EXE="${WINEPREFIX}/drive_c/Program Files/ADI/LTspice/LTspice.exe" + +# ── 2. Prime Wine (no X server yet) ─────────────────────────────────────── # The first wine invocation after a fresh container start triggers internal # service/init work (winebth, shell32, etc.). If an X server IS available, # those services try to create windows and block forever. Running LTspice @@ -21,12 +52,12 @@ XVFB_RESOLUTION="${XVFB_RESOLUTION:-1024x768x16}" # and complete their init. The second run then works cleanly. echo "[entrypoint] Priming Wine (display ${XVFB_DISPLAY}, no X yet)..." export DISPLAY="${XVFB_DISPLAY}" -wine "/root/.wine/drive_c/Program Files/ADI/LTspice/LTspice.exe" -b 2>/dev/null || true +wine "$LTSPICE_EXE" -b 2>/dev/null || true wineserver --wait 2>/dev/null || true echo "[entrypoint] Wine primed." -# ── 2. Start Xvfb ───────────────────────────────────────────────────────── -rm -f "/tmp/.X${DISPLAY_NUM}-lock" "/tmp/.X11-unix/X${DISPLAY_NUM}" +# ── 3. Start Xvfb ───────────────────────────────────────────────────────── +rm -f "/tmp/.X${DISPLAY_NUM}-lock" "/tmp/.X11-unix/X${DISPLAY_NUM}" 2>/dev/null || true echo "[entrypoint] Starting Xvfb on ${XVFB_DISPLAY} (${XVFB_RESOLUTION})" Xvfb "${XVFB_DISPLAY}" -screen 0 "${XVFB_RESOLUTION}" -nolisten tcp 2>/dev/null & XVFB_PID=$! @@ -46,5 +77,5 @@ done export DISPLAY="${XVFB_DISPLAY}" -# ── 3. Hand off to the user's command ────────────────────────────────────── -exec "$@" \ No newline at end of file +# ── 4. Hand off to the user's command ────────────────────────────────────── +exec "$@" diff --git a/test.sh b/test.sh index 628ed44..f908429 100755 --- a/test.sh +++ b/test.sh @@ -4,9 +4,15 @@ # Usage: ./test.sh [image] # image Docker image to test (default: aanas0sayed/docker-ltspice) # +# Runs the canonical hardened invocation: --user=$(id -u):$(id -g) plus +# --cap-drop=ALL (no --cap-add=DAC_OVERRIDE). This is the supported +# consumption pattern for the image. Verifies the produced .raw file is +# owned by the host user, proving the image works as a non-root sandboxed +# simulator without elevated capabilities. +# # Exit codes: -# 0 all .meas checks passed -# 1 simulation failed or a measurement was not found in the log +# 0 all .meas checks passed and .raw is owned by host uid +# 1 simulation failed, a measurement was not found, or .raw uid mismatch set -euo pipefail @@ -15,34 +21,50 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TEST_DIR="$SCRIPT_DIR/test" NETLIST="rc_filter.net" LOG="${NETLIST%.net}.log" +RAW="${NETLIST%.net}.raw" LOG_PATH="$TEST_DIR/$LOG" +RAW_PATH="$TEST_DIR/$RAW" +HOST_UID=$(id -u) +HOST_GID=$(id -g) + +# stat -c works on GNU stat (Linux); -f '%u' is BSD/macOS. +file_uid() { + if stat -c '%u' "$1" >/dev/null 2>&1; then + stat -c '%u' "$1" + else + stat -f '%u' "$1" + fi +} echo "==> LTspice headless batch test" echo " image : $IMAGE" echo " netlist: $TEST_DIR/$NETLIST" +echo " flags : --user=${HOST_UID}:${HOST_GID} --cap-drop=ALL" echo "" # ── Clean up previous run artifacts ────────────────────────────────────────── rm -f "$TEST_DIR"/"${NETLIST%.net}".{log,raw,op.raw,db} # ── Run LTspice inside the container ───────────────────────────────────────── -# Wine maps Z:\ to the Linux root, so /sim inside the container becomes Z:\sim +# Wine maps Z:\ to the Linux root, so /sim inside the container becomes Z:\sim. docker run --rm \ --platform linux/amd64 \ + --user="${HOST_UID}:${HOST_GID}" \ + --cap-drop=ALL \ --volume "$TEST_DIR:/sim" \ "$IMAGE" /bin/bash -c ' set -e - + NETLIST_WIN="Z:\\sim\\rc_filter.net" echo " [run] ltspice -b \"$NETLIST_WIN\"" -timeout 120 wine "/root/.wine/drive_c/Program Files/ADI/LTspice/LTspice.exe" -b -run "$NETLIST_WIN" || true +timeout 120 wine "$WINEPREFIX/drive_c/Program Files/ADI/LTspice/LTspice.exe" -b -run "$NETLIST_WIN" || true wineserver --wait 2>/dev/null || true echo " [done] simulation finished" ' -# ── Check log was produced ──────────────────────────────────────────────────── +# ── Check log was produced ─────────────────────────────────────────────────── if [[ ! -f "$LOG_PATH" ]]; then echo "FAIL: log file was not created at $LOG_PATH" exit 1 @@ -54,18 +76,12 @@ echo "------------------------------------------------------------" cat "$LOG_PATH" echo "------------------------------------------------------------" -# ── Validate .meas results ──────────────────────────────────────────────────── +# ── Validate .meas results ─────────────────────────────────────────────────── echo "" echo "==> Validating .meas results..." PASS=1 -# Extract the numeric value from a .meas log line, e.g.: -# vout_max: MAX(v(out))=4.96832 FROM 0 TO 0.005 → 4.96832 -meas_value() { - grep -i "^${1}" "$LOG_PATH" 2>/dev/null | head -n1 | grep -oE '=[0-9eE.+-]+' | head -n1 | tr -d '=' -} - check_meas_range() { local name="$1" lo="$2" hi="$3" local line val @@ -87,7 +103,6 @@ check_meas_range() { else val=$(echo "$line" | grep -oE '=[0-9eE.+-]+' | head -n1 | tr -d '=') fi - # Use awk for float comparison if awk -v v="$val" -v lo="$lo" -v hi="$hi" 'BEGIN{exit !(v>=lo && v<=hi)}'; then printf " PASS %-14s = %s (expected [%s, %s])\n" "$name" "$val" "$lo" "$hi" else @@ -96,17 +111,28 @@ check_meas_range() { fi } -# V(out) max: 5·(1−e⁻⁵) ≈ 4.966 V — accept 4.9 to 5.0 check_meas_range "vout_max" 4.9 5.0 -# Steady-state average (last 1 ms): > 4.9 V check_meas_range "vout_ss" 4.9 5.0 -# τ crossing time ≈ 1 ms — accept 0.9 ms to 1.1 ms check_meas_range "tau_rise" 0.0009 0.0011 +# ── Verify .raw file is owned by the host uid (proves no DAC bypass) ──────── +if [[ ! -f "$RAW_PATH" ]]; then + echo " FAIL $RAW was not produced" + PASS=0 +else + RAW_UID=$(file_uid "$RAW_PATH") + if [[ "$RAW_UID" != "$HOST_UID" ]]; then + echo " FAIL $RAW uid=$RAW_UID does not match host uid $HOST_UID" + PASS=0 + else + printf " PASS %-14s uid=%s (matches host)\n" "$RAW" "$RAW_UID" + fi +fi + echo "" if [[ "$PASS" -eq 1 ]]; then echo "==> All checks PASSED." else - echo "==> Test FAILED – one or more .meas values out of range." + echo "==> Test FAILED – one or more checks did not pass." exit 1 fi diff --git a/wrappers/ltspice b/wrappers/ltspice index 3d22278..e5ec803 100644 --- a/wrappers/ltspice +++ b/wrappers/ltspice @@ -1,2 +1,2 @@ #!/usr/bin/env bash -exec wine "/root/.wine/drive_c/Program Files/ADI/LTspice/LTspice.exe" "$@" +exec wine "${WINEPREFIX:-$HOME/.wine}/drive_c/Program Files/ADI/LTspice/LTspice.exe" "$@"