Skip to content

Repository files navigation

LaunchLayer

LaunchLayer

Layered launch orchestration for Steam games on Linux

One Steam launch string. Per-game configs. GameMode, Gamescope, MangoHUD, and more — assembled automatically.

License: CC BY-NC-SA 4.0 CI Shell Platform

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.

See it work

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%

LaunchLayer TUI game picker
Interactive TUI — browse games, preview configs, flip toggles

Quick paths

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

Contents

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

Quick start

  1. 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
  1. Run onboarding — completions, symlink, launch string, and machine defaults:
./launchlayer --setup --completions --symlink --print-launch-option --write-local-config

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

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

  1. Scaffold a per-game config:
./launchlayer --init-appid 2357570 competitive    # by AppID
./launchlayer --init-appid "Overwatch" competitive  # by name
./launchlayer --tui                                 # or browse interactively
  1. Sanity check:
./launchlayer --doctor

If Proton titles misbehave, fix vm.max_map_count once — see System tuning.


Integrating with Steam launch options

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.

1. Get your launch string

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 --doctor

Example 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 .exe or 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, …).

2. Paste into Steam

Per game (typical workflow—repeat for each title, or copy/paste the same string):

  1. Open SteamLibrary
  2. Right-click the game → Properties
  3. In General, find Launch Options
  4. Paste your launch string, e.g. "$HOME/launchlayer/launchlayer" %command%
  5. Close Properties and launch the game normally
Steam Deck / Big Picture

On Deck: Library → select game → gear iconPropertiesGeneralLaunch Options. The same %command% string applies.

3. Flatpak Steam

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/launchlayer

Check access and get a tailored hint:

./launchlayer --detect-environment
./launchlayer --doctor

The flatpak-steam profile layers automatically when Flatpak Steam is detected.

4. Per-game settings (no launch-option edits)

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 chain

Steam sets SteamAppId / STEAM_APPID when launching; LaunchLayer uses that to pick GAMES_DIR/<AppID>.env (or auto preset when no file exists).

5. Verify before or after first launch

Preview the resolved chain (terminal—no game start):

SteamAppId=2357570 ./launchlayer --dry-run %command%
# or
./launchlayer --show-config 2357570

After a real launch, inspect history:

./launchlayer --launch-stats 2357570
tail ~/.local/state/launchlayer/launch.log

Health check:

./launchlayer --doctor

Troubleshooting

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

What it does

Area Behavior
Layered config Plain KEY=VALUE files stack: profiles → default.envlocal.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.


How a launch works

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
Loading
Step-by-step (text)
  1. Recover stale state — Resume VRAM-heavy services left paused after a crash (lib/vram.sh)
  2. Resolve AppID — From SteamAppId, STEAM_APPID, or launch argv (lib/config.sh)
  3. Load layered config — Profiles → default.envlocal.env → preset or per-game file; then apply_defaults and apply_detected_defaults
  4. Detect game flags — Native vs Proton, EAC/BattlEye, engine hints (lib/steam/detect.sh)
  5. Auto hardware defaults — X3D CPU mask, display resolution/refresh for Gamescope (lib/hardware/)
  6. Parse extra args — Split GAME_EXTRA_ARGS into argv appended after %command%
  7. Preflight checks — Skipped when BENCHMARK=1 (lib/preflight.sh): sysctl, shader/compat caches, VRAM, GPU power/processes, disk, concurrent launch guard
  8. Tool warnings & anticheat guardrails — Missing optional tools; warn on risky settings for EAC/BattlEye titles
  9. VRAM hogs — Optionally pause configured systemd user units with refcount + exit trap (before runtime tuning)
  10. Runtime tuning — Network (ethtool), PipeWire latency, CPU perf profile, NVIDIA power mode, Proton/DXVK/VKD3D env
  11. Build launch chain — Assemble wrappers per build_launch_chain in lib/runtime/chain.sh
  12. ExecPRE_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.


Configuration

Settings are plain KEY=VALUE files. Later layers override earlier ones. Full key tables: docs/cli.md. TUI editors: docs/tui.md.

Layer order

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

Where data lives

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

Auto preset selection

When no per-game .env exists:

Path
N Native Linux buildpresets/native.env
P Everything else (Proton)presets/standard.env

Presets

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

Machine profiles

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)

Common config keys

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.

Upscaling paths (DLSS / FSR / XeSS)

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.

Latency knobs (Arch Gaming)

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

Bazzite / Deck identity & FPS caps

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

Display detection

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)


Interactive TUI

./launchlayer --tui          # always opens the TUI (interactive terminal required)
launchlayer                  # same when symlinked; also opens TUI with no args when fzf + TTY

Requires fzf for fuzzy menus with live previews; without it, numbered prompts are used instead.

LaunchLayer main menu
Main menu with status banner

LaunchLayer game picker with live preview
Game picker — fuzzy search, live config preview, Ctrl-E/Ctrl-D shortcuts

LaunchLayer per-game quick toggles
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).


Community hub

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:deploy

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


System tuning

vm.max_map_count (Proton)

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.conf if present—it is superseded by elasticsearch.conf.

Set VM_MAX_MAP_COUNT_FIX=1 in config to raise the value at launch when passwordless sudo is available.

Passwordless sudo for runtime tuning (optional)

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:

  1. Create a sudoers override configuration file:

    sudo visudo -f /etc/sudoers.d/launchlayer
  2. Add a rule (replace username with your Linux user). Verify absolute paths with which 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/tee
    
    • ip / ethtool / sysctl — bring NIC up, ring buffers, EEE, TCP low-latency, vm.max_map_count
    • iw / iwconfig — disable Wi‑Fi power save when DISABLE_WIFI_POWER_SAVE=1
    • tee — write I/O scheduler under /sys/block/<dev>/queue/scheduler when DISK_TUNE=1 (LaunchLayer validates the path before calling tee)

    Prefer the narrowest paths that exist on your distro (/usr/sbin/ip vs /bin/ip, etc.).

Workstation setup (optional)

sudo ./scripts/setup-workstation-tuning.sh

Installs irqbalance, enables btrfs autodefrag when applicable, and installs the X3D IRQ affinity helper + irq-affinity-x3d.service when the helper binary is found.

systemd timers

Maintenance — stale launch cleanup + cache report (launchlayer-maintenance.timer):

./launchlayer --install-systemd
# or: ./launchlayer --setup --systemd

Backup — scheduled config export + prune (launchlayer-backup.timer; configure backup.conf first):

./launchlayer --backup-timer install
# or: ./launchlayer --setup --backup-timer

Both write user units under ~/.config/systemd/user/ with the resolved script path.


Project layout

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

Runtime state

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

Optional dependencies

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

Anticheat and native detection

  • 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 unless FORCE_PROTON=1
  • Heuristics in lib/steam/detect.sh also inspect install manifests; --scan-anticheat and --scan-detections help keep lists accurate

Testing

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 gate

Releases: 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 test

Hub dependency audits run weekly via .github/workflows/hub-audit.yml (or workflow_dispatch), not on every PR.


FAQ

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.


Contributing

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

Star history

Star History Chart

License

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.

About

Layered launch orchestration for Steam games on Linux with preflight checks, per-game environments, and composable wrapper chains.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages