1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
# setup-chess.sh
## Overview
Single-shot installer for a chess analysis stack on Arch Linux: the En Croissant GUI, Stockfish, Lc0, and the nine Maia personality engines (1100–1900 Elo). All artifacts land under `$HOME/.local/`; no root-owned files. The script is idempotent — re-runs replace broken components, preserve user-tuned settings in `engines.json`, and keep custom engine IDs stable across invocations.
Run it once on a fresh Arch install to get a working chess setup, or run it again later (with `--force` or `--component <name>`) to repair a single component without rebuilding the rest.
## Usage
```
./setup-chess.sh # full install / refresh
./setup-chess.sh --force # re-download and rebuild everything
./setup-chess.sh --component lc0 # repair just lc0
./setup-chess.sh --component lc0 --force # force re-install of just lc0
```
Valid `--component` names: `prereqs`, `en-croissant`, `lc0`, `maia`, `stockfish`, `engines-json`, `desktop`.
## What the script does
Each section below maps to one numbered block in `setup-chess.sh`.
### 1. Prerequisites
Checks for the tools and libraries used downstream and installs missing pacman packages with `pacman -S --needed --noconfirm` via `sudo` when not running as root. Always-required: `git`, `curl`, `tar`, `coreutils`, `grep`, `desktop-file-utils`, `fuse2`. The build-toolchain deps (`base-devel`, `meson`, `ninja`, `python`, `pkgconf`, `protobuf`, `zlib`, `openblas`, `eigen`) are installed when a healthy system `lc0` is unavailable, since the AUR path can still fail and fall back to a source build.
### 2. En Croissant AppImage
Downloads the pinned version of the En Croissant AppImage to `~/.local/bin/en-croissant.AppImage`, verifies its SHA256, and marks it executable. Skipped when the AppImage already exists, unless `--force` is set.
### 3. Lc0
Tries paths in order of speed and durability, gating each on a UCI handshake smoke test (`echo uci | timeout 5 <bin> | grep '^id name Lc0'`):
1. **Existing `~/.local/bin/lc0` passes** — keep it as-is.
2. **System `/usr/bin/lc0` passes** — symlink it into `~/.local/bin/lc0`. This is the steady state for an AUR install.
3. **AUR helper available** — `yay -S --needed --noconfirm lc0 lc0-network-sm` (or `paru` if that's what's installed), then symlink. Pacman-managed binaries get re-resolved automatically when Abseil, Protobuf, or OpenBLAS bump SONAMEs — see the BLAS conflict note below.
4. **Source build** — clone the upstream `v0.32.1` tag and build with the project's own `build.sh`. Slower (several minutes), and the resulting binary is dynamically linked, so a later library bump can break it. Used only as the last-resort fallback.
The final binary is run through the smoke test again. If it fails, the script aborts with a pointer to running `lc0` manually to read the load-time error.
### 4. Maia weights and wrappers
Downloads the nine `maia-{1100..1900}.pb.gz` files from the CSSLab Maia release `v1.0` into `~/.local/share/maia/`, verifying each against a pinned SHA256. Existing files are re-validated against the pin; a mismatch triggers a re-download.
Then generates the nine wrapper scripts at `~/.local/bin/maia-{1100..1900}`. Each is a one-line shell script that execs the lc0 binary with the matching `--weights=` flag. Wrappers are regenerated unconditionally so they always point at the current `~/.local/bin/lc0` (handy if step 3 swapped the binary out from under them).
### 5. Stockfish
Probes `/proc/cpuinfo` and selects the best Stockfish binary for the CPU — in order, `vnni512`, `avx512`, `avxvnni`, `avx2`, `bmi2`, `sse41-popcnt`, `x86-64`. Downloads the matching tarball from the pinned Stockfish release (`sf_18`), verifies its SHA256, extracts it into `~/.local/share/org.encroissant.app/engines/stockfish/`, and runs a UCI handshake to confirm the binary works.
### 6. engines.json
Writes `~/.local/share/org.encroissant.app/engines/engines.json` with Stockfish plus the nine Maia entries. Before writing, backs up the existing file to `engines.json.bak.<timestamp>`.
The Python that builds the JSON does three things worth knowing:
- **Engine IDs are preserved by name.** If an entry named `Stockfish` already exists, its UUID is reused; otherwise a fresh one is generated. Same for each Maia engine. This keeps any per-engine state En Croissant tracks (analysis history, custom names) stable across re-runs.
- **User-tuned settings are preserved.** The script ships conservative defaults (`Stockfish: Threads=1, Hash=16, MultiPV=1`), but if the existing entry has different settings, those are kept instead of clobbered. Re-running the script never resets a tuned engine to defaults.
- **Unknown engines are preserved.** Any entry in the existing file whose name isn't one of the ten managed ones (Stockfish + 9 Maias) is appended to the new file as-is. Hand-added engines like Komodo or a second Stockfish build survive a re-run.
### 7. Desktop file
Writes `~/.local/share/applications/en-croissant.desktop` so En Croissant appears in app launchers (rofi, fuzzel, Wayland app drawers, etc.) and runs `update-desktop-database` to refresh the cache.
## Known issues
### BLAS provider conflict
The AUR `lc0` package depends on `openblas`, which on Arch is provided by the `blas-openblas` package. `blas-openblas` is mutually exclusive with the reference netlib BLAS bundle (`blas` + `cblas` + `lapack`). A system that has the netlib trio installed will see pacman refuse the lc0 transaction:
```
:: blas-openblas-0.3.33-1 and blas-3.12.1-2 are in conflict. Remove blas? [y/N]
error: unresolvable package conflicts detected
```
The script's `lc0` section detects this state before invoking the AUR helper (because `--noconfirm` would just abort), prints a warning with the resolution command, and falls through to the source-build path for that run.
To resolve once and for all:
```
sudo pacman -S blas-openblas
```
Pacman prompts to remove `blas`, `cblas`, and `lapack` (answer `y`), then installs `blas-openblas`. `blas-openblas` provides the same `libblas.so` / `libcblas.so` / `liblapack.so` ABIs, so any package linked against BLAS (NumPy, SciPy, R, Octave, GIMP G'MIC, OpenCV, etc.) keeps working — and typically runs faster, since OpenBLAS is heavily optimized while the netlib reference impl is not.
The swap is reversible (`sudo pacman -S blas cblas lapack` flips it back), and most Arch users with scientific software already run on `blas-openblas` by default.
### En Croissant: Lc0/Maia hangs when playing engine as Black
When you set up a "play vs engine" game in En Croissant with the engine as Black and yourself (the human) as White with **no clock**, En Croissant fills your `wtime` field with `4294967295` (UINT32_MAX) to signal "no time control." Lc0 parses `wtime` as a bounded integer and rejects anything above ~2.1 billion ms:
```
go wtime 4294967295 btime 30000 winc 0 binc 2000
error out of range value 4294967295
```
The engine never issues `bestmove`. The UI sits there waiting forever. This affects every Maia engine and any other Lc0-based engine, since they all share the same UCI parser.
**Workaround:** when setting up the game, give White a finite time control. Anything reasonable (60 min + 0 inc, even 1 day) is well below Lc0's limit. The `go: {t: "Nodes", c: 1}` setting that ships in `engines.json` only governs analysis mode, not play mode — the time control comes from the game dialog.
This is an En Croissant bug; a fix would either omit `wtime` entirely when a side has no clock, or cap the sentinel to a value Lc0 accepts (e.g. `2147483647`).
## Files this script manages
```
~/.local/bin/en-croissant.AppImage En Croissant GUI
~/.local/bin/lc0 Lc0 engine (binary or symlink to /usr/bin/lc0)
~/.local/bin/maia-{1100..1900} Maia wrapper scripts (shell, exec lc0)
~/.local/share/maia/maia-{1100..1900}.pb.gz Maia neural network weights
~/.local/share/org.encroissant.app/engines/
stockfish/stockfish-ubuntu-x86-64-* Stockfish binary
engines.json Engine registry read by En Croissant
~/.local/share/applications/en-croissant.desktop Launcher entry
```
|