Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Electron apps can't run in the Steam Linux Runtime (libcups) #579

Closed
dfabulich opened this issue Mar 27, 2023 · 41 comments
Closed

Electron apps can't run in the Steam Linux Runtime (libcups) #579

dfabulich opened this issue Mar 27, 2023 · 41 comments

Comments

@dfabulich
Copy link

dfabulich commented Mar 27, 2023

Your system information

  • Steam Runtime Version: soldier 0.20230228.42394
  • Distribution (e.g. Ubuntu 18.04): Steam Deck SteamOS 3.4.6
  • Link to your full system information (Help -> System Information) in a Gist: https://gist.github.com/dfabulich/5ee302908d2bd09ad38fca464ef8a6a2
  • Have you checked for system updates?: Yes
  • What compatibility tool are you using?: Steam Linux Runtime
  • If you are using Steam Linux Runtime for native Linux games: What versions are listed in SteamLinuxRuntime/VERSIONS.txt?
#Name	Version		Runtime	Runtime_Version	Comment
depot	0.20230216.0			# Overall version number
LD_LIBRARY_PATH	-	scout	-	# see ~/.steam/root/ubuntu12_32/steam-runtime/version.txt
scripts	0.20230216.0			# from steam-runtime-tools
  • If you are using Steam Linux Runtime, or Proton 5.13 or newer: What versions are listed in SteamLinuxRuntime_soldier/VERSIONS.txt?
#Name	Version		Runtime	Runtime_Version	Comment
depot	0.20230228.42394			# Overall version number
pressure-vessel	0.20230227.0	scout		# pressure-vessel-bin.tar.gz
scripts	0.20230227.0			# from steam-runtime-tools
soldier	0.20230228.42394	soldier	0.20230228.42394	# soldier_platform_0.20230228.42394/

Please describe your issue in as much detail as possible:

Apps that are built with Electron https://www.electronjs.org/ are unable to launch in the Steam Linux Runtime.

I first encountered this issue while attempting to use the Steam Linux Runtime on my Steam Deck with a game that has a working native Linux build, but it crashes on startup when I use the option to "Force the use of a specific Steam Play compatibility tool" and I select "Steam Linux Runtime."

But furthermore, I can provide instructions to build a basic "Hello, World" Electron app, and show that it crashes in the same way as my game.

As you'll see, the problem is that Steam Linux Runtime is missing libcups, part of the CUPS printing server. https://openprinting.github.io/cups/

Ideally, Steam games "shouldn't" require CUPS, because they shouldn't need to print anything, but Electron apps embed a copy of the Chromium browser, which does require CUPS support. Thus, Electron apps are unable to run in Steam Linux Runtime.

EDIT: We've found a workaround. #579 (comment)

Steps for reproducing this issue:

I'll provide two methods to reproduce the issue. The first method to reproduce the issue is to launch my game. The other method is to launch a "Hello, World" Electron app, built from scratch.

Method 1: Launch my game

  1. On Steam Deck, download my game, SLAMMED https://store.steampowered.com/app/343370/SLAMMED/ (you can test with the Demo if you don't want to buy the full game)
  2. In the Steam Library, click the Gear --> Properties --> Compatibility
  3. Check the box "Force the use of a specific Steam Play compatibility tool" and select "Steam Linux Runtime."
  4. Launch the game. It will crash on startup.

Following the instructions at https://github.com/ValveSoftware/steam-runtime/blob/master/doc/reporting-steamlinuxruntime-bugs.md I switched to Desktop mode, launched a Konsole, and ran Steam with STEAM_LINUX_RUNTIME_LOG=1 steam. Then, I launched SLAMMED again, and found the debug log at this path: /home/deck/.local/share/Steam/steamapps/common/SteamLinuxRuntime_solder/var/slr-app343370-t20230327T114647.log. The log is here as a gist: https://gist.github.com/dfabulich/66f22fdf45f1c6e24124cdeece8f1574

The crucial line is on line 195:

/home/deck/.local/share/Steam/steamapps/common/SLAMMED/SLAMMED: symbol lookup error: /home/deck/.local/share/Steam/steamapps/common/SLAMMED/SLAMMED: undefined symbol: cupsEnumDests

Method 2: Launch a "Hello, World" empty Electron app on Steam Deck

First, you'll need to set a password for the deck sudo user. Press the Steam button, Power --> Desktop Mode. Then launch System Settings --> Users --> Steam Deck User --> Change Password and set a password.

To use Electron, you'll first need to install nodejs and npm. Instructions are as follows:

sudo steamos-readonly disable
sudo pacman-key --init
sudo pacman -Sy --noconfirm archlinux-keyring
sudo pacman -Su --noconfirm
sudo pacman -S --noconfirm libuv c-ares nodejs
node --version
sudo pacman -Rs --noconfirm npm
sudo pacman -S --noconfirm npm
npm --version

Once you have those, you'll be able to follow steps to set up a basic Electron app. These steps are documented here: https://www.electronforge.io/

cd ~
npm init electron-app@latest my-app
cd my-app
npm start

To build your app for distribution, you'll need to edit the forge.config.js file in your new my-app directory. You'll need to remove the sections about building .deb and .rpm files, and replace the word darwin with linux. I've provided an example of this in this Git repository: https://github.com/dfabulich/hello-world-electron-app using this forge.config.js file: https://github.com/dfabulich/hello-world-electron-app/blob/main/forge.config.js

