Skip to content

Latest commit

 

History

History

Linux

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Apprentice on Linux

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)

Build from Windows (WSL2)

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 .node files in tests ("Unknown file extension .node"). Point TEST_NODE_BIN at its bin directory.

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.

Status

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).

ptrace permissions

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.

Packaging (AppImage / deb)

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.

Packaging (Arch Linux / CachyOS)

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.