Felix Förtsch 1ece944a45 initial implementation: multi-user Steam shared library via bwrap overlay
Share one Steam game library across multiple Linux users with fully
isolated Proton prefixes. Uses bubblewrap to create a per-user kernel
overlay on /opt/steam/steamapps/compatdata/ so game files stay shared
while Proton prefixes are isolated per user, with no compatibility
tool selection or per-game configuration required.

Includes:
- steam-shared launcher that sets up the per-user overlay and execs
  Steam inside a bwrap mount namespace
- activate/uninstall scripts plus an add-user helper for steamshare
  group membership
- permission watcher (steam-fix-perms.path/.service) to keep ACLs
  correct under pressure-vessel's restrictive mode bits
- .desktop override that routes the system Steam launcher through
  steam-shared
- Nix flake exposing activate, uninstall, and add-user packages
- design doc and implementation plan covering the approach
2026-04-15 09:53:09 +02:00

Steam Shared Library

Share one Steam game library across multiple Linux users with fully isolated Proton prefixes.

The Problem

Steam stores Proton/Wine prefixes (compatdata) inside the game library folder. When multiple users share a library, they all write to the same prefix directory, causing:

  • wineserver: pfx is not owned by you
  • PermissionError: os.chmod
  • pressure-vessel: Permission denied

How It Works

Uses bubblewrap to create a per-user kernel overlay on /opt/steam/steamapps/compatdata/:

  • Game files (common/, shadercache/) → shared directly, readable and writable by all group members
  • Proton prefixes (compatdata/) → overlaid per-user via bwrap's mount namespace

Each user gets their own overlay. Writes go to ~/.local/share/steam-shared/upper/. Reads fall through to the shared directory. Multiple users can be logged in simultaneously (GDM user switching) — each session has its own isolated mount namespace.

No compatibility tool selection. No per-game configuration. Works with any Proton variant.

Prerequisites

  • Linux with a modern kernel (overlay support)
  • bubblewrap (pacman -S bubblewrap / apt install bubblewrap)
  • acl package for setfacl (usually pre-installed)
  • Steam (native package or Flatpak)
  • Nix (for installation via flake)

Installation

# install (requires sudo)
nix run github:felixfoertsch/steam-shared-library
# or from a local checkout:
sudo ./scripts/activate.sh

This creates:

  • steamshare group
  • /opt/steam/ with correct permissions and ACLs
  • /usr/local/bin/steam-shared launcher
  • .desktop override so Steam launches through the shared launcher

Setup

# add users to the shared library group
sudo usermod -aG steamshare <username>
# user must log out and back in for group membership

# each user: open Steam → Settings → Storage → add /opt/steam
# install games to /opt/steam — they're shared for all users
# use any Proton version — prefix isolation is automatic

Steam Detection

The launcher prefers native Steam over Flatpak:

  1. If /usr/bin/steam exists → native Steam
  2. Else if Flatpak Steam is installed → Flatpak with filesystem access to /opt/steam
  3. Else → error

If both are installed, native wins. Having both native and Flatpak Steam is discouraged — it leads to two separate configs, library confusion, and wasted disk space. Pick one.

Uninstall

nix run github:felixfoertsch/steam-shared-library#uninstall
# or from a local checkout:
sudo ./scripts/uninstall.sh

This removes the launcher, .desktop override, and steamshare group. Game data at /opt/steam/ and per-user prefixes at ~/.local/share/steam-shared/ are preserved (remove manually if desired).

How It Works (Technical)

The Steam launcher (steam-shared) does:

  1. Verify prerequisites (shared dir exists, user in steamshare group, bwrap installed)
  2. Detect Steam installation (native or Flatpak)
  3. Create per-user overlay directories (~/.local/share/steam-shared/{upper,work})
  4. Launch Steam inside a bwrap mount namespace with an overlay on compatdata/
bwrap --dev-bind / / \
    --overlay-src /opt/steam/steamapps/compatdata \
    --overlay ~/.local/share/steam-shared/upper \
             ~/.local/share/steam-shared/work \
             /opt/steam/steamapps/compatdata \
    -- steam

Inside the namespace, any write to /opt/steam/steamapps/compatdata/ goes to the per-user upper layer. The shared lower layer is read-only from the overlay's perspective. Game files outside compatdata/ are not overlaid — they're shared normally.

Caveats

Do not nest steam-shared inside another bwrap wrapper

bwrap without the setuid bit (the default on modern distros) creates an unprivileged user namespace. User namespaces cannot preserve supplementary groups — every group except the primary one becomes nobody inside the namespace.

That has two consequences if you wrap steam-shared inside an outer bwrap (for example, a Steam lancache wrapper that bind-mounts a custom resolv.conf):

  1. The steamshare group check in steam-shared fails — id -nG no longer lists steamshare inside the outer namespace.
  2. Even if you bypass the check, Steam itself cannot write to /opt/steam/steamapps/common/. The group bit that grants access is gone inside the namespace, so new game installs fail with EACCES.

If you need a lancache or similar DNS redirect, put it at the system level (a systemd-resolved drop-in, or a local dnsmasq), not inside a second bwrap around steam-shared.

Prior Art

This project originally used a Proton compatibility tool wrapper to redirect STEAM_COMPAT_DATA_PATH. That approach was brittle:

  • Required selecting the wrapper as the compatibility tool for every game
  • Per-game Proton overrides bypassed the wrapper entirely
  • Steam sometimes wrote to the original path before the wrapper ran

The bwrap overlay approach solves all of these by operating at the filesystem level, below Steam and Proton.

Description
Share one Steam game library across multiple Linux users with isolated Proton prefixes
Readme MIT 63 KiB
2026.04.15 Latest
2026-04-15 09:57:16 +02:00
Languages
Shell 90.7%
Nix 9.3%