(You could, if you wish, check out my Git repository and use that, instead, but I wanted to provide trivial steps to reproduce it, so you can trust that I didn't do anything weird.)

With that done, you can npm run make to build the Linux app. You can run ./out/my-app/linux-x64/my-app to launch the app.

So far, so good. Now, try to run it in Steam Linux Runtime soldier. We'll start by launching xterm in the solder SLR, following instructions here: https://gitlab.steamos.cloud/steamrt/soldier/platform/#running-the-steam-container-runtime-without-steam

cd ~
./.local/share/Steam/steamapps/common/SteamLinuxRuntime_soldier/run-in-soldier -- xterm

In the xterm:

cd my-app
./out/my-app/linux-x64/my-app

The app will crash immediately with this error:

./out/my-app/linux-x64/my-app: error while loading shared libraries: libcups.so.2: cannot open shared object file: No such file or directory

Since libcups is missing in the container, the app fails to start.

@kisak-valve
Copy link
Member

Related: ValveSoftware/Proton#2818 (comment)

@insraq
Copy link

insraq commented Mar 27, 2023

I have encountered this exact same issue while trying to add support for Steam Deck (which requires Steam Linux Runtime): https://store.steampowered.com/app/1574000/Industry_Idle/

/home/gamer/.local/share/Steam/steamapps/common/IndustryIdle/IndustryIdle: symbol lookup error: /home/gamer/.local/share/Steam/steamapps/common/IndustryIdle/IndustryIdle: undefined symbol: cupsEnumDests

From reading this issue: ValveSoftware/steam-for-linux#8031 I thought libcups were available in Steam Linux Runtime but apparently it was not (at least not in all versions?)

A lot of game uses Electron/NW.js as runtime on Steam. Since Electron/NW.js provides prebuilt native binaries (and that's how the majority of developers package the game, after all, not everyone would like to compile Chromium for every release 😄), I think it would be nice to support that out of the box. The game works fine on most distros without Steam Linux Runtime but I was informed that supporting Steam Linux Runtime is a must for Steam Deck support.

@dfabulich
Copy link
Author

Related: nwjs/nw.js#8032

See especially this comment including a reply from Steam Support:

The Steam Linux Runtime is used by default for native titles running on Steam Deck so support is a requirement.

I escalated your issue with Electron to a dev team member and they provided some guidance:
You will need to either rebuild Electron binaries against the Steam Linux Runtime, or get the vendor to do it. Since Electron > has a lot of JS and web stuff, you may need to use the newer 'sniper' Steam Linux Runtime.

It isn't possible to build Electron binaries in the container, precisely because the container is missing required libraries like libcups. (I strongly suspect that libcups is just one of the missing libraries; it's just the first one we know about.)

I'd be perfectly happy to have our users use Proton to run my app on Steam Deck, (it works fine, if users manually opt-in to Proton support in compatibility settings), but AFAIK there's no way for me to say in Steamworks settings "Please use Proton for my app."

So, we can't build Electron in the Steam Linux Runtime, and we can't opt-in to use Proton on Steam Deck, so we're completely stuck until Valve implements a fix!

@TTimo
Copy link
Collaborator

TTimo commented Mar 28, 2023

Trying to untangle and clarify a few things here:

  • If your app gets reviewed for the Deck, the reviewers will flag Proton as the preferred version since native doesn't start. You can also request this when asking for a review I believe (please review/use the Proton version).

  • If you do not ship a Linux depot at all, then anyone installing your game on Deck will use the Proton version.

Why should you not ship a Linux depot? It seems you are shipping a Linux depot because it works on some Linux desktop distributions (presumably, Ubuntu). It's important to understand that it only works there by accident as the game escapes the Steam Linux Runtime ABI and uses libraries that happen to be present and ABI compatible on the host (libcups).

That is not a good way to do things (it's fragile), and the Deck is more strict in that regard.

In order to fix this reliably for the future, Electron (and it's embedded CEF) needs to be built in the sniper runtime environment. Additional libraries (such as libcups) may either need to be disabled, or also compiled and bundled with the resulting package.

Unfortunately this is a lot of source code with pretty convoluted build systems (we know, we use CEF in Steam), so I don't know who might be able to work on this, or when.

@dfabulich
Copy link
Author

@TTimo I recognize how challenging it might be to fix this issue. I'm available to try to work on this, but it's not clear that I have the power to help.

I can imagine two approaches to this bug.

  1. Add libraries to SLR: I'd identify a handful of necessary libraries, including, at least, libcups, and I'd file a PR(??) that would add libcups to sniper. (Is it even possible for me to file a PR like that? Would the PR be accepted?)
  2. Fork Electron: We'd start a new Github project, "Electron for Steam Linux Runtime," in which I (and hopefully some volunteers) would maintain a fork of Electron that works in SLR. (And not just Electron; it's NW.js, too!) We'd tweak some build flags, maybe bundle some libraries, whatever. Developers who want to ship a game for Electron/NW that works in SLR would have to use our special version of Electron, instead.

Forking Electron seems like a huge hassle, way way more work than just adding libcups to sniper. Other Steam developers would have to discover our project and use it. You couldn't just npm init electron-app. You'd have to use my special Electron-for-SLR build. And I'd have to keep it up to date, and running, in perpetuity.

If it's even possible for me to file a PR to add libcups (and whatever else) to SLR, I'd like to do it.

As for why…

Why should you not ship a Linux depot? It seems you are shipping a Linux depot because it works on some Linux desktop distributions (presumably, Ubuntu). It's important to understand that it only works there by accident as the game escapes the Steam Linux Runtime ABI and uses libraries that happen to be present and ABI compatible on the host (libcups).

As you may be aware, the Steam Deck doesn't necessarily run games in the Steam Linux Runtime. Steam Deck will use SLR if it's forced to, in compatibility settings, and Valve's "Steam Deck Compatibility Review" process will only mark a game as Verified if it works in Proton or SLR, but if the Compatibility Review team hasn't gotten around to checking your game for compatibility, Steam Deck will just run the native Linux version, with no Compatibility tool.

It turns out that we have more than one Electron game on Steam, and for games that haven't gone through the Steam Deck Compatibility Review, our native Linux build Just Works on Steam Deck, because its not running in SLR.

And, more than that, our native Steam Linux build doesn't just work on Steam Deck, in fact the native Linux build works better than the Proton build, due to a number of (already filed) bugs in Proton. (For example, on our native Linux build, the Deck's touchscreen controls work perfectly, and register as touch events in Electron; on Proton, touchscreen controls just register as clicks, no matter what we configure in our Steam Input settings.)

Maybe somebody will get around to fixing those Proton bugs over time, but Proton bugs are Hard to Fix, and I'm not holding my breath.

(Also, fixing bugs in Proton is way outside my personal area of technical expertise, whereas adding libcups to SLR seems like a relatively simpler problem.)

In conclusion, I'm happy to help, it's worth my time to help, and I'd be delighted to file a PR, if a PR is possible and would be accepted. What do you say?

@smcv
Copy link
Contributor

smcv commented Mar 28, 2023

Electron/NW.js provides prebuilt native binaries

How were those native binaries built, on what OS, and are their requirements documented? I wasn't able to find that information.

Are those requirements stationary, or are they going to go up over time?

the problem is that Steam Linux Runtime is missing libcups

Not exactly! You'll see that in the writeup of "Method 1", the error message is different: "symbol lookup error: blahblahblah: undefined symbol: cupsEnumDests". This is because Steam Runtime 1 'scout' does guarantee to provide libcups, but only an ancient version resembling Ubuntu 12.04, which is presumably older than whatever (essentially arbitrary) version is assumed by these NW.js binaries.

The user-facing "Steam Linux Runtime" consists of the Steam Runtime 2 'soldier' container, with Steam Runtime 1 'scout' also available to your game via a LD_LIBRARY_PATH, using the soldier version of a library if available or the scout version otherwise. This is intended to be a predictable way to run older native Linux games, without randomly breaking or working dependent on what happens to be included in the host OS. You are correct to say that Steam Runtime 2 'soldier' doesn't provide libcups, but the scout layer does (but, again, only an ancient version).

Manually using SteamLinuxRuntime_soldier/run-in-soldier puts you in a different environment, which isn't normally available to user-facing Steam games (but it's used internally by Proton). This environment is purely Steam Runtime 2 'soldier', with no extras, and yes this lacks libcups (on the assumption that games rarely want to print, and every MB of libraries we add to soldier is a MB your game can't use for something actually useful).

The sniper environment that @TTimo mentioned is a newer runtime, Steam Runtime 3 'sniper', which is a separate thing to target: a game can be compiled for scout (based on Ubuntu from 2012), or it can be compiled for sniper (based on Debian from 2021). Compiling for "Linux" isn't really something that makes sense, because different Linux distributions ship different subsets and versions of the libraries that a game might depend on: a game has to have a specific Linux environment that it was built in (or in the case of prebuilt binaries, that decision was delegated to whoever built those prebuilt binaries, in whatever environment). All native Linux Steam games are assumed to target scout unless configured otherwise, because that was the original Steam Runtime when Steam was first released on Linux.

The intention is that for modern native Linux games, we'll skip Steam Runtime 2 'soldier' and go directly to Steam Runtime 3 'sniper'. At the moment getting games to be run in Steam Runtime 3 'sniper' requires manual setup from a Valve developer, but the intention is that it will eventually be a self-service thing in the partner web interface, so that when you upload your game's Linux depot, you'll choose whether it's marked as compatible with scout, sniper, or some future runtime.

As you may be aware, the Steam Deck doesn't necessarily run games in the Steam Linux Runtime. Steam Deck will use SLR if it's forced to, in compatibility settings, and Valve's "Steam Deck Compatibility Review" process will only mark a game as Verified if it works in Proton or SLR, but if the Compatibility Review team hasn't gotten around to checking your game for compatibility, Steam Deck will just run the native Linux version, with no Compatibility tool.

That seems like a steam-for-linux bug? I would have expected unreleased or unreviewed native Linux games on Deck to default to Steam Linux Runtime.

@smcv
Copy link
Contributor

smcv commented Mar 28, 2023

With that done, you can npm run make to build the Linux app

This is presumably downloading prebuilt Electron binaries from ... someone, somewhere. Same questions: where do these binaries come from, how were they built, and are their requirements documented?

@smcv
Copy link
Contributor

smcv commented Mar 28, 2023

I'd identify a handful of necessary libraries, including, at least, libcups, and I'd file a PR(??) that would add libcups to sniper. (Is it even possible for me to file a PR like that? Would the PR be accepted?)

It's not exactly possible to file a PR, but the list of libraries in sniper is: https://gitlab.steamos.cloud/steamrt/steamrt/-/blob/steamrt/sniper/abi/steam-runtime-abi.yaml (and similar for other branches of the runtime). What we would need is a list of the library SONAMEs that these prebuilt binaries require, and that aren't already in the runtimes.

As for whether a request to add more libraries would be accepted, it's really a cost vs. benefit question which depends on which libraries they are. The cost is making the runtimes larger for everyone (particularly significant on the Deck's smallest, 64G model), plus the legal implications of distributing more libraries, and the ongoing maintenance needed to keep updating more libraries with security fixes etc.; and the benefit is games that need those libraries having guaranteed access to them, as long as they target a suitable runtime. The more libraries these browser engines need, the less appealing the cost/benefit tradeoff for adding them to the runtime is going to look.

