Everything Linux-specific lives here; the rest of the repo is shared with the Windows build.
| Path | Purpose |
|---|---|
native/ |
Linux platform backend (platform_linux.cc), compiled by native/binding.gyp on Linux |
scripts/build-in-wsl.sh |
Build and test from a Windows checkout via WSL2 (verified path) |
scripts/build-in-docker.sh, docker/Dockerfile |
Same, in an Ubuntu 22.04 container (unverified locally; CI verifies) |
scripts/run-tests.sh |
Build harness + addon and run the Linux test set |
scripts/apprentice.sh |
Dev launcher with a ptrace-policy check |
scripts/smoke-package.sh |
CI check that a packaged AppImage actually launches a window |
packaging/electron-builder.linux.json |
AppImage/deb config (electron-builder --linux --config Linux/packaging/electron-builder.linux.json) |
packaging/after-install.sh |
The .deb's postinstall: setcap cap_sys_ptrace+ep on the installed binary |
packaging/apprentice.desktop |
Menu entry template for a manually-installed AppImage (the .deb gets its own, generated by electron-builder) |
packaging/99-apprentice-ptrace.conf |
sysctl drop-in for the AppImage's ptrace opt-in (see below) |
scripts/package-arch.sh |
Builds the Apprentice-<version>-linux-x64.tar.gz the Arch package below installs from |
packaging/arch/PKGBUILD, packaging/arch/apprentice.install |
Arch Linux / CachyOS package (makepkg -si from packaging/arch/) |
scripts/smoke-arch.sh |
Builds/installs/launches the Arch package in a clean archlinux:latest container |
test-harness/harness.c |
ELF counterpart of test-harness/harness.c for the native tests |
test-harness/build-wine-harness.sh |
Builds the Windows harness with mingw-w64 for the Wine (Proton path) tests; see test-harness/README.md |
dist/ |
Build output (gitignored) |
Prerequisites in the distro (Ubuntu 22.04): build-essential python3 rsync curl, plus two Node versions:
- Node 22 on PATH: builds the addon (node-gyp headers
22.12.0). - Node 26 for vitest: Node 22 cannot import
.nodefiles in tests ("Unknown file extension .node"). PointTEST_NODE_BINat itsbindirectory.
Install Node 26 from the official tarball (checksum-verified), as root:
mkdir -p /root/dl && cd /root/dl
V=v26.13.0; F=node-$V-linux-x64.tar.xz
curl -fsSLo $F https://nodejs.org/dist/$V/$F
curl -fsSLo SHASUMS256.txt https://nodejs.org/dist/$V/SHASUMS256.txt
grep " $F\$" SHASUMS256.txt | sha256sum -c -
tar -xJf $F -C /opt
/opt/node-$V-linux-x64/bin/node -v
Then, from the checkout:
wsl.exe -d Ubuntu-22.04 -u root -- bash -lc "cd /mnt/d/<path-to-checkout> && TEST_NODE_BIN=/opt/node-v26.13.0-linux-x64/bin bash Linux/scripts/build-in-wsl.sh"
Without TEST_NODE_BIN the addon builds but addon_smoke.test.ts fails on Node 22.
The sources are synced to ~/apprentice-build (WSL filesystem), built there, and the addon and
harness are copied to Linux/dist/. The checkout's own node_modules and native/build are never used.
The Linux backend is partly implemented. Done: process listing, naming (Wine-aware), attach and
liveness (/proc), and the whole read side: regions (/proc/<pid>/maps), memory reads
(process_vm_readv, /proc/<pid>/mem fallback), module listing (ELF and Wine PE) and export
resolution (PE export directory, ELF .dynsym). Writes, injection, threads, ptrace write-watch and
Mono are still stubs, IsSupported() is false, and write operations refuse. The tests that need
only the read side run on Linux (the list in scripts/run-tests.sh); the rest need the later tasks.
The PE/ELF parsers are covered by native/tests/module_parse_test.cc. When wine and mingw-w64
are installed, run-tests.sh also runs a second set against the Windows harness under real Wine
(APPRENTICE_HARNESS=wine, see test-harness/README.md).
Attaching to another process is restricted by Yama (ptrace_scope). Options:
run as root, sudo setcap cap_sys_ptrace+ep <binary>, or
sudo sysctl kernel.yama.ptrace_scope=0.
The .deb package grants CAP_SYS_PTRACE automatically (packaging/after-install.sh
runs setcap on the installed binary at install time) — nothing further to do unless
setcap is missing (sudo apt install libcap2-bin) or your filesystem doesn't support
file capabilities, in which case fall back to the sysctl below.
The AppImage cannot carry a capability bit at all — it's a single file mounted
read-only at run time, so there is nothing for setcap to act on — so its only route
is the Yama sysctl:
sudo sysctl kernel.yama.ptrace_scope=0 # this login session only
sudo cp Linux/packaging/99-apprentice-ptrace.conf /etc/sysctl.d/ && sudo sysctl --system # persists across reboots
src/main/ipc.ts's ptraceHelp() picks between these two messages at runtime
(process.env.APPIMAGE is set by electron-builder's AppImage launcher, so it doubles
as "am I running as an AppImage" without any packaging-specific code needing to say so
explicitly) when a write-watch capture or process attach is denied by Yama.
bash Linux/scripts/build-in-docker.sh --package # or build-in-wsl.sh's equivalent flow by hand
bash Linux/scripts/smoke-package.sh # extracts the AppImage and confirms it launches
Produces Linux/dist/Apprentice-<version>.AppImage and Linux/dist/Apprentice-<version>.deb.
electron-builder --linux --config Linux/packaging/electron-builder.linux.json is the
underlying command; it mirrors package.json's own build config (same files list) but
points at the Linux-built Linux/dist/memory_addon.node instead of the Windows build's
native/build/Release/memory_addon.node, and swaps the .ico icon for App Icon/Apprentice.png.
Running the AppImage needs fuse2 (sudo apt install libfuse2 on Ubuntu 22.04+, where it's
no longer pulled in by default). Without it, either install libfuse2 or run with
./Apprentice-<version>.AppImage --appimage-extract-and-run, which unpacks to a temporary
directory and runs from there instead of mounting via FUSE — the same fallback
smoke-package.sh uses in CI (there via a plain --appimage-extract since the smoke test
doesn't need the AppImage to clean itself up afterward).
The .deb installs to /opt/Apprentice/, symlinks the binary to /usr/bin/apprentice, and
registers a menu entry — standard electron-builder/fpm layout, not anything specific to this
app. packaging/apprentice.desktop is for the AppImage instead, which (being one portable
file) has no installer to register itself: copy it to ~/.local/share/applications/ and
point its Exec= line at wherever you placed the AppImage.
Proton note: Apprentice itself is always a native Linux binary (AppImage, deb, or the Arch package below) — it is never run through Proton/Wine itself. It is the game you attach to that may be a Windows build running under Proton/Wine; see "Status" above for what that path supports today.
bash Linux/scripts/package-arch.sh # writes Linux/dist/Apprentice-<version>-linux-x64.tar.gz
# and syncs PKGBUILD's _pkgver to match
cp Linux/dist/Apprentice-*-linux-x64.tar.gz Linux/packaging/arch/
(cd Linux/packaging/arch && makepkg -si) # build + install, asks for sudo for pacman -U
bash Linux/scripts/smoke-arch.sh # optional: builds/installs/launches it in a clean archlinux:latest container
package-arch.sh reuses electron-builder.linux.json from the AppImage/deb build above,
just with the dir target instead of AppImage/deb — that leaves the app as a plain
unpacked directory (Linux/dist/linux-unpacked) rather than wrapping it in an installer, which
is what a tarball needs. package-arch.sh also rewrites PKGBUILD's _pkgver line to match
the version it just read from package.json, so the committed PKGBUILD and the tarball it
names can never drift apart across a version bump — re-run package-arch.sh rather than
hand-editing _pkgver. Linux/packaging/arch/PKGBUILD then only unpacks and installs that
tarball: no separate Arch-specific build step. It installs to /opt/apprentice with a
/usr/bin/apprentice symlink and a .desktop entry, same layout shape as the .deb.
Linux/packaging/arch/apprentice.install's post_install/post_upgrade run the same setcap cap_sys_ptrace+ep step packaging/after-install.sh runs for the .deb (see "ptrace
permissions" above) — idempotent, and it never fails the install if setcap (package libcap)
is missing.
On CachyOS specifically: Steam's default library path, ~/.local/share/Steam, is already one
of the roots src/main/steamLibrary.ts checks, so game discovery needs no extra configuration.
Proton-CachyOS games work unchanged — nothing in this package or Apprentice's own code depends
on which Proton build the game runs under.
Running the AppImage path (above) on Arch/CachyOS instead of this package still needs
fuse2 (pacman -S fuse2, or ./Apprentice-<version>.AppImage --appimage-extract-and-run
without it, same as the general AppImage note above) — the Arch package in this section
avoids that entirely since it isn't an AppImage.