215 lines
13 KiB
Markdown
215 lines
13 KiB
Markdown
# Houdini image — design notes and debugging history
|
|
|
|
Context for whoever (Claude) touches this Dockerfile next. Simon doesn't need
|
|
to read this — it's implementation reasoning, not usage docs (those are in
|
|
`README.md`).
|
|
|
|
## Why these choices
|
|
|
|
- **Base image + install command copy SideFX's own `docker/Ubuntu/Dockerfile`**
|
|
(shipped inside the Houdini tarball under `docker/Ubuntu/`), not guessed.
|
|
That reference folder has been deleted from this repo once its contents
|
|
were folded in here — it only ever served as a source to copy from, and
|
|
keeping it around invited drift between two copies of the same info.
|
|
- **Ubuntu 22.04, not Rocky/RHEL.** The tarball is `gccXX.X`-tagged (here
|
|
`gcc14.2`), meaning the runtime needs a `libstdc++` new enough for that
|
|
build. Ubuntu's base image keeps `libstdc++6` updated via regular package
|
|
updates; RHEL-family distros pin their system toolchain for the whole OS
|
|
lifetime, so a Rocky 9 base risks a `GLIBCXX_x` version mismatch depending
|
|
on which Houdini build you're on. This was a real, not hypothetical,
|
|
concern with the 22.0.429/gcc14.2 build tested here.
|
|
- **`--auto-install --accept-EULA <date>`, no `--make-dir`, no
|
|
`--install-license`.** An earlier draft of this Dockerfile invented an
|
|
`--install-license <path>` flag that doesn't exist, based on scripted-
|
|
install docs that actually describe a *different* tool
|
|
(`houdini_installer`, the newer credential-based launcher — irrelevant
|
|
here, since it needs network + stored SideFX credentials at build time,
|
|
which is worse for a private-registry-baked-tarball workflow). The
|
|
flags actually used are copy-pasted from SideFX's own Dockerfile.
|
|
- **EULA date is a plain `YYYY-MM-DD`, no `SideFX-` prefix.** The
|
|
`SideFX-YYYY-MM-DD` form belongs to `houdini_installer`'s config file, not
|
|
to `houdini.install`. Mixing the two up silently breaks `--accept-EULA`
|
|
matching. Value as of writing: `2021-10-13` (from
|
|
https://www.sidefx.com/legal/license-agreement/ — SideFX can update this
|
|
without notice, it's not pinned/checked anywhere).
|
|
- **`/opt/hfs` symlink is created explicitly.** `houdini.install`'s default
|
|
behavior only creates a major.minor symlink (`/opt/hfs22.0` here), not a
|
|
generic `/opt/hfs`. The Dockerfile globs for the installed `hfs[0-9]*` dir
|
|
and symlinks it to `/opt/hfs` itself, so `houdini-run` and the
|
|
`/etc/profile.d` snippet don't need to know the exact version.
|
|
|
|
## SideFX Labs — baked in, not installed at runtime
|
|
|
|
Simon hit `No such file or directory` using Houdini's own in-app package
|
|
installer for Labs. Root cause (confirmed, not guessed): this image never
|
|
installed `ca-certificates`, so any HTTPS download from inside the container
|
|
fails TLS verification — reproduced directly with `wget` against
|
|
`github.com` inside a test build (`wget` exit 5, "cannot verify github.com's
|
|
certificate"). The in-app installer's `No such file or directory` is almost
|
|
certainly it shelling out to `git` (also never installed) for the same
|
|
underlying reason: no working way to fetch anything over HTTPS. `wget` was
|
|
already installed and used fine elsewhere in this Dockerfile, so the gap was
|
|
specifically the missing CA bundle, not a missing downloader. Fixed by
|
|
adding `ca-certificates` to the apt-get install list (`base` stage).
|
|
|
|
Rather than also install `git` and instruct Simon to run the in-app
|
|
installer, Labs is fetched and baked in at build time:
|
|
|
|
- Downloaded from a GitHub **release tag**
|
|
(`https://github.com/sideeffects/SideFXLabs/archive/refs/tags/<tag>.tar.gz`),
|
|
pinned via `ARG SIDEFXLABS_VERSION`, same pattern as `HOUDINI_EULA_DATE` —
|
|
unpinned upstream, pick the newest tag whose `target_commitish` is
|
|
`Development` and whose bundled `SideFXLabs.json` `enable` range covers
|
|
this image's Houdini version. Checked via the GitHub releases API at
|
|
writing time: tag `22.0.440` declares
|
|
`"enable": "houdini_version >= '22.0' and houdini_version < '22.5'"`,
|
|
which covers this image's `22.0.429`.
|
|
- Extracted straight into **`$HFS/packages/SideFXLabs22.0/`**, with the
|
|
vendored `SideFXLabs.json` copied up to `$HFS/packages/SideFXLabs22.0.json`
|
|
unmodified. This location was not a guess: `$HFS/packages` is where
|
|
Houdini's *own* shipped optional packages already live (confirmed by
|
|
listing the real installed tree in a test build — `apex`, `kinefx`,
|
|
`ocio`, `sculpt`, `shotbuilder`, `texturepaint` all follow the exact same
|
|
`<name>.json` + `<name>/` pattern there). It's also documented as a
|
|
package search path independent of `$HOME`
|
|
(https://www.sidefx.com/docs/houdini/ref/plugins.html), which matters
|
|
specifically because distrobox bind-mounts the host's `$HOME` over
|
|
whatever the image ships under `$HOUDINI_USER_PREF_DIR` — anything baked
|
|
in there would be invisible at runtime. No edits to the vendored JSON were
|
|
needed: its default `"$HOUDINI_PACKAGE_PATH/SideFXLabs22.0"` already
|
|
resolves correctly, since `HOUDINI_PACKAGE_PATH` just means "the directory
|
|
this json file itself lives in".
|
|
- Verified end-to-end in a real build: `ca-certificates` fix lets `wget`
|
|
fetch the release tarball; extracted tree has 452 `.hda` files under
|
|
`otls/`; `$HFS/packages/SideFXLabs22.0.json` present and well-formed.
|
|
**Not** verified against a real license + GUI launch (Apprentice
|
|
activation needs a real SideFX login, out of scope for this check) — the
|
|
Labs menu actually appearing in a running Houdini session is unconfirmed,
|
|
though the mechanism matches Houdini's own shipped packages exactly.
|
|
|
|
## Runtime library list
|
|
|
|
SideFX's own package list in `docker/Ubuntu/Dockerfile` is built for headless
|
|
`hython`/`sesinetd` use (their `docker-compose.yml` only ever runs `hython`
|
|
or `sesinetd` as separate services, never the GUI). Running the actual
|
|
`houdinifx` GUI needed 4 more libs, each found by `ldd`-checking the real
|
|
binaries/plugins in a test build and confirmed missing before adding:
|
|
|
|
- `libegl1` — EGL dispatch, `houdinifx-bin` linked against it directly.
|
|
- `libatomic1` — `houdinifx-bin`/`hython-bin` both needed it.
|
|
- `libxkbfile1` — same.
|
|
- `libxcb-cursor0` — this one took the longest to find. Qt6's `xcb` platform
|
|
plugin (`libqxcb.so`, under
|
|
`/opt/hfs<ver>/dsolib/Qt_plugins/platforms/`) hard-depends on it (a known
|
|
Qt6 change from Qt5, where cursor theming moved from Xcursor to
|
|
xcb-cursor). Without it, Houdini fails at Qt platform-plugin init with a
|
|
generic "no Qt platform plugin could be initialized" — nothing points at
|
|
the actual missing symbol unless you `ldd` the plugin directly.
|
|
|
|
If a future Houdini/Qt version adds new failures here, the fastest diagnosis
|
|
path is: `ldd` every `.so` under `dsolib/Qt_plugins/` recursively (see git
|
|
history of this file / session transcript for the exact one-liner), not
|
|
guessing from the vague top-level Qt error message.
|
|
|
|
## `houdini-run` strict-mode compatibility
|
|
|
|
`houdini_setup_bash` (sourced by `houdini-run` to set up `$HFS`/`PATH`/etc.)
|
|
is not written for `set -euo pipefail`:
|
|
- It references variables that are legitimately unset in some code paths
|
|
(`set -u` would abort).
|
|
- It does things like `d=$(which java)` expecting a silent empty result on
|
|
failure (`set -e` would abort on the failed `which`).
|
|
|
|
Fix: wrap the `source ./houdini_setup` call with `set +euo pipefail` /
|
|
`set -euo pipefail` around it, rather than trying to patch the vendor
|
|
script.
|
|
|
|
## NixOS / distrobox host environment leakage
|
|
|
|
Distrobox forwards the *host* shell's environment into the container by
|
|
design (needed for `DISPLAY`, `WAYLAND_DISPLAY`, `XDG_*`, D-Bus, etc. to
|
|
work at all for GUI apps). On a NixOS host that also sets its own
|
|
`LD_LIBRARY_PATH` (observed here pointing at a Nix-store `alsa-lib` built
|
|
against a much newer glibc than Ubuntu 22.04 ships), that leaks into the
|
|
container and crashes Houdini's binaries at dynamic-link time
|
|
(`GLIBC_ABI_GNU2_TLS not found`, etc.) — a library meant for the host's
|
|
libc, loaded into a binary linked against the container's older libc.
|
|
|
|
`houdini_setup_bash` only ever *prepends* to `LD_LIBRARY_PATH` if it's
|
|
already set — it never resets it. So `houdini-run` explicitly
|
|
`unset LD_LIBRARY_PATH` before sourcing it, discarding whatever leaked in
|
|
from the host and letting Houdini build its own from scratch. This is safe
|
|
on any host, not just NixOS — it just happens to matter there.
|
|
|
|
This class of problem (host env leaking into the container and colliding
|
|
with container-local binaries) is worth checking first if a *new*,
|
|
unexplained crash shows up after this image has otherwise worked before.
|
|
|
|
## sesinetd / Apprentice licensing — the two real bugs
|
|
|
|
Both now handled automatically inside `houdini-run` (not just documented as
|
|
manual steps), found via `/var/log/sidefx/sesinetd.log` after a genuinely
|
|
confusing symptom in the GUI:
|
|
|
|
1. **License file permissions.** `sesinetd` drops privileges to `www-data`
|
|
after being started as root via `sudo`. The license file bind-mounted
|
|
from the host (`~/.houdini-apprentice/licenses:/usr/lib/sesi/licenses`)
|
|
is created with `touch` as the host user, so it's not writable by
|
|
`www-data` inside the container. Result: activation's network round-trip
|
|
to SideFX succeeds fine (curl-level success), but writing the resulting
|
|
license locally fails — surfaced in the GUI as the deeply misleading
|
|
**"Success (curl error 0)"** error dialog that just loops back to the
|
|
install-license prompt with no indication anything is actually wrong.
|
|
The real error only shows up in `/var/log/sidefx/sesinetd.log`:
|
|
`[FATAL - Licensing] [Manager] - Failed to open license file.`
|
|
Fix: `houdini-run` now `chmod 666`s the license file before starting
|
|
`sesinetd`, every time, unconditionally.
|
|
2. **Stuck self-signed TLS cert.** If cert generation for `sesinetd`'s HTTPS
|
|
listener gets interrupted (observed here across our own repeated
|
|
container kill/restart cycles during testing), it leaves 0-byte
|
|
`/usr/lib/sesi/ssl/auth.cert` / `auth.priv` behind. On next start,
|
|
`sesinetd` tries to read those, fails (`Bad certificate chain: 'PEM
|
|
lib'`), and — rather than regenerating — spins retrying at ~100% CPU
|
|
forever, never actually finishing startup or opening port 1715.
|
|
No error surfaces in the GUI for this one at all; the only symptom is a
|
|
pegged CPU core and a license dialog that never succeeds. Fix:
|
|
`houdini-run` deletes those two files first if either is 0 bytes.
|
|
|
|
Also confirmed, contra a plausible-sounding but wrong worry: SideFX's own
|
|
docker README warns that regular (non-floating) licenses need special
|
|
"container enabled" entitlements to run `sesinetd` in Docker, working around
|
|
it with `network_mode: host` in their own `docker-compose.yml`. That warning
|
|
is about Docker's default *isolated* network namespace confusing the
|
|
node-lock check. Distrobox shares the host's network namespace by default
|
|
(no `--unshare-netns`), which is the same situation as SideFX's own
|
|
workaround — so this doesn't apply here, and was confirmed by an actual
|
|
successful Apprentice activation.
|
|
|
|
## Confidence table (as of the last real end-to-end test)
|
|
|
|
Everything below was confirmed with an actual build + an actual
|
|
`distrobox create` + `houdini-run` launch on a real AMD iGPU (Vega,
|
|
`raven`) host, not just read from docs.
|
|
|
|
| Area | Confidence | Note |
|
|
|---|---|---|
|
|
| Base image + install command | High | Matches SideFX's own Dockerfile; built end to end against the real 22.0.429/gcc14.2 tarball. |
|
|
| `/opt/hfs` symlink | High | Confirmed by test build. |
|
|
| EULA date value | Medium | `2021-10-13` as of writing; unpinned, SideFX can change it. |
|
|
| Licensing works under distrobox's shared netns | High | Confirmed with a real Apprentice activation. |
|
|
| Runtime library list | High | Vendor's list + 4 libs added, all confirmed via `ldd` and an actual successful launch. |
|
|
| `houdini-run` strict-mode compatibility | High | Confirmed end to end. |
|
|
| `sesinetd` license-file-permission and stuck-cert fixes | High | Both bugs actually hit during testing, root-caused via `/var/log/sidefx/sesinetd.log`, fixed in `houdini-run`, confirmed working after the fix. |
|
|
| distrobox default hostname | Medium | Pinned explicitly (`houdini-apprentice`) rather than trusting the default, since Apprentice node-locks to hostname too. |
|
|
| GUI rendering via XWayland (Hyprland host) | High | Confirmed real AMD hardware acceleration (`glxinfo`: direct rendering, AMD Radeon Vega renderer, not llvmpipe). First launch was slow (container's first-run package install + cold start); later launches fluid. |
|
|
| AMD OpenCL (sims) | Low | Not set up; needs ROCm/rusticl. GL/Vulkan viewport is unaffected. |
|
|
| NVIDIA variant | Untested | Builds fine (same base, no extra packages), but never run against real NVIDIA hardware — only the `amd` target has been used end to end. |
|
|
| SideFX Labs baked into `$HFS/packages` | Medium | Build-time fetch + extraction confirmed (452 `.hda`s, well-formed json), and the target path matches Houdini's own shipped packages exactly — but never confirmed against a real license + GUI launch that the Labs menu actually appears. |
|
|
|
|
## Repo-wide conventions
|
|
|
|
See the top-level `../README.md` for the multi-software repo convention
|
|
(one self-contained folder per app, shared registry setup notes) — that
|
|
file is meant for Simon to read; this one and the top-level `../CLAUDE.md`
|
|
(if one exists) are for future-Claude context.
|