@prominentdetail
Copy link

and yes this lacks libcups (on the assumption that games rarely want to print, and every MB of libraries we add to soldier is a MB your game can't use for something actually useful).

You can buy more than just games on the valve store though. Sure there might be less cases where something may be used, but they still might be used.

I use the prebuilt nw.js builds for my software app, and it runs fine on linux machines, however it fails to launch on steam deck, or rather it will crash (although if you run it from the local folder without launching from steam, it will run fine and not crash). I was told that steam requires it running with their Steam Linux Runtime when launched through steam. Why it works fine when run manually from the local folder and not through steam, I don't understand..

I don't have the technical understanding for solving this sort of issue, but it would be nice it is just works. It seems like something is odd in the way Steam launches apps considering it runs fine when manually opening it from the app folder. Sorry for not adding much, but this is just my perspective. I have no idea how to solve the issue, and I probably can't do it myself.

@dfabulich
Copy link
Author

dfabulich commented Mar 28, 2023

How were those native binaries built, on what OS, and are their requirements documented? I wasn't able to find that information.

Electron itself is distributed prebuilt as an npm package. The Electron team builds it themselves, and uploads their binaries to the npm file registry on https://registry.npmjs.org/

When you use Electron as a developer, you're downloading the Electron team's prebuilt binaries from there. Then, those dependencies get zipped up when you npm run make.

Electron's Github README documents how Electron was built. https://github.com/electron/electron/blob/main/README.md

The prebuilt binaries of Electron are built on Ubuntu 20.04. They have also been verified to work on:

  • Ubuntu 14.04 and newer
  • Fedora 24 and newer
  • Debian 8 and newer

The requirements for the Electron build (not runtime dependencies) are documented here: https://www.electronjs.org/docs/latest/development/build-instructions-linux

Note that these dependencies are much larger than the runtime dependency set. Nobody would/should ask Valve to include those dependencies in the Steam Linux Runtime.

Seven years ago, somebody filed a bug requesting that Electron's Linux runtime dependencies be documented. electron/electron#6945 Someone replied, linking to a file in the Electron build system that documented dependencies at that time, but that build system is no longer used.

Still, as that bug points out, you can run Electron with LD_DEBUG=files to see all of the runtime dependencies.

As for whether a request to add more libraries would be accepted, it's really a cost vs. benefit question which depends on which libraries they are.

OK, here's what I'll do.

  1. I'll use LD_DEBUG=files on an Electron app running on a Steam Deck, and get a list of libraries.
  2. Using that list, I'll copy individual files from the Steam Deck into the Steam Deck's run-as-soldier container. Presumably eventually, it's gonna work.
  3. Once I have a list of files, which presumably includes libcups (but there's probably more), I'll post the list here, and then y'all can tell me whether y'all would accept that list in SLR, at least in principle.

But, I do want to point out one thing.

The cost is making the runtimes larger for everyone (particularly significant on the Deck's smallest, 64G model), plus the legal implications of distributing more libraries, and the ongoing maintenance needed to keep updating more libraries with security fixes etc.

We're not asking for a large, exotic new dependency here. We're asking for access to libraries that the Steam Deck already ships.

Valve's legal team has (hopefully) already signed off on everything you're distributing as part of that distribution. You've already decided that every file you're distributing is/was worth its weight in disk space on the Steam Deck. Valve is already committed to maintaining these libraries, and shipping security fixes for them.

Adding libraries that already exist on Steam Deck can't possibly require additional legal review, or even incur additional maintenance burden. By distributing libcups etc. in SteamOS, you're on the hook for these libraries, regardless of whether Valve decides to add them to Steam Linux Runtime.

@vaughnroyko
Copy link

It turns out that we have more than one Electron game on Steam, and for games that haven't gone through the Steam Deck Compatibility Review, our native Linux build Just Works on Steam Deck, because its not running in SLR.

Toss us in the camp of not being able to run our game via Steam Linux Runtime due to this issue: https://store.steampowered.com/app/379210/Wayward/

There are actually quite a few games on Steam that use nw.js/Electron and just to name a few: https://github.com/greenheartgames/greenworks/wiki/Apps-games-using-greenworks (very non-exhausted, out-of-date)

And, more than that, our native Steam Linux build doesn't just work on Steam Deck, in fact the native Linux build works better than the Proton build, due to a number of (already filed) bugs in Proton. (For example, on our native Linux build, the Deck's touchscreen controls work perfectly, and register as touch events in Electron; on Proton, touchscreen controls just register as clicks, no matter what we configure in our Steam Input settings.)

Also, toss us into that camp as our current in-development build of our game doesn't launch at all due to a Proton bug.

@dfabulich
Copy link
Author

As you may be aware, the Steam Deck doesn't necessarily run games in the Steam Linux Runtime. Steam Deck will use SLR if it's forced to, in compatibility settings, and Valve's "Steam Deck Compatibility Review" process will only mark a game as Verified if it works in Proton or SLR, but if the Compatibility Review team hasn't gotten around to checking your game for compatibility, Steam Deck will just run the native Linux version, with no Compatibility tool.

That seems like a steam-for-linux bug? I would have expected unreleased or unreviewed native Linux games on Deck to default to Steam Linux Runtime.

You can file that bug if you want to, but I'm not going to, because this "bug" is making my app work better on Steam Deck.

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

The prebuilt binaries of Electron are built on Ubuntu 20.04

If that's the case, the fact that they work on Steam Deck (or Fedora, or Debian, or Arch, or even Ubuntu 18.04 or 22.04) without the container runtime is more luck than judgement. Some of the libraries in Ubuntu 20.04 are known to be incompatible with the most similar libraries on a rolling release like Arch: notably, Ubuntu 20.04 has OpenSSL 1.1 but more recent stacks have OpenSSL 3, and Ubuntu 20.04 has libjpeg.so.8 but most other Linux distros have libjpeg.so.62.

I know it's very convenient to be able to download prebuilt binaries from the npmjs registry, but the price you pay for that convenience is that you've given up control over how those binaries were built, what they depend on, and what platforms they'll run successfully on.

We're asking for access to libraries that the Steam Deck already ships

Libraries that it already ships today. Will it ship them next year? I don't know, and neither do you: SteamOS 3 as shipped on the Steam Deck is an implementation detail, not a guaranteed ABI. If it can run Flatpak apps and Steam successfully, then its job has been done. Today, that includes libcups, because libcups happens to be pulled in by some library (probably GTK or Qt) as a dependency; but if space is running out on the root partition, SteamOS developers might take the decision to recompile without libcups printing support. I'm not a SteamOS developer, so I can't predict whether that will happen.

The only guaranteed ABI for native Linux games on Steam is scout (which does have libcups, but at a version that is too old for these prebuilt Electron binaries), or for new and actively-updated games, sniper (which doesn't currently have libcups). If sniper gains libcups as part of its guaranteed ABI, it will be the long-term-stable version from Debian 11, which is what sniper is based on: sniper is intentionally not a moving target, unlike SteamOS.

I'll use LD_DEBUG=files on an Electron app running on a Steam Deck, and get a list of libraries

An easier way to get similar information would be to run your game without Steam Linux Runtime (so your game is in a state where it is working the way you want it to), and read the /proc/*/maps for the main game process to get the libraries that it has loaded. The result will not necessarily be a minimal dependency list, but it will at least be an upper bound for what's needed, and we can discuss from there.

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

On Steam Deck, download my game, SLAMMED https://store.steampowered.com/app/343370/SLAMMED/ (you can test with the Demo if you don't want to buy the full game)

The demo seems to install on Linux as an empty directory, at least on desktop. I think something might be misconfigured? If I force it to use Proton it installs about 75M compressed size, which seems more reasonable (but obviously that gives me the Windows executables, so I can't tell from that what the Linux dependencies look like).

Heart of the House Demo (742160) looks like it's also Electron, and installs correctly. Is that using the same binaries?

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

Heart of the House Demo (742160) mandatory dependencies from objdump -T -x:

  • libffmpeg.so is bundled locally, so can be ignored
  • Not part of the runtime:
    • libnss3.so (Mozilla libnss)
    • libnssutil3.so (Mozilla libnss)
    • libsmime3.so (Mozilla libnss)
    • libnspr4.so (Mozilla libnspr)
    • libcups.so.2 (CUPS)
  • Not officially a guaranteed part of the runtime ABI right now, but it does ship them, so perhaps they should be:
    • libatk-bridge-2.0.so.0
    • libatspi.so.0
    • libxkbcommon.so.0
  • Not officially a guaranteed part of the runtime ABI, but we count on them being provided by host graphics drivers:
    • libgbm.so.1

... and the rest are already in sniper.

Additionally, it bundles libvulkan.so.1, but perhaps shouldn't (all of the runtimes provide an up-to-date copy of libvulkan.so.1).

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

The recursive dependencies from LD_DEBUG=files or /proc/*/maps are misleading because they'll include transitive dependencies of the graphics drivers. On a Debian 12 system with Radeon gfx, they possibly include these libraries that are not officially guaranteed to be in sniper, but maybe should be (in practice they're present):

  • libpciaccess.so.0
  • libsensors.so.5
  • libxshmfence.so.1
  • libglapi.so.0
  • libmd.so.0
  • libbrotlicommon.so.1
  • libkeyutils.so.1
  • libbsd.so.0
  • libbrotlidec.so.1
  • libdatrie.so.1
  • libgraphite2.so.3
  • libwayland-cursor.so.0
  • liblz4.so.1
  • libzstd.so.1
  • libhogweed.so.6
  • libnettle.so.8
  • libtasn1.so.6
  • libunistring.so.2
  • libkrb5support.so.0
  • libk5crypto.so.3
  • libkrb5.so.3
  • libblkid.so.1
  • libwayland-egl.so.1
  • libcom_err.so.2
  • libwayland-server.so.0
  • libthai.so.0
  • libepoxy.so.0
  • libcairo-gobject.so.2
  • libfribidi.so.0
  • libharfbuzz.so.0
  • libsystemd.so.0
  • libgssapi_krb5.so.2
  • libmount.so.1
  • libpcre2-8.so.0

and these libraries that are not in sniper:

  • libicudata.so.72.1
  • libz3.so.4
  • libnssckbi.so
  • libfreeblpriv3.so
  • libLLVM-15.so.1
  • libsoftokn3.so
  • libicuuc.so.72.1
  • libavahi-client.so.3
  • libavahi-common.so.3
  • libplds4.so
  • libplc4.so

but I'm hoping most of those are implementation details of my host system that aren't strictly required.

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

The demo seems to install on Linux as an empty directory, at least on desktop. I think something might be misconfigured?

https://steamdb.info/sub/58212/depots/ only gives demo users access to the Windows demo depot free of charge, and not the Mac or Linux demo depots. If this is your game, you might want to fix that.

@smcv
Copy link
Contributor

smcv commented Mar 29, 2023

If the binaries used in Heart of the House Demo (742160) are representative, it looks like the key missing libraries are:

  • libcups2 (libcups.so.2): too old in scout, not in soldier (which is why "Steam Linux Runtime" doesn't work), also not in sniper (so configuring these games to run under the next-gen sniper runtime will also not work)
    • dependency: this will pull in libavahi-client and libavahi-common (which were in scout but not in soldier) unless patched to disable mDNS printing support
  • libnspr4 (libnspr4.so, etc.): present in soldier, but removed from sniper
  • libnss3 (libnss3.so, etc.): present in soldier, but removed from sniper

@insraq
Copy link

insraq commented Mar 29, 2023

I know it's very convenient to be able to download prebuilt binaries from the npmjs registry, but the price you pay for that convenience is that you've given up control over how those binaries were built, what they depend on, and what platforms they'll run successfully on.

From another perspective, most (indie) game developers do not have the resources to test against all different distros/versions. Choosing Electron is one way to "solve" the problem. Electron powers some of the most used apps (VSCode, Slack, Discord, Figma, MS Teams). They are "battle tested" against lots of Linux environments. If things do not work, it is very likely to get fixed in the upsteam. Us indie devs are mostly piggy backing on this effort for Linux support.

I know this is not guaranteed and is not futureproof but the alternative is simply not to provide Linux support, which is considerably worse - If Electron works on more than 80% of Linux distros, it is good enough. In fact, one of the most played game on Steam Deck Vampire_Survivors is also using Electron.

Steam Linux Runtime, from this point of view, is "just another Linux distro". It would be really nice to have out of box support for Electron - that will make it easy for game devs to provide a Linux build - and hopefully more will do so.

@insraq
Copy link

insraq commented Mar 29, 2023

Also, not sure if this can help, I searched Chromium repo, it seems the missing symbol is introduced by this change: https://chromium.googlesource.com/chromium/src/+/d4672d5e5ea885aba941f0e98517e8201c1a7967 (released in Chromium 94)

@dfabulich
Copy link
Author

The demo seems to install on Linux as an empty directory, at least on desktop. I think something might be misconfigured? If I force it to use Proton it installs about 75M compressed size, which seems more reasonable (but obviously that gives me the Windows executables, so I can't tell from that what the Linux dependencies look like).

Oops! How embarrassing. 😳 I've added the appropriate depots to the Slammed Demo package. It should now be possible to access its files.

Thanks so much for taking a look at a different game of ours, which, yes, is equivalent in its dependencies.

@dfabulich
Copy link
Author

dfabulich commented Mar 30, 2023

I've found a workaround

I used the my-app reproduction steps, described in my original bug description.

Using run-as-soldier on the Steam Deck, I was able to manually copy /usr/lib/libcups.so.2 into ~/my-app/out/my-app-linux-x64, and then run ./my-app from there. That gave me a different error:

./my-app: error while loading shared libraries: libavahi-client.so.3: cannot open shared object file: no such file or directory

… which is exactly what @smcv's findings predicted, because libcups.so.2 will pull in libavahi-client and libavahi-common. I then manually copied /usr/lib/libavahi-client.so.3.2.9 as libavahi-client.so.3, and then copied /usr/lib/libavahi-common.so.3.5.4 as libavahi-common.so.3.

With those three files copied into the output directory, and without copying in any version of libnspr4 or libnss3, my ./my-app app launched!

But note that it worked in soldier, not sniper. To be forward compatible with sniper, we'd presumably need libnspr4 and libnss3 as well.

Instructions to test my workaround

I've cooked a Steam beta of Slammed that embeds those three libraries.

You should be able to test the Slammed beta in SLR like this:

  1. Download Slammed (or the Demo) on Steam Deck
  2. In the Library, click the gear icon and select Properties
  3. Go to Betas, and enter the beta access code: clotheslined When you Check the Code you'll be able to access the steamlinuxruntime beta.
  4. Go to Compatibility, check "Force the use of a specific Steam Play compatibility tool", and select Steam Linux Runtime.
  5. Update the app if needed and play it.

Apply the workaround to your own Electron app

I took these three files from Steam Deck. You can get your own copies, or you can trust my copies.

https://drive.google.com/drive/folders/1qeTNZLn_kWFFUYeWu5l_c-jKXx2_W7rl?usp=sharing

  • libcups.so.2
  • libavahi-client.so.3 (this was /usr/lib/libavahi-client.so.3.2.9 on the Steam Deck)
  • libavaih-common.so.3 (this was /usr/lib/libavahi-common.so.3.5.4 on the Steam Deck)

I added these three files into the zip file that Electron Forge makes. (Be sure to add the files to the right subdirectory within the zip.)

Then, I uploaded the files in the zip to SteamPipe.

Troubleshoot your app

If it's not working for you, try using run-as-soldier in Desktop Mode on the Steam Deck.

./.local/share/Steam/steamapps/common/SteamLinuxRuntime_soldier/run-in-soldier -- xterm

From there, you should be able to cd to the directory containing your Steam app (it's somewhere in ./.local/share/Steam/steamapps/common) and try running your app from there. If it says you're missing a library, copy that file from /usr/lib on the Steam Deck into your app's directory and try running again.

(Note that you won't be able to access the Steam Deck's real /usr/lib directory from inside the SLR container's xterm. You'll have to launch a console terminal outside the SLR, copy the files into place, and then use the files from inside the container.)

@dfabulich
Copy link
Author

OK, so, I've got a workaround. But that still raises a question about whether I should actually ship this thing to end users.

libcups2, libavahi-client, and libavahi-common are all LGPL libraries; it's legal to redistribute them. (Valve sells a copy of them with every Steam Deck.) And they're "small," only 750K total. (Most of that is libcups2.)

-rw-r--r--@ 1 dfabulich  staff    74K Mar 29 18:13 libavahi-client.so.3
-rw-r--r--@ 1 dfabulich  staff    50K Mar 29 18:13 libavahi-common.so.3
-rw-r--r--@ 1 dfabulich  staff   626K Mar 29 18:13 libcups.so.2

Still, it's not at all clear to me that it's wise to bundle them with my game app.

I think the core question I have is: will Steam Linux Runtime at some point include its own working (modern) copy of libcups2?

In particular, it seems unwise to ship my own copies of these libraries if the Valve team is just about to distribute your own copies, which may or may not exactly match the libraries I'd ship. (Seems like maybe y'all might try to ship a patched version of libcups2 that doesn't use libavahi? That seems like a lot of work just to shave 100KB…??)

If Valve said, "we'd be delighted to upgrade libcups2 in SLR, just give us a week," then I think I'd prefer to wait until y'all shipped these libraries as part of SLR.

On the other hand, if y'all said, "we might upgrade libcups2 in SLR, but we might not; it might be next month, it might be next year, it might be never, but we promise that shipping these three libraries on top of today's SLR won't break your game in the future if/when we do decide to ship our own version of libcups2 in SLR," then I'd certainly ship these libraries for now. (And stop shipping them if/when Valve ever decides to include a newer version of libcups2 in SLR.)

Can Valve offer me some guidance here? Should I ship this workaround, or nah?

P.S. In light of how little we need, and in light of how popular Electron is on Steam, I do sincerely hope that Valve seriously considers offering an upgraded version of libcups2 in SLR, even if we do decide that Electron apps can ship this workaround.

@smcv
Copy link
Contributor

smcv commented Mar 30, 2023

Can Valve offer me some guidance here?

I help Valve to maintain the Steam Runtime, but I don't make the rules, so all I can do is say what's an option and what specifically doesn't work. @TTimo would be able to confirm what is the right thing to do (after we've agreed on what's happening in the Steam Runtime, which is not set in stone yet).

shipping these three libraries on top of today's SLR won't break your game in the future

Sorry, that is not something that we will ever be able to promise. If a game bundles a library that subsequently gets added to the runtime, there is always the possibility that the bundled copy will break compatibility, and in fact there's a semi-common pattern where bundling the library does break the game.

As long as nothing else in the runtime depends on that library, in practice it's usually not a problem.

However, if something else in the runtime starts depending on a library that you have bundled, then that can break things. The most likely way for libcups to cause that problem is if we stopped reconfiguring GTK 3 to remove its libcups dependency, so that we had this dependency chain:

libcups --> GTK --> }
  |                 } Electron
   \--------------> }

If that happens, then it's possible for a version of libcups bundled with the game to be incompatible with the one required by the runtime's GTK, breaking GTK, and indirectly breaking Electron.

(This is another reason why the answer to "please add this library to the runtime" can't always be an immediate "yes".)

If you remove bundled libraries promptly after they become unnecessary, that minimizes the risk of something like this going wrong.

If you choose a version that matches the one we're most likely to add to the runtime (soldier: Debian 10, sniper: Debian 11) then that also minimizes the risk.

I took these three files from Steam Deck

Steam Deck is a moving target (SteamOS is based on Arch Linux, a fast-moving rolling-release distro), so it is not a suitable source for libraries that need to stay compatible and consistent essentially forever. This time you were lucky, and the Steam Deck's copies of libcups and libavahi-* happened to be compatible enough with the Steam Linux Runtime, but that's by luck, not by design. Tomorrow, or next week, or next year, they might not be; or they might not even be present.

That's why the Steam Deck always uses Steam Linux Runtime for "works on Deck" QA for native Linux games, and not the OS's libraries: we want to be confident that if a game works today, it will still work next year. If games were allowed to rely on the OS's libraries, then there would be no way to predict that. The OS filesystem is very small compared with the game library filesystem, and every MB in the OS filesystem "costs" 2MB of storage because of how OS upgrades are done, so it isn't practical to keep old versions of libraries in the OS filesystem after the OS no longer needs them.

scout is based on a version of Ubuntu from 2012, soldier is based on a version of Debian from 2019, and sniper is based on a a version of Debian from 2021, all of them "long term support" distributions that don't frequently change. The only supported library stacks that native Linux games on Steam can rely on are scout (by default) and sniper (next-generation runtime, requires a per-game opt-in which currently needs to be set up by someone in Valve). Anything else that happens to work by coincidence is just that: coincidence.

In an effort to be nice to older games that weren't compiled according to Valve's recommendations, the "Steam Linux Runtime" container is already providing more than is strictly guaranteed or supported: games in that container get access to the updated libraries from soldier, not just scout. It is still the case that compiling a game in the scout SDK is the only thing that is supported or supportable. (Originally the "Steam Linux Runtime" container was strictly scout, with no components from soldier - but it turned out that a lot of existing games from before the container runtime existed were compiled in an unsupported way and wouldn't work with only scout libraries, so layering scout onto soldier was a way to get something resembling a then-current Linux system as they existed in practice.)

For games that are flagged to run in the sniper container, the only thing that is supported or supportable is to compile the game in the sniper SDK. If the game binaries were built in some other environment (for example Ubuntu 20.04 like these Electron binaries) then they might work, or they might not, depending how lucky you happen to be; no guarantees either way.

If we add libcups/libavahi to soldier (which I can't confirm or deny, it's not my decision to make), then they are very likely to be the long-term-stable versions from Debian 10 'buster' (2019), which is what soldier is based on, and not the same versions that exist at the OS level on Steam Deck. soldier already contains the libnspr4 and libnss3 from buster.

Similarly, if we add libcups/libavahi/libnspr4/libnss3 to sniper, they will be the versions from Debian 11 'bullseye'. If we go that route, each game that needs these libraries would need to be flagged as "needs sniper", which would make it always, consistently run in sniper (both on Deck and on desktop Linux), never in scout or scout+soldier. Retroarch is an early example of a game that works like this (it's free, if you want to try installing it and see how it behaves).

If you bundle libcups with Electron games as a workaround, and the version in scout isn't enough (which apparently it isn't), then I would suggest the version from Debian 10 as more suitable than some arbitrary version from an Arch/SteamOS rolling release.

Seems like maybe y'all might try to ship a patched version of libcups2 that doesn't use libavahi? That seems like a lot of work just to shave 100KB…

I hadn't yet looked at the size of libavahi-* at the time I mentioned the possibility of patching out that dependency. Since the Avahi libraries are that small, it's probably not worth it.

libcups2, libavahi-client, and libavahi-common are all LGPL libraries; it's legal to redistribute them. (Valve sells a copy of them with every Steam Deck.)

(I am not a lawyer, please get real legal advice if your business needs it.)

Strictly speaking, if you bundle LGPL libraries with your game, you need to provide the source code for those libraries. For the libraries in the Steam Runtime, each version in https://repo.steampowered.com/steamrt-images-scout/snapshots, https://repo.steampowered.com/steamrt-images-soldier/snapshots and https://repo.steampowered.com/steamrt-images-sniper/snapshots comes with corresponding source code for everything we release; if we do add libcups etc. to the Steam Runtime, then that will start to include source code for the corresponding version of libcups.

I don't work on SteamOS, so I don't know the specifics of how Valve arrange to provide corresponding source for the libraries on Steam Deck, but most likely it's via the Arch-Linux-style pacman package manager repositories.

@TTimo
Copy link
Collaborator

TTimo commented Mar 30, 2023

Hello everyone,

Thanks for all the very useful feedback. This is the plan right now:

  • We will be adding libcups2 into the soldier runtime. We think this will fix a large proportion of Electron/NWJS apps to run natively as 'Steam Linux Runtime' (e.g. "scout over soldier SLR"), at least the ones that use the binaries most commonly distributed and lifted out of an Ubuntu 20 host.

  • We will add libcups2 and several more libraries to sniper (both the runtime and the sdk). This can be used as a basis to address more difficult cases not fixed by the above.

Ideally, when the next major Electron update happens, the right thing to do would be to build a release specifically against the sniper runtime for anything that will be distributed on Steam and wants to run natively.

We don't expect each indie devs to be doing this for themselves of course, but due to the nature of open source maybe someone will step up. This is something we'll be looking at doing ourselves as well, and like everyone else we have to account for cost and benefits before we'd commit to it.

@dfabulich
Copy link
Author

That's fantastic news, @TTimo!

Would you be able to say a word or two about timing? I recognize that pre-announcing timing is always perilous, but it would be a huge help if you could say whether soldier will include libcups2 in "weeks" or "months".

Basically, if it's "months," I'll probably ship my games with the workaround, and plan to rollback the workaround promptly when soldier includes libcups2.

If it's "weeks," I'll just wait for your fix. (The Proton build works OK-ish for me as-is.)

@smcv
Copy link
Contributor

smcv commented Mar 30, 2023

I've added the appropriate depots to the Slammed Demo package. It should now be possible to access its files.

Thanks, it now installs correctly.

I've confirmed that the Slammed demo runs successfully in a local prerelease build of the soldier runtime with the libcups2 from Debian 10 added to its library set.

Similarly, it also runs successfully in a local prerelease build of the sniper runtime with libcups2, libnss3 and libnspr4 from Debian 11 added to its library set.

I need to respin those with the appropriate development files added to the SDK (to make it possible, or at least easier, to build Electron in our official SDK) but if all goes well this should be in the next beta.

I can't promise anything about timing, but we generally aim for 1 or 2 betas a month, and if there are no bad regressions then changes in beta get promoted to stable alongside the next beta.

@smcv
Copy link
Contributor

smcv commented Mar 30, 2023

the next beta

(I'm referring here to "Steam Linux Runtime - soldier" and "Steam Linux Runtime - sniper" betas, which are in their own release pipeline that isn't directly connected with Steam client betas.)

@dfabulich
Copy link
Author

Thanks so much! That sounds to me like "no promises, but we're thinking weeks, not months," so for now I'll just wait for the fix to roll out, rather than shipping my own libcups2.

If you have time, please keep us updated here on when the fix is available in beta (so we can try it) and when the fix is released to end users.

@dfabulich
Copy link
Author

Ideally, when the next major Electron update happens, the right thing to do would be to build a release specifically against the sniper runtime for anything that will be distributed on Steam and wants to run natively.

We don't expect each indie devs to be doing this for themselves of course, but due to the nature of open source maybe someone will step up. This is something we'll be looking at doing ourselves as well, and like everyone else we have to account for cost and benefits before we'd commit to it.

A related approach would be to have a canonical "best practice" guide for running web-based content on Steam, which may or may not be Electron/NW.js.

For example, a competitor platform to Electron is Tauri. https://tauri.app/

Electron and NW.js embed a copy of Chromium, which has new "major" releases on a monthly basis. Tauri is designed to use the platform's existing web libraries. On Windows, Tauri uses WebView2. On macOS, Tauri uses WKWebView. On Linux, Tauri uses libwebkit2gtk.

And, on Tauri, "back end" code is written in Rust, not in JavaScript.

Tauri also provides GitHub Actions, allowing you to compile your Tauri app on Github's Linux machines.

It would be convenient if libwebkit2gtk were just part of soldier or sniper, but I think that's too big a dependency to take. libwebkit2gtk is basically a browser. It weighs ~11MB. It requires frequent security and maintenance fixes. Steam Deck doesn't ship it, even outside the container.

But, if Valve were to publish a public build of libwebkit2gtk that had been tested to work in Steam Linux Runtime, and especially if Valve were to make a Tauri GitHub Action for SLR, allowing developers to compile Tauri apps for SLR via that action, that could be an excellent well-supported way for developers to develop a a webapp in JS + Rust, and compile it to SLR, without anyone having to maintain a fork of all of Electron, build Chromium from scratch in the SLR dev environment, etc.

One of the takeaways I'm having from this bug thread is that even though Electron v23 with Chromium 111 will work in soldier and sniper once libcups2 is added, there's just no guarantee that it will keep working when it comes time to upgrade Electron.

If there were a "best practice" approach for web-based Steam apps, and it turned out to be Tauri, I'd switch to it, and I imagine a lot of other people would, too.

@smcv
Copy link
Contributor

smcv commented Apr 4, 2023

One of the takeaways I'm having from this bug thread is that even though Electron v23 with Chromium 111 will work in soldier and sniper once libcups2 is added, there's just no guarantee that it will keep working when it comes time to upgrade Electron.

The same is true for any third-party binary, whether it's a typical game engine (Source, Unity, Unreal, Godot, GameMaker) or a language interpreter (Haxe/Hashlink, Flash, NodeJS) or a web browser engine (Chromium/Electron/Blink, WebKit, Gecko). If it wasn't compiled in the Steam Runtime SDK, then the Steam Runtime isn't necessarily going to be able to run it.

Desktop Linux is in a similar situation: if a third-party binary was compiled on (for example) Ubuntu 22.04, then (for example) Fedora 37 systems won't necessarily be able to run it (maybe they can in practice, if you're sufficiently lucky, but nobody can guarantee that without actually doing the testing). The same is true for any pair of distributions and versions, and each branch of the Steam Runtime behaves a lot like a specialized Linux distribution.

@smcv
Copy link
Contributor

smcv commented Apr 4, 2023

I've confirmed that the Slammed demo runs successfully in a local prerelease build of the soldier runtime with the libcups2 from Debian 10 added to its library set.

While testing this more thoroughly in preparation for a new beta, I found that on a Debian 11 host system with AMD GPU and Mesa, the Slammed and Heart of the House demos running under SLR both crash shortly after startup (I get a white window for a moment, but no text is drawn) with a segfault in the Chrome_InProcGp thread. The sniper runtime behaves similarly, and so does the non-container scout runtime.

Debian 10 and 12 on the same hardware are OK, and so are Arch Linux, Steam Deck, and Ubuntu 22.04 on different hardware. (It could be a graphics driver bug specific to the version of Mesa in Debian 11, I can't tell.)

This isn't a regression, and your games don't run under SLR by default on anything except Steam Deck, so I'm treating this as non-critical and will proceed anyway; but I thought you should be aware that adding these few libraries is not a 100% solution to running Electron/NW.js games in a cross-platform way.

@dfabulich
Copy link
Author

Oh, dang! Please let me know when it becomes possible for me to test the upcoming version… I'll try to get to the bottom of it.

@smcv
Copy link
Contributor

smcv commented Apr 5, 2023

The public beta branch of Steam Linux Runtime - soldier has this change since build 0.20230403.46955 (VERSIONS.txt should say soldier 0.20230403.46955 or later). Instructions for selecting the beta

There is a known regression in this beta: it will not start up successfully if there is a dangling symlink $XDG_RUNTIME_DIR/discord-ipc-* (#581). Workaround: remove the dangling symlink. [edit: This is fixed in newer beta 0.20230405.47174.]

The public beta branch of Steam Linux Runtime - sniper has the same changes since build 0.20230403.46952 (VERSIONS.txt should say sniper 0.20230403.46952 or later). More information about sniper

@dfabulich
Copy link
Author

The beta looks good to me so far, but I don't have convenient access to an Intel machine today, so I haven't yet tried to reproduce the issue you reported on Debian 11.

On Steam Deck, I downloaded the client_beta version of Soldier, which VERSIONS.txt identifies as 0.20230405.47174.

Then I launched the non-beta version of Slammed (which doesn't include special copies of libcups etc.). It ran without error, and I clicked through a few pages; it didn't crash.

@smcv
Copy link
Contributor

smcv commented Apr 26, 2023

On Steam Deck, I downloaded the client_beta version of Soldier, which VERSIONS.txt identifies as 0.20230405.47174.

This version was promoted to the default branch today, so I think this issue can now be closed.

@smcv
Copy link
Contributor

smcv commented Apr 26, 2023

Ideally, when the next major Electron update happens, the right thing to do would be to build a release specifically against the sniper runtime for anything that will be distributed on Steam and wants to run natively.

The default branch of the sniper runtime (and indeed the default branch of the soldier runtime) should now contain most (hopefully all) of the necessary -dev libraries to make this possible.

@dfabulich
Copy link
Author

dfabulich commented Apr 26, 2023

I agree! SLAMMED is working without error in Steam Linux Runtime mode. Thanks again for fixing this!

@prominentdetail
Copy link

prominentdetail commented Apr 27, 2023

Didn't work for my app. What should I do?
I had a user test it using Steam linux runtime and also proton, but it fails to launch (it closes after launching)

@kisak-valve
Copy link
Member

Hello @prominentdetail, please open a new issue report.

@prominentdetail
Copy link

opened: #585

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants