Files
steam-shared-library/README.md
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

5.0 KiB

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.