Layered launch orchestration for Steam games on Linux
One Steam launch string. Per-game configs. GameMode, Gamescope, MangoHUD, and more — assembled automatically.
For Linux gamers who tune every launch — GameMode, CPU affinity, MangoHUD, Gamescope, VRAM hogs, network latency — but do not want a different Steam launch string per title.
LaunchLayer sits in Steam’s Launch Options ahead of %command%. It loads layered config, runs preflight checks, and builds the wrapper chain before your game starts.
| Without LaunchLayer | With LaunchLayer |
|---|---|
gamemoderun mangohud gamescope -W 3440 … %command% pasted per game |
"/path/to/launchlayer" %command% once per game |
| Settings scattered across Steam, shell aliases, and one-off scripts | Plain KEY=VALUE files: profiles → presets → per-game |
No preflight for vm.max_map_count, shader bloat, or VRAM pressure |
Doctor, cache trim, compositor-aware display detection |
Built for a tuned workstation (7900X3D, RTX 3080 Ti, Wayland / Plasma 6), with auto-detection and profiles for Steam Deck, Flatpak Steam, BSD, and WSL2.
Requirements: bash 4.2+, Steam (or any launcher that passes %command%). Optional tools (fzf, gamescope, …) enhance the stack but are not required for basic launches.
Preview the resolved layers and launch chain without starting a game:
./launchlayer --show-config 2357570=== Config for AppID 2357570 (Overwatch®) ===
Layers:
→ profiles/arch-linux.env
→ profiles/nvidia-desktop.env
→ default.env
→ presets/competitive.env
→ games/2357570.env
Launch chain:
gamemoderun → taskset → game-performance → dlss-swapper → gamescope → %command%
Interactive TUI — browse games, preview configs, flip toggles
| I want to… | Go to |
|---|---|
| Get running in five minutes | Quick start |
| Paste into Steam’s Launch Options | Steam integration |
| Browse and edit games interactively | Interactive TUI · screenshots |
| Share configs with similar machines | Community hub |
| Understand the launch pipeline | How a launch works |
| Full CLI command tables | docs/cli.md |
| TUI menus and shortcuts | docs/tui.md |
| Module-level internals | docs/architecture.md |
| Licenses / inject / nest Gamescope | docs/third-party.md |
| Docs map (topic → page) | docs/README.md |
| Section | |
|---|---|
| ▶ | Quick start |
| ◆ | Steam launch options |
| · | What it does |
| ⚙ | How a launch works |
| ≡ | Configuration |
| ⌨ | CLI reference |
| ▤ | Interactive TUI · docs/tui.md |
| ◉ | Community hub |
| ⊞ | System tuning |
| ⚖ | Third-party licenses |
| ☰ | Docs index |
| / | Project layout |
| + | Optional dependencies |
| ✓ | Testing · Release runbook · Changelog |
| ? | FAQ |
| ↗ | Contributing |
| § | License |
- Clone to a stable path (Steam needs a fixed absolute path in Launch Options):
git clone https://github.com/bolens/launch-layer.git ~/launchlayer
cd ~/launchlayer- Run onboarding — completions, symlink, launch string, and machine defaults:
./launchlayer --setup --completions --symlink --print-launch-option --write-local-configThis installs shell completions, adds ~/.local/bin/launchlayer, prints your Steam launch string, and writes launch.d/local.env. Add --systemd for the maintenance timer or --backup-timer for scheduled config backups.
- Paste into Steam — copy the printed string into each game’s Launch Options (same string for every title):
"$HOME/launchlayer/launchlayer" %command%
See Integrating with Steam launch options for UI paths, Flatpak notes, and verification.
- Scaffold a per-game config:
./launchlayer --init-appid 2357570 competitive # by AppID
./launchlayer --init-appid "Overwatch" competitive # by name
./launchlayer --tui # or browse interactively- Sanity check:
./launchlayer --doctorIf Proton titles misbehave, fix vm.max_map_count once — see System tuning.
LaunchLayer hooks into Steam by prefixing the normal game command. Steam replaces %command% with Proton wrappers, the game binary, and any args Steam already knows about; LaunchLayer loads config, runs preflight, builds wrapper chains, then execs that command.
Use the same launch string on every game you want managed. Per-game tuning lives in GAMES_DIR/<AppID>.env—you do not need different launch options per title.
Run onboarding (recommended) or print the string alone:
./launchlayer --setup --symlink --print-launch-option
# or
./launchlayer --setup --print-launch-option
# or (also printed at the end of doctor)
./launchlayer --doctorExample output:
"$HOME/launchlayer/launchlayer" %command%
| Approach | Launch string | Notes |
|---|---|---|
| Absolute path (recommended) | "$HOME/launchlayer/launchlayer" %command% |
Most reliable—Steam’s environment often has a minimal PATH; --print-launch-option prints your real path |
Symlink (after --setup --symlink) |
"$HOME/.local/bin/launchlayer" %command% |
Use the full path to the symlink (realpath ~/.local/bin/launchlayer); bare launchlayer usually fails in Steam |
Rules:
- Keep
%command%at the end. Without it Steam never runs the game binary. - Quote the script path when it contains spaces.
- Do not substitute the game
.exeor Proton command for%command%—LaunchLayer receives the full Steam-built argv automatically. - Replace other wrapper prefixes (
gamemoderun %command%,mangohud %command%,sd0 %command%, etc.) with LaunchLayer; enable those features in config instead (GAMEMODE=1,MANGOHUD=1,DISABLE_STEAM_DECK=1, …).
Per game (typical workflow—repeat for each title, or copy/paste the same string):
- Open Steam → Library
- Right-click the game → Properties
- In General, find Launch Options
- Paste your launch string, e.g.
"$HOME/launchlayer/launchlayer" %command% - Close Properties and launch the game normally
Steam Deck / Big Picture
On Deck: Library → select game → gear icon → Properties → General → Launch Options. The same %command% string applies.
If Steam is installed via Flatpak, the sandbox must read your LaunchLayer install:
- Script under
$HOME(e.g.~/launchlayer/…) → usually works as-is - Script outside
$HOME(e.g./path/to/launchlayer) → grant filesystem access:
flatpak override --user com.valvesoftware.Steam --filesystem=/path/to/launchlayerCheck access and get a tailored hint:
./launchlayer --detect-environment
./launchlayer --doctorThe flatpak-steam profile layers automatically when Flatpak Steam is detected.
After the launch string is set once per game, adjust behavior with per-game configs—not by changing Steam’s field again:
./launchlayer --init-appid 2357570 competitive # scaffold GAMES_DIR/2357570.env
./launchlayer --edit-appid "Overwatch" # open in $EDITOR
./launchlayer --show-config 2357570 # resolved layers + launch chainSteam sets SteamAppId / STEAM_APPID when launching; LaunchLayer uses that to pick GAMES_DIR/<AppID>.env (or auto preset when no file exists).
Preview the resolved chain (terminal—no game start):
SteamAppId=2357570 ./launchlayer --dry-run %command%
# or
./launchlayer --show-config 2357570After a real launch, inspect history:
./launchlayer --launch-stats 2357570
tail ~/.local/state/launchlayer/launch.logHealth check:
./launchlayer --doctor| Symptom | Likely cause | Fix |
|---|---|---|
| Game starts but LaunchLayer never runs | Launch string missing or wrong game | Confirm Launch Options on that title; path must point to the launchlayer script |
| Game never starts / instant exit | %command% omitted |
Use "/path/to/launchlayer" %command%—not the script alone |
Permission denied or No such file |
Bad path or Flatpak sandbox | Use absolute path; for Flatpak Steam see Flatpak Steam |
| Wrong preset / no per-game config | No GAMES_DIR file yet |
./launchlayer --init-appid APPID preset or --tui |
| Double wrappers / odd behavior | Old launch option left in place | Remove gamemoderun, mangohud, dlss-swapper, sd0, etc. from Steam; configure via LaunchLayer (GAMEMODE, MANGOHUD, DLSS_SWAPPER, DISABLE_STEAM_DECK) |
| Proton crashes / map errors | Low vm.max_map_count |
./launchlayer --sysctl install — see System tuning |
| Area | Behavior | |
|---|---|---|
| ≡ | Layered config | Plain KEY=VALUE files stack: profiles → default.env → local.env → preset → per-game overrides |
| ◦ | Auto-detection | Distro, GPU, compositor, display resolution/VRR, X3D V-Cache CPU mask, native vs Proton |
| ⊛ | Preflight | Checks vm.max_map_count, shader/compat cache size (optional trim), VRAM, GPU power/processes, disk space, concurrent launches |
| ⚡ | Runtime tuning | Network (ethtool), PipeWire latency, NVIDIA power mode, Proton/DXVK/VKD3D env, shader-cache boost, DLSS/FSR4/XeSS upgrade knobs |
| ◆ | VRAM management | Pause configured systemd units (Sunshine, etc.) during play; resume on exit |
| → | Launch chain | LAUNCH_WRAPPERS_BEFORE → GameMode → CPU affinity → game-performance → DLSS swapper → LAUNCH_WRAPPERS → Gamescope (--mangoapp when both Gamescope and MangoHUD) → MangoHUD → game |
| ▤ | CLI + TUI | Manage configs, backup/restore, doctor checks, optional Community hub |
Use --dry-run %command% to print the resolved config and chain without starting the game.
When Steam invokes the script, run_game_launch in lib/launch.sh runs this pipeline:
flowchart LR
A["↻ Recover state"] --> B["· Resolve AppID"]
B --> C["≡ Load config"]
C --> D["◦ Detect flags"]
D --> E["▤ Hardware defaults"]
E --> F["⊛ Preflight"]
F --> G["◆ VRAM hogs"]
G --> H["⚡ Runtime tune"]
H --> I["→ Build chain"]
I --> J["▶ Exec game"]
style A fill:#1e293b,stroke:#475569,color:#e2e8f0
style B fill:#1e293b,stroke:#475569,color:#e2e8f0
style C fill:#1e3a5f,stroke:#3b82f6,color:#e2e8f0
style D fill:#1e293b,stroke:#475569,color:#e2e8f0
style E fill:#1e293b,stroke:#475569,color:#e2e8f0
style F fill:#422006,stroke:#f59e0b,color:#fef3c7
style G fill:#1e293b,stroke:#475569,color:#e2e8f0
style H fill:#1e293b,stroke:#475569,color:#e2e8f0
style I fill:#14532d,stroke:#22c55e,color:#dcfce7
style J fill:#14532d,stroke:#22c55e,color:#dcfce7
Step-by-step (text)
- Recover stale state — Resume VRAM-heavy services left paused after a crash (
lib/vram.sh) - Resolve AppID — From
SteamAppId,STEAM_APPID, or launch argv (lib/config.sh) - Load layered config — Profiles →
default.env→local.env→ preset or per-game file; thenapply_defaultsandapply_detected_defaults - Detect game flags — Native vs Proton, EAC/BattlEye, engine hints (
lib/steam/detect.sh) - Auto hardware defaults — X3D CPU mask, display resolution/refresh for Gamescope (
lib/hardware/) - Parse extra args — Split
GAME_EXTRA_ARGSinto argv appended after%command% - Preflight checks — Skipped when
BENCHMARK=1(lib/preflight.sh): sysctl, shader/compat caches, VRAM, GPU power/processes, disk, concurrent launch guard - Tool warnings & anticheat guardrails — Missing optional tools; warn on risky settings for EAC/BattlEye titles
- VRAM hogs — Optionally pause configured systemd user units with refcount + exit trap (before runtime tuning)
- Runtime tuning — Network (
ethtool), PipeWire latency, CPU perf profile, NVIDIA power mode, Proton/DXVK/VKD3D env - Build launch chain — Assemble wrappers per
build_launch_chaininlib/runtime/chain.sh - Exec —
PRE_LAUNCH_CMD→ run chain +%command%+ extras →POST_LAUNCH_CMD; log to~/.local/state/launchlayer/launch.log
For module-level detail, see docs/architecture.md. Config-key cheat sheets: docs/cli.md. Inject / license policy: docs/third-party.md. Docs map: docs/README.md.
Settings are plain KEY=VALUE files. Later layers override earlier ones. Full key tables: docs/cli.md. TUI editors: docs/tui.md.
flowchart BT
P["0 · profiles/*.env<br/><i>machine</i>"]
D["1 · default.env<br/><i>global</i>"]
L["2 · local.env<br/><i>this machine</i>"]
R["3 · presets/*.env<br/><i>gameplay</i>"]
G["4 · games/AppID.env<br/><i>per-game — wins</i>"]
P --> D --> L --> R --> G
style P fill:#312e81,stroke:#6366f1,color:#e0e7ff
style D fill:#1e3a5f,stroke:#3b82f6,color:#dbeafe
style L fill:#164e63,stroke:#06b6d4,color:#cffafe
style R fill:#365314,stroke:#84cc16,color:#ecfccb
style G fill:#14532d,stroke:#22c55e,color:#dcfce7
| Order | File | Purpose |
|---|---|---|
| 0 | launch.d/profiles/*.env |
Machine profiles (auto-detected or via LAUNCHLAYER_PROFILES) |
| 1 | launch.d/default.env |
Global infrastructure defaults |
| 2 | launch.d/local.env |
Machine-local overrides (gitignored; from --write-local-config; force-overwrites profile/default keys) |
| 3 | launch.d/presets/*.env |
Gameplay preset via per-game INCLUDE= or auto standard/native when no per-game file |
| 4 | games/<AppID>.env |
Per-game overrides in GAMES_DIR (wins over everything above) |
Preset loading: If GAMES_DIR/<AppID>.env exists, only that file is loaded (plus its INCLUDE= chain). Auto standard.env / native.env applies only when no per-game file exists. Per-game files usually start with INCLUDE=presets/competitive.env (or another preset) and then override individual keys.
After files load, runtime detection fills any still-unset keys: PipeWire latency, network tuning, NVIDIA checks, VRAM hog filtering, disk thresholds, and platform guardrails (Steam Deck, WSL2, containers).
| Location | Default | Contents |
|---|---|---|
LAUNCHLAYER_CONFIG_DIR |
repo root | launch.d/ shipped layers + optional local.env |
LAUNCHLAYER_GAMES_DIR |
~/.local/share/launchlayer/games |
Per-game <AppID>.env files |
~/.config/launchlayer/ |
user prefs | tui.conf, backup.conf, hub.conf |
~/.local/state/launchlayer/ |
runtime | Launch logs, PID/stamp files (see Runtime state) |
Per-game configs are not stored under launch.d/ in git—only in GAMES_DIR. Example: examples/games/2357570.env (Overwatch 2).
When no per-game .env exists:
| Path | |
|---|---|
| N | Native Linux build → presets/native.env |
| P | Everything else (Proton) → presets/standard.env |
| Preset | Use case |
|---|---|
standard |
Default Proton titles — GameMode on |
competitive |
Online / latency-sensitive — extends standard with MangoHUD, Gamescope, VRR, VRAM hogs, network tune |
lightweight |
2D / indie — minimal overhead |
native |
Native Linux — skips Proton env and cache checks |
Init with: ./launchlayer --init-appid APPID competitive
Profiles in launch.d/profiles/ layer automatically based on detection, or set explicitly:
LAUNCHLAYER_PROFILES=steam-deck,flatpak-steam # comma-separated
# legacy: LAUNCHLAYER_PROFILE=steam-deck| Category | Profiles |
|---|---|
| Distros | arch-linux, debian, fedora, suse, nixos, alpine, void, gentoo, solus, clearlinux, immutable-linux |
| Environment | steam-deck, flatpak-steam, wsl2, bsd, macos, non-systemd |
| GPU | amd-gpu, intel-gpu, nvidia-desktop (auto-layered) |
Per-game files typically start with INCLUDE=presets/competitive.env, then override individual keys:
# Layering
INCLUDE=presets/competitive.env
# Wrappers and game args
DLSS_SWAPPER=1 # 1=dlss-swapper (NGX+presets), dll=dlss-swapper-dll (presets only)
PROTON_DLSS_UPGRADE=0 # 1=Proton-CachyOS/GE DLSS DLL upgrade (not Valve Proton)
PROTON_FSR4_UPGRADE=0 # 1=FSR4 upgrade (RDNA3 auto → PROTON_FSR4_RDNA3_UPGRADE)
PROTON_XESS_UPGRADE=0 # 1=XeSS upgrade (Intel / forks)
SHADER_CACHE_BOOST=1 # raise Mesa/NVIDIA shader cache size limits
LD_BIND_NOW=0 # 1=eager dynamic linking (Arch Gaming)
DISABLE_VBLANK=0 # 1=Mesa vblank off / immediate present
VKBASALT=0 # 1=ENABLE_VKBASALT (vkBasalt layer)
VKBASALT_CONFIG_FILE= # optional path to vkBasalt.conf
LSFG_VK=0 # lsfg-vk (owned Lossless Scaling required)
OBS_VKCAPTURE=0 # obs-gamecapture / obs-vkcapture after Gamescope
SPECIAL_K=0 # Special K under Proton (WINEDLLOVERRIDES + optional inject)
RESHADE=0 # Wine ReShade local inject
GAMESCOPE_NESTED_FIX=1 # strip LD_PRELOAD for nested desktop Gamescope
LATENCYFLEX=0 # 1=LFX=1 (LatencyFleX layer)
DISABLE_STEAM_DECK=0 # 1=SteamDeck=0 (Bazzite sd0)
FRAME_RATE= # e.g. 60 → DXVK_FRAME_RATE + VKD3D_FRAME_RATE
LAUNCH_WRAPPERS="" # custom PATH wrappers after DLSS; do not list dlss-swapper when DLSS_SWAPPER is set
LAUNCH_WRAPPERS_BEFORE=""
GAME_EXTRA_ARGS="-skipintro -nolog"
UNSET_VARS="DXVK_ASYNC VKD3D_CONFIG"
# Hooks (local only — hub publish rejects non-empty values; hub apply strips them)
PRE_LAUNCH_CMD=""
POST_LAUNCH_CMD=""
# Flags
FORCE_NATIVE=1 FORCE_PROTON=1
BENCHMARK=1 DEBUG=1
# Features (0/1 unless noted)
GAMEMODE MANGOHUD MANGOHUD_CONFIG MANGOHUD_LOG
GAMESCOPE GAMESCOPE_W GAMESCOPE_H GAMESCOPE_R
GAMESCOPE_ADAPTIVE_SYNC GAMESCOPE_FSR GAMESCOPE_FSR_SHARPNESS GAMESCOPE_HDR
VRAM_HOGS LAUNCH_WATCHDOG NETWORK_TUNE PIPEWIRE_LOW_LATENCY
GPU_POWER_CHECK NVIDIA_POWER_MODE GAME_PERFORMANCE DLSS_SWAPPER
DISABLE_CPU_AFFINITY CPU_AFFINITY_RANGE CONCURRENT_LAUNCH_GUARD
DISABLE_NIC_EEE DISABLE_WIFI_POWER_SAVE DISK_TUNE
MALLOC_ALLOCATOR ENABLE_HDR OVERRIDE_PROTON
PROTON_DLSS_UPGRADE PROTON_FSR4_UPGRADE PROTON_XESS_UPGRADE
PROTON_NVIDIA_LIBS PROTON_NVIDIA_LIBS_NO_32BIT SHADER_CACHE_BOOST
LD_BIND_NOW VKBASALT LATENCYFLEX DISABLE_VBLANK
DISABLE_STEAM_DECK FRAME_RATE
# Preflight thresholds
SHADER_CACHE_CHECK SHADER_CACHE_MAX_GB SHADER_CACHE_TRIM SHADER_CACHE_BOOST_GB
COMPATDATA_CHECK COMPATDATA_MAX_GB COMPATDATA_TRIM
VRAM_PREFLIGHT_MIN_MB DISK_PREFLIGHT_MIN_GB GPU_VRAM_PROCESS_MIN_MB
VM_MAX_MAP_COUNT_MIN VM_MAX_MAP_COUNT_FIX
# Proton / GPU (passed through when set)
PROTON_* DXVK_* VKD3D_* __GL_* __VK_* SDL_* MESA_* RADV_* AMD_* INTEL_*Inject, capture, Conty, and Wine keys: docs/cli.md · licenses / purchase gates: docs/third-party.md · nest Gamescope: docs/third-party.md § Nested Gamescope.
See also CachyOS: Forcing the Latest DLSS Preset.
| Approach | When to use |
|---|---|
DLSS_SWAPPER=1 |
CachyOS dlss-swapper: NGX updater + latest SR/RR/FG presets at launch |
DLSS_SWAPPER=dll |
Manual DLL replace + dlss-swapper-dll (presets only, no NGX) |
PROTON_DLSS_UPGRADE=1 |
Proton-CachyOS / GE download latest DLSS into the prefix (needs those forks) |
PROTON_FSR4_UPGRADE=1 |
Same forks for FSR4; RDNA3 GPUs auto-use PROTON_FSR4_RDNA3_UPGRADE |
PROTON_XESS_UPGRADE=1 |
Same forks for XeSS |
| dlss-updater | GUI app to replace game-folder DLLs offline — no CLI; LaunchLayer detects it and tips only |
Prefer one DLSS path per game (DLSS_SWAPPER or PROTON_DLSS_UPGRADE) to avoid double upgrades. Do not also list dlss-swapper in LAUNCH_WRAPPERS when DLSS_SWAPPER is set.
| Key | Effect |
|---|---|
LD_BIND_NOW=1 |
Eager symbol bind (first-call latency) |
DISABLE_VBLANK=1 |
Mesa vblank_mode=0 / immediate present; NVIDIA __GL_SYNC_TO_VBLANK=0 |
VKBASALT=1 |
ENABLE_VKBASALT=1 (vkBasalt Vulkan layer) |
LATENCYFLEX=1 |
LFX=1 (LatencyFleX); pair with DISABLE_VBLANK=1 when possible |
| Key | Effect |
|---|---|
DISABLE_STEAM_DECK=1 |
SteamDeck=0 (Bazzite sd0) — full graphics menus when Deck mode locks settings |
FRAME_RATE=N |
DXVK_FRAME_RATE + VKD3D_FRAME_RATE (restart to change; best latency of the FPS-cap methods) |
See Bazzite launch options. Prefer LaunchLayer keys over pasting sd0 / dlss-swapper into Steam when using "…/launchlayer" %command%.
Cross-compositor probing covers KDE/Plasma, GNOME/COSMIC, Hyprland, Sway, wlroots compositors, and X11 stacks (via xrandr). Compositor IPC probes are gated so inactive tools (e.g. hyprctl on KDE) do not false-match. Wayland sessions auto-set GAMESCOPE_EXPOSE_WAYLAND=0.
Inspect detection: ./launchlayer --detect-environment (see docs/cli.md)
./launchlayer --tui # always opens the TUI (interactive terminal required)
launchlayer # same when symlinked; also opens TUI with no args when fzf + TTYRequires fzf for fuzzy menus with live previews; without it, numbered prompts are used instead.
Game picker — fuzzy search, live config preview, Ctrl-E/Ctrl-D shortcuts
Quick toggles — inherited vs per-game overrides (green/red)
Full menu tree, shortcuts, and preferences: docs/tui.md (includes screenshots)
Regenerate screenshots after UI changes: make tui-screenshots (requires VHS and fzf).
Share per-game configs and discover settings from similar machines (GPU, OS, display tier, profiles, Deck/Flatpak/WSL flags). Optional — local launches do not need the hub. Client: lib/hub/; backend: Convex app in hub/.
Setup — copy the template and set your deployment URL and publish token:
mkdir -p ~/.config/launchlayer
cp share/launchlayer/templates/hub.conf.example ~/.config/launchlayer/hub.conf
# hub_url=https://your-deployment.convex.site
# publish_token=<same value as Convex HUB_PUBLISH_TOKEN>
# Optional: machine_label, fingerprint_level (minimal | standard | detailed)Hub publish/delete is fail-closed: set HUB_PUBLISH_TOKEN on the Convex deployment and matching publish_token in hub.conf. For local open hubs only, set HUB_ALLOW_OPEN_PUBLISH=1 on the deployment (never in production). Published configs cannot include remote-exec keys (PRE_LAUNCH_CMD, wrappers, OVERRIDE_PROTON, VRAM-hog controls); hub apply strips those if present. INCLUDE= paths must stay under launch.d/.
Hub rate limiting also fails closed. Route the HTTP actions endpoint through an ingress that overwrites a client-identity header, then set HUB_TRUSTED_CLIENT_IP_HEADER to that header name and HUB_IDENTIFIER_HASH_KEY to at least 32 random characters. Direct clients must not be able to supply the trusted header.
Hub CLI commands: docs/cli.md § Community hub
Also useful without the hub: --suggest-config APPID|NAME [--apply] ranks ProtonDB reports for this machine and can write allowlisted knobs into games/<AppID>.env (docs/cli.md § Games and config). TUI: Games → Game → [Edit] Suggest from ProtonDB.
The TUI exposes hub flows under Community hub (main menu) and [Hub] Community configs (per-game actions), including viewing history and applying a historical version (also on Apply config by ID).
Deploy or develop the backend from hub/ (Node 22+, pnpm pinned via hub/package.json packageManager). The repo root package.json is a scripts-only shim (no lockfile) — always install inside hub/. Prefer Vite+ (vp) when available — it resolves the pinned pnpm. Otherwise enable Corepack and call pnpm directly. From the repo root you can also use bash scripts/hub-pm.sh … / make test-hub / make lint-hub.
cd hub
# With Vite+ (preferred):
vp install
vp run dev # development — runs convex dev
vp run lint # ESLint + tsc
vp run convex:deploy # production only
# Without Vite+ (Corepack + pnpm):
corepack enable
pnpm install
pnpm dev
pnpm run lint
pnpm run convex:deployPoint hub_url in hub.conf at your deployment’s HTTP actions URL (e.g. https://your-deployment.convex.site).
See docs/architecture.md for similarity weights, fingerprint levels, and HTTP routes. Do not commit hub/.env.local, hub/.convex/, or hub/node_modules/ — they are gitignored; make check runs check-hub-git to catch accidental staging.
Elasticsearch’s package sysctl can reset vm.max_map_count to 262144, which breaks some Proton games:
./launchlayer --sysctl install
# or manually:
sudo cp share/launchlayer/sysctl/elasticsearch.conf /etc/sysctl.d/
sudo sysctl --system
sysctl -n vm.max_map_count # expect 2147483642ⓘ Remove
/etc/sysctl.d/99-proton-vm.confif present—it is superseded byelasticsearch.conf.
Set VM_MAX_MAP_COUNT_FIX=1 in config to raise the value at launch when passwordless sudo is available.
Certain settings (NETWORK_TUNE=1, VM_MAX_MAP_COUNT_FIX=1, DISK_TUNE=1, and Wi‑Fi power-save disable) require root to query or modify hardware/kernel state. To allow LaunchLayer to apply these at game startup without a password prompt:
-
Create a sudoers override configuration file:
sudo visudo -f /etc/sudoers.d/launchlayer
-
Add a rule (replace
usernamewith your Linux user). Verify absolute paths withwhich ip ethtool sysctl iw iwconfig tee:username ALL=(ALL) NOPASSWD: /usr/sbin/ip, /usr/bin/ethtool, /usr/bin/sysctl, /usr/bin/iw, /usr/sbin/iwconfig, /usr/bin/teeip/ethtool/sysctl— bring NIC up, ring buffers, EEE, TCP low-latency,vm.max_map_countiw/iwconfig— disable Wi‑Fi power save whenDISABLE_WIFI_POWER_SAVE=1tee— write I/O scheduler under/sys/block/<dev>/queue/schedulerwhenDISK_TUNE=1(LaunchLayer validates the path before callingtee)
Prefer the narrowest paths that exist on your distro (
/usr/sbin/ipvs/bin/ip, etc.).
sudo ./scripts/setup-workstation-tuning.shInstalls irqbalance, enables btrfs autodefrag when applicable, and installs the X3D IRQ affinity helper + irq-affinity-x3d.service when the helper binary is found.
Maintenance — stale launch cleanup + cache report (launchlayer-maintenance.timer):
./launchlayer --install-systemd
# or: ./launchlayer --setup --systemdBackup — scheduled config export + prune (launchlayer-backup.timer; configure backup.conf first):
./launchlayer --backup-timer install
# or: ./launchlayer --setup --backup-timerBoth write user units under ~/.config/systemd/user/ with the resolved script path.
launchlayer # ▶ entry point (bash 4.2+)
launch.d/ # ≡ shipped layers: default.env, profiles/, presets/, *.txt lists
anticheat-appids.txt # known EAC/BattlEye AppIDs
native-appids.txt # known native Linux AppIDs
lib/ # ⚙ core modules (config, launch, hardware, tui, …)
hub/ # ◉ community hub client (fingerprint, HTTP)
hub/ # ◉ optional Convex backend (vp / pnpm)
share/launchlayer/ # ▣ templates, sysctl, systemd units, completions
examples/games/ # ◆ tracked example per-game configs
scripts/
tui-screenshots/ # VHS frame scripts + fixtures (make tui-screenshots)
check-staged-hub-secrets.sh
setup-workstation-tuning.sh
test/ # ✓ bats integration + unit tests
docs/ # [docs/README.md](docs/README.md) — topic → page map
README.md # docs index + drift checklist
architecture.md # module load order, paths, hub API
cli.md # full CLI command reference
tui.md # interactive TUI menus, shortcuts, screenshots
third-party.md # licenses, purchase gates, nest Gamescope
release_runbook.md # version bump + GitHub release
assets/
launchlayer.svg
tui-main-menu.png
tui-game-picker.png
tui-quick-toggles.png
Under $XDG_STATE_HOME/launchlayer (default ~/.local/state/launchlayer/):
| File | Purpose |
|---|---|
launch.log |
Structured launch history (rotated; default max 5000 lines) |
paused-vram-units |
systemd units stopped for VRAM |
paused-vram-pids |
PIDs tracked for VRAM hog pause |
vram-hog-refcount |
Nested launch refcount |
active-launch.pid |
Current game PID |
launch-watchdog.pid |
Cleanup subprocess when LAUNCH_WATCHDOG=1 |
x3d-cpus / x3d-cpus.meta |
Cached V-Cache CPU mask |
shader-cache-check-<AppID>.stamp |
Rate-limit shader cache preflight |
compatdata-check-<AppID>.stamp |
Rate-limit compatdata preflight |
The script degrades gracefully when tools are missing. Run --doctor or --detect-environment for distro-aware install hints.
| Tool | Used for |
|---|---|
fzf |
Interactive TUI |
gamemoderun |
GameMode CPU governor |
game-performance |
CPU perf profile wrapper |
gamescope |
Compositor upscaling, VRR |
mangohud |
Overlay |
vkbasalt |
Vulkan post-process layer via VKBASALT=1 |
lsfg-vk |
Frame gen layer via LSFG_VK=1 (needs owned Lossless Scaling — third-party) |
obs-vkcapture / obs-gamecapture |
Capture wrap via OBS_VKCAPTURE=1 |
latencyflex |
LatencyFleX layer via LATENCYFLEX=1 |
conty |
32-bit container wrap via CONTY=1 |
protontricks / winetricks |
Prefix verbs / winecfg / registry |
replay-sorcery |
Chain-wrapped replay via REPLAY_CAPTURE=1 |
gpu-screen-recorder |
Preferred external recorder (REPLAY_TOOL) — not chain-wrapped |
wine-discord-ipc-bridge |
Discord IPC via DISCORD_IPC=1 |
dlss-swapper |
NGX + latest DLSS presets via DLSS_SWAPPER=1 (CachyOS wiki; package cachyos-settings) |
dlss-updater |
Optional GUI for offline DLL replace (detected/tipped only — no launch CLI) |
taskset |
Pin to X3D V-Cache CCD |
nvidia-smi, nvidia-settings |
VRAM/power checks |
ethtool |
NETWORK_TUNE (ring buffers, EEE) |
iw / iwconfig |
Wi‑Fi power-save disable under NETWORK_TUNE |
tee |
DISK_TUNE scheduler writes (with passwordless sudo) |
pw-metadata |
PIPEWIRE_LOW_LATENCY |
curl |
Community hub HTTP client |
jq or python3 |
Hub apply / ProtonDB suggest |
| systemd user session | VRAM_HOGS unit pause/resume |
launch.d/anticheat-appids.txt— Known EAC/BattlEye AppIDs; guardrails warn on risky settings (DEBUG=1,DXVK_ASYNC)launch.d/native-appids.txt— Known native Linux builds; skips Proton env unlessFORCE_PROTON=1- Heuristics in
lib/steam/detect.shalso inspect install manifests;--scan-anticheatand--scan-detectionshelp keep lists accurate
make test # bats integration + unit (parallel when GNU parallel is installed)
make test-unit # bats test/unit only
make test-integration # bats test/integration only
make check # shellcheck + check-hub-git + bats (shell gate)
make check-hub-git # fail if hub secrets are staged
make check-dependency-pins # enforce exact deps, lock integrity, Action SHAs
make test-hub # hub unit + convex tests (via scripts/hub-pm.sh)
make lint-hub # hub ESLint + tsc
make check-hub # lint-hub + test-hub
make test-all # shell bats + test-hub
make check-all # check + check-hub (full local gate matching CI)
make bump-version VERSION=X.Y.Z
make check-version # LAUNCHLAYER_VERSION consistency gateReleases: follow docs/release_runbook.md. Notes live in CHANGELOG.md.
Or directly:
bats --jobs "$(nproc)" --no-parallelize-within-files test/unit test/integration
shellcheck -x -P lib -a --severity=warning launchlayer test/helpers.bash scripts/*.sh
bash scripts/hub-pm.sh install # once, from repo root (or cd hub && pnpm install)
bash scripts/hub-pm.sh lint
bash scripts/hub-pm.sh testHub dependency audits run weekly via .github/workflows/hub-audit.yml (or workflow_dispatch), not on every PR.
Do I need a different launch string per game?
No. Use the same "/path/to/launchlayer" %command% on every title. Per-game tuning lives in GAMES_DIR/<AppID>.env.
Can I keep gamemoderun or mangohud in Steam’s launch options?
Remove external wrappers from Steam and enable GAMEMODE=1, MANGOHUD=1, GAMESCOPE=1, etc. in config instead — otherwise you get double-wrapped launches.
Steam Overlay / Steam Input broken under nested Gamescope?
LaunchLayer clears LD_PRELOAD around nested desktop Gamescope by default (GAMESCOPE_NESTED_FIX). See docs/third-party.md § Nested Gamescope and docs/cli.md § Gamescope nest.
Special K, ReShade, lsfg-vk, Conty — where are licenses and keys? docs/third-party.md (licenses / purchase gates) · docs/cli.md (keys) · docs/tui.md § Advanced config (Inject & Wine).
Does this work with Flatpak Steam?
Yes. Installs under $HOME usually work as-is; paths outside $HOME need a Flatpak filesystem override. Run ./launchlayer --detect-environment and see Flatpak Steam.
Do I need the community hub? No. Local launches, the TUI, backup/restore, and doctor all work without it. The hub is optional for sharing configs with similar machines. Hub strip rules: docs/architecture.md · docs/cli.md § Community hub.
Where do per-game configs live?
In ~/.local/share/launchlayer/games/<AppID>.env by default — not in the git repo. See Configuration.
Commercial use? This project is CC BY-NC-SA 4.0. Commercial use requires separate permission from bolens. Third-party tool licenses: docs/third-party.md.
Issues and pull requests are welcome at github.com/bolens/launch-layer.
make check # shellcheck + hub secret guard + bats
make check-all # check + hub lint/test (needs hub/node_modules)
make test # bats only- Docs map (update when adding features): docs/README.md
- Deep module reference: docs/architecture.md
- CLI and TUI reference: docs/cli.md · docs/tui.md (with TUI screenshots)
- Third-party / inject policy: docs/third-party.md
- Releases: docs/release_runbook.md · CHANGELOG.md
- Example per-game config: examples/games/2357570.env
- Do not commit
hub/.env.local,hub/.convex/, or publish tokens —make check-hub-gitcatches accidental staging
CC BY-NC-SA 4.0 — non-commercial use with attribution; derivatives must use the same license.
Third-party tools keep their own licenses. See docs/third-party.md for upstream links, SPDX notes, purchase gates (e.g. Lossless Scaling for lsfg-vk), and redistrib rules. LaunchLayer never vendors proprietary/GPL binaries into this repository.
You may use, modify, and share this project for personal or non-commercial purposes if you credit bolens, link to github.com/bolens/launch-layer, and release any derivatives under the same terms. Commercial use requires separate permission.
