I'm compiling here Steam Deck quality of life improvements and tricks that will make working with it smoother and a more comfy place to work in.
- Install packages into the rootfs using pacman
- Easy SSH access to the Steam Deck
- Disable powersave on wlan0 for snappier remote command
- Add konsole (terminal) to Steam Deck UI
- Encrypted Vaults with Plasma Vault and gocryptfs
- Shared keyboard, mouse and clipboard with barrier
- auto-cpufreq
- Wireguard
- Use podman to create a SteamOS/Arch development image
- Create a SteamOS/Arch development root in your home folder
- Use your smartphone as webcam via Droidcam
- Android via Waydroid
- Convert the home partition to Btrfs
- Gamescope fps limiter not working on flatpaks
- Toggle internal display via hotkeys
To install system packages you'll need to make the rootfs read-write and set up the keyrings first.
Anything you install this way will disappear after a system update or branch change. (But it's easy enough to reinstall them)
BUT, files installed into specifc locations will remain because they are offloaded onto your /home
partition or the separate /var
partition:
/etc
/opt
/root
/srv
- (and a few specific ones in
/var
:/var/cache/pacman
,/var/lib/docker
,/var/lib/flatpak
,/var/lib/systemd/coredump
,/var/log
,/var/tmp
)
Concerning the arch repos maintained by Valve, the package versions seem to be pinned to a fixed target which means the packages are not rolling as much as on pure arch (like glibc
being at 2.33 instead of 2.35 in the normal arch repos).
The ota updater of SteamOS is the one supposed to roll the whole system to a newer target safely. That's why in my guides about installing software through pacman
I'm somewhat going against Arch's mantra about Partial upgrades being unsupported and invoking pacman
with -Sy
without -u
. The -y
is present to make sure the missing package databases are synced but having -u
might potentially pull in upgrades for system libraries that should preferably be updated during an ota update. So the worst case scenario in this instance would be that the package you just installed does not work which is preferable to potentially going through a full upgrade leaving the system in a weird state. For "simple" packages (like ones that do not have many dependencies) this should be ok but more complex packages might need a full upgrade -u
nonetheless.
sudo steamos-readonly disable
sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo pacman-key --populate holo
sudo pacman -Sy <packagetoinstall>
sudo steamos-readonly enable
In desktop mode, set a password for your user
passwd
Enable and start the ssh server
sudo systemctl enable --now sshd.service
Remember the ip address of the Steam Deck
ip addr
Generate a new keypair
ssh-keygen -t ed25519 -f ~/.ssh/steamdeck_ed25519
(replace <ip>
with the ip of the Steam Deck)
Copy the public key over to the Steam Deck (will ask for the password of user deck)
ssh-copy-id -i ~/.ssh/steamdeck_ed25519.pub deck@<ip>
Create a configuration for the Steam Deck, create ~/.ssh/config
and add:
Host steamdeck
HostName <ip>
IdentityFile ~/.ssh/steamdeck_ed25519
User deck
Now you can simply connect with
ssh steamdeck
On the Steam Deck you can disable password authentication:
Open /etc/ssh/sshd_config
and find:
#PasswordAuthentication yes
and change to
PasswordAuthentication no
Reload sshd
sudo systemctl reload sshd.service
If ssh or barrier is lagging
sudo iw dev wlan0 set power_save off
It can be practical to have konsole directly accessible from the Steam Deck UI
Add non-steam game to Steam in Desktop mode, select konsole and add as launch options:
--fullscreen --notransparency
This should make it work correctly inside gamescope. For the controller layout make sure to select the Web Browser Template.
If you get LD_PRELOAD
logs or obnoxious pid XXXX != XXXX, skipping destruction (fork without exec?)
lines between the commands you execute, you can do the following:
- In the
Target
of thekonsole
shortcut set:env
- In the
Launch options
set:-u LD_PRELOAD konsole --fullscreen --notransparency
- Enjoy a clean terminal!
Plasma Vault is a handy way to quickly create and manage encrypted folder embedded into the desktop environment.
Vaults might be hidden by default under Status and notifications visible by clicking the up arrow left to the date and time.
You can choose between CryFS, EncFS and gocryptfs crypto backend. My preferred is gocryptfs: https://wiki.archlinux.org/title/Gocryptfs
To add gocryptfs support:
sudo pacman --cachedir /tmp -Sw --noconfirm gocryptfs
mkdir -p ~/.local/bin
tar -xf /tmp/gocryptfs-*.pkg.tar.zst -C ~/.local/bin --strip-components=2 usr/bin
sudo rm -f /tmp/gocryptfs*.pkg.*
mkdir -p ~/.config/environment.d
echo 'PATH="$PATH:$HOME/.local/bin"' >> ~/.config/environment.d/envvars.conf
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.bash_profile
Logout and back in for Plasma Vaults to pick up gocryptfs. You can now create encrypted Vaults with the gocryptfs backend.
It is more secure to store the browser profile in an encrypted Vault in case of theft or unauthorised access to the Steam Deck.
The following uses brave as example but you can apply it to any app where you can identify the config path and move it into the encrypted vault.
- We assume we have a plasma Vault called
main
located and unlocked at~/Vaults/main
- The flatpak version of Brave stores the user profile at
~/.var/app/com.brave.Browser/config/BraveSoftware
- Close Brave if it's running
- Unlock the Vault
- Copy the user profile to the Vault
cp -a ~/.var/app/com.brave.Browser/config/BraveSoftware ~/Vaults/main/
- Remove the unencrypted profile and create a symlink to the encrypted profile
rm -rf ~/.var/app/com.brave.Browser/config/BraveSoftware
ln -s ../../../../Vaults/main/BraveSoftware ~/.var/app/com.brave.Browser/config/BraveSoftware
- Add the new location as allowed path to Brave flatpak using flatseal
- Install flatseal from Discover and open it
- Select Brave Browser (com.brave.Browser)
- Under Filesystem > Other files add
~/Vaults/main/BraveSoftware
- Now all user data will be stored encrypted and Brave will only launch if the Vault is unlocked!
Another popular example is Discord:
- The user profile is located at
~/.var/app/com.discordapp.Discord/config/discord
cp -a ~/.var/app/com.discordapp.Discord/config/discord ~/Vaults/main/
rm -rf ~/.var/app/com.discordapp.Discord/config/discord
ln -s ../../../../Vaults/main/discord ~/.var/app/com.discordapp.Discord/config/discord
- in flatseal add
~/Vaults/main/discord
under Filesystem > Other files
If you store your sensitive data and profiles inside Plasma Vaults, it might be practical to have a way to unlock them from the Steam Deck UI.
- Create
~/.config/systemd/user/[email protected]
:
(mkdir -p ~/.config/systemd/user ; vim ~/.config/systemd/user/[email protected]
)
[Unit]
Description=Mount plasma Vault %I
DefaultDependencies=no
Conflicts=umount.target
Before=umount.target
[Service]
Type=forking
ExecStartPre=-rm -f /tmp/plasma-vault-pass-%i
ExecStartPre=mkfifo /tmp/plasma-vault-pass-%i
ExecStart="%h/.local/bin/gocryptfs" -idle 5m --extpass "head -n 1 /tmp/plasma-vault-pass-%i" "%h/.local/share/plasma-vault/%I.enc" "%h/Vaults/%I"
ExecStartPost=-rm -f /tmp/plasma-vault-pass-%i
The systemd user service will handle the mounting of the encrypted folder in the user sessions.
You can set the idle time to lock to whatever you prefer.
Reload the systemd user daemon:
systemctl --user daemon-reload
- Create
~/.local/bin/vault-handler
:
(mkdir -p ~/.local/bin ; vim ~/.local/bin/vault-handler
)
#!/bin/sh
VAULT_NAME="$1"
VAULT_NAME_ESC="$(systemd-escape "$VAULT_NAME")"
mountpoint -q "$HOME/Vaults/$VAULT_NAME" && exit 0
PASS_FIFO="/tmp/plasma-vault-pass-${VAULT_NAME_ESC}"
for _ in $(seq 3)
do
systemctl --user --no-block start plasma-vault@"$VAULT_NAME_ESC"
sleep 1
ksshaskpass "Unlock plasma Vault '$VAULT_NAME'" > "$PASS_FIFO" || exit 0
sleep 1
systemctl --user --quiet is-active plasma-vault@"$VAULT_NAME_ESC" && break
done
Make it executable:
chmod +x ~/.local/bin/vault-handler
This will use ksshaskpass
as graphical password prompt to unlock the vault.
- Add
vault-handler
to Steam as Non-Steam game and in the launch options set the name of your vault to unlock (e.g.:main
)
barrier is a fork of synergy which makes it possible to share keyboard, mouse and clipboard of one computer with another over the network.
Install barrier from Discovery on the computer and the Steam Deck.
- Choose Server
- Configure Server... gives you a visual representation of relative position the computers should have
- Double click on the position relative to the main computer you want to position your Steam Deck
- As Screen name set
steamdeck
or the hostname you have set for your Steam Deck - Start server
- Make sure port 24800 tcp is open if a firewall is configured
- Choose Client
- Auto config should work otherwise input the ip address of the main computer
- Make the client hide on startup: Barrier > Change Settings > Hide on startup
- Set barrier to autostart when Desktop mode is active:
- System Settings > Startup and Shutdown > Autostart > + Add > + Add Application... > Utilities > Barrier
You should be good to go!
Automatically optimize cpu speed and power with auto-cpufreq: https://github.com/AdnanHodzic/auto-cpufreq
It remains to be seen if it really helps or does something on the Steam Deck.
The following steps will install auto-cpufreq into the home directory.
(Build steps taken from the AUR PKGBUILD: https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=auto-cpufreq)
curl https://github.com/AdnanHodzic/auto-cpufreq/archive/refs/tags/v1.9.3.tar.gz | tar -xzf -
cd auto-cpufreq-*
mkdir -p "$HOME/.local"
ln -s . "$HOME/.local/usr"
python -m ensurepip --upgrade
python -m pip install --upgrade pip
python -m pip install distro
sed -i 's|^\([^#].*\)/usr/\(local/\)\?|\1'"$HOME"'/.local/|g' auto_cpufreq/core.py
python setup.py build
python setup.py install --root="$HOME/.local" --optimize=1 --skip-build
install -Dm755 scripts/cpufreqctl.sh -t "$HOME/.local/share/auto-cpufreq/scripts"
Add export PATH="$PATH:$HOME/.local/bin"
to your ~/.bash_profile
.
Create a systemd user service that will activate auto-cpufreq: ~/.config/systemd/user/auto-cpufreq.service
(mkdir -p ~/.config/systemd/user ; vim ~/.config/systemd/user/auto-cpufreq.service
)
[Unit]
Description=auto-cpufreq - Automatic CPU speed & power optimizer for Linux
[Service]
Type=simple
ExecStart=sudo -E -n -- "%h/.local/bin/auto-cpufreq" --daemon
Restart=on-failure
[Install]
WantedBy=default.target
Make sure there's a ~/.config/environment.d/envvars.conf
with PATH="$PATH:$HOME/.local/bin"
in it.
Add a sudoers rule to launch auto-cpufreq without password (/etc/sudoers.d/zzz-auto-cpufreq
)
echo "$USER ALL=(ALL) NOPASSWD:SETENV: $HOME/.local/bin/auto-cpufreq *" | sudo tee -a /etc/sudoers.d/zzz-auto-cpufreq
sudo chmod 0440 /etc/sudoers.d/zzz-auto-cpufreq
Reload the user daemon
systemctl --user daemon-reload
You can enable auto-cpufreq to start on every boot:
systemctl --user enable --now auto-cpufreq.service
You can monitor the stats:
auto-cpufreq --stats
By default when the device is not charging auto-cpufreq will switch to the powersave
cpu governor which might affect games' performance.
You can force it to use the default schedutil
for both charging and on battery.
Create ~/.config/auto-cpufreq.conf
:
[charger]
governor = schedutil
[battery]
governor = schedutil
Change the service file ~/.config/systemd/user/auto-cpufreq.service
:
[Unit]
Description=auto-cpufreq - Automatic CPU speed & power optimizer for Linux
[Service]
Type=simple
ExecStart=sudo -E -n -- "%h/.local/bin/auto-cpufreq" --daemon --config "%h/.config/auto-cpufreq.conf"
Restart=on-failure
[Install]
WantedBy=default.target
Reload user daemon
systemctl --user daemon-reload
Restart the service
systemctl --user restart auto-cpufreq.service
You can install the wireguard tools locally like this:
sudo pacman --cachedir /tmp -Sw --noconfirm wireguard-tools
mkdir -p ~/.local/bin
tar -xf /tmp/wireguard-tools-*.pkg.tar.zst -C ~/.local/bin --strip-components=2 usr/bin
sudo rm -f /tmp/wireguard-tools*.pkg.*
Make sure there is a export PATH="$PATH:$HOME/.local/bin"
in your ~/.bash_profile
.
You can create /etc/wireguard
and copy you wireguard configs there.
wg-quick up <wgconf>
Network Manager also has wireguard support.
Helper script for creating a separate network namespace for your applications with a wireguard connection.
Save it as ~/.local/bin/wg-netns
and make it executable.
(vim ~/.local/bin/wg-netns ; chmod +x ~/.local/bin/wg-netns
)
#!/usr/bin/env bash
IFS=$'\n'
set -euo pipefail
set -x
export WG_ENDPOINT_RESOLUTION_RETRIES=infinity
WGCONF="$(sudo -u "${SUDO_USER:-${USER}}" -- cat "$2")"
WGDEV="$(basename "$2" .conf)"
WGPK="/tmp/$WGDEV-pk"
val_parse() {
sed -n 's/^'"$1"'\s*=\s*\(\S\+\)\s*$/\1/p' <<<"$WGCONF"
}
arr_parse() {
sed -n 's/^'"$1"'\s*=\s*\(\S.*\S\)\s*$/\1/p' <<<"$WGCONF" | sed 's/\s*,\s*/\n/g'
}
PrivateKey="$(val_parse PrivateKey)"
ListenPort="$(val_parse ListenPort)"
FwMark="$(val_parse FwMark)"
PublicKey="$(val_parse PublicKey)"
PresharedKey="$(val_parse PresharedKey)"
AllowedIPs=($(arr_parse AllowedIPs))
Endpoint="$(val_parse Endpoint)"
PersistentKeepalive="$(val_parse PersistentKeepalive)"
Addresses=($(arr_parse Address))
DNS=($(arr_parse DNS))
MTU="$(val_parse MTU)"
wg_set_opts=()
[[ -n "$ListenPort" ]] && wg_set_opts+=(listen-port "$ListenPort")
[[ -n "$FwMark" ]] && wg_set_opts+=(fwmark "$FwMark")
[[ -n "$PrivateKey" ]] && wg_set_opts+=(private-key "$WGPK")
[[ -n "$PublicKey" ]] && wg_set_opts+=(peer "$PublicKey")
[[ -n "$PresharedKey" ]] && wg_set_opts+=(preshared-key "$PresharedKey")
[[ -n "$Endpoint" ]] && wg_set_opts+=(endpoint "$Endpoint")
[[ -n "$PersistentKeepalive" ]] && wg_set_opts+=(persistent-keepalive "$PersistentKeepalive")
[[ "${#AllowedIPs[@]}" -gt 0 ]] && wg_set_opts+=(allowed-ips "$(IFS=, ; echo "${AllowedIPs[*]}")")
if [[ "$1" == "up" ]]
then
ip netns del "$WGDEV" &>/dev/null || true
ip netns add "$WGDEV"
ip -n "$WGDEV" link set lo up
ip link add dev "$WGDEV" type wireguard
ip link set "$WGDEV" netns "$WGDEV"
rm -f "$WGPK"
mkfifo -m 0600 "$WGPK"
echo "$PrivateKey" > "$WGPK" &
ip netns exec "$WGDEV" wg set "$WGDEV" "${wg_set_opts[@]}"
rm -f "$WGPK"
for a in "${Addresses[@]}" ; do ip -n "$WGDEV" addr add "$a" dev "$WGDEV" ; done
if [[ "${#DNS[@]}" -gt 0 ]]
then
mkdir -p "/etc/netns/$WGDEV"
truncate -s 0 "/etc/netns/$WGDEV/resolv.conf"
for d in "${DNS[@]}" ; do echo "nameserver $d" >> "/etc/netns/$WGDEV/resolv.conf" ; done
echo 'options edns0 single-request-reopen' >> "/etc/netns/$WGDEV/resolv.conf"
fi
[[ -n "$MTU" ]] && ip -n "$WGDEV" link set "$WGDEV" mtu "$MTU"
ip -n "$WGDEV" link set "$WGDEV" up
for a in "${AllowedIPs[@]}" ; do ip -n "$WGDEV" route add "$a" dev "$WGDEV" ; done
elif [[ "$1" == "down" ]]
then
ip netns del "$WGDEV" || true
rm -f "/etc/netns/$WGDEV/resolv.conf" || true
rmdir "/etc/netns/$WGDEV" || true
elif [[ "$1" == "exec" ]]
then
shift 2
exec sudo -E -- ip netns exec "$WGDEV" sudo -E -u "${SUDO_USER:-${USER}}" -- "$@"
fi
# Bring up wireguard conf in a network namespace of the same name
sudo wg-netns up <path/to/wgconf>
# Bring down network namespace
sudo wg-netns down <path/to/wgconf>
# Execute a command inside the network namespace
wg-netns exec <path/to/wgconf> firefox
# Join a network namespace using firejail
firejail --noprofile --netns=<wgconfname> firejail
I found a bug when using an ipv6 endpoint as soon as packets were sent, it would freeze the whole system and force a reboot after some time. Use an ipv4 endpoint for now. (last checked on kernel 5.13.0-valve10.3-1-neptune-02176-g5fe416c4acd8
)
You can get a portable AppImage of podman which works on SteamOS here: https://github.com/popsUlfr/podman-appimage/releases
It should make getting a development root much easier and robust and you can then make use of all the existing container images out there to get all sorts of software running.
Once you downloaded the podman-*.AppImage
there are various ways to launch it:
- In Dolphin
- Right-click the AppImage > Properties > Permissions > Check Is executable > Click OK
- Right-click the AppImage > Run In Konsole
- Open Konsole
- Navigate (
cd
) to the folder where the AppImage is stored chmod +x podman-*.AppImage
./podman-*.AppImage
- In Dolphin
- Right-click the folder where the AppImage is stored > Open Terminal / or hit Shift+F4
chmod +x podman-*.AppImage
./podman-*.AppImage
It will most likely prompt you to fix some things to get the rootless mode working, so make sure you set your user up with a password using passwd
beforehand.
It will open a podman-shell
that gives you access to the various podman commands, see: https://docs.podman.io/en/latest/Commands.html
You can also rename the AppImage or create a symlink to access a contained binary directly:
ln -s podman-*.AppImage podman
./podman info
ln -s podman-*.AppImage podman-remote
./podman-remote --url='ssh://root@localhost:22/run/podman/podman.sock' info
The awesome people over at SteamDeckHomebrew have already prepared docker images with rust, go toolchains and an accurate SteamOS holo image: https://github.com/orgs/SteamDeckHomebrew/packages?repo_name=holo-docker
./podman-*.AppImage
(deck@podman-shell:~) $ podman pull ghcr.io/steamdeckhomebrew/holo-base:latest
(deck@podman-shell:~) $ podman run -ti holo-base:latest /bin/bash
(root@<id> /)#
With this you should be good to go with your development needs on the Steam Deck.
Here's a manual way to get SteamOS image using the archlinux image as base:
./podman-*.AppImage
(deck@podman-shell:~) $ podman pull archlinux:latest
(deck@podman-shell:~) $ podman run -ti archlinux:latest
[root@<id> /]# echo 'Server = https://steamdeck-packages.steamos.cloud/archlinux-mirror/$repo/os/$arch' > /etc/pacman.d/mirrorlist
[root@<id> /]# sed -i 's|^\(\[core\]\)|[jupiter]\nInclude = /etc/pacman.d/mirrorlist\nSigLevel = Never\n\n[holo]\nInclude = /etc/pacman.d/mirrorlist\nSigLevel = Never\n\n\1|' /etc/pacman.conf
[root@<id> /]# pacman -Syy
[root@<id> /]# pacman-key --init
[root@<id> /]# pacman-key --populate archlinux
[root@<id> /]# pacman -S holo-keyring
[root@<id> /]# pacman -Rdd --noconfirm libverto
[root@<id> /]# pacman -Qqn | pacman -S --noconfirm --overwrite='*' -
[root@<id> /]# pacman -S --noconfirm --needed base-devel git sudo
[root@<id> /]# rm -rf /var/cache/pacman/pkg/*
[root@<id> /]# useradd -m deck
[root@<id> /]# echo 'deck ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/deck
This replaces all Arch packages with the SteamOS' mirror versions and installs development packages.
Finally an unprivileged deck
user is created.
You can be come this user immediately with:
[root@<id> /]# su - deck
[deck@<id> /]$
And sudo
is set up to do tasks as root.
You can exit the container:
[root@<id> /]# exit
Now create a new image from this container:
Query the container id with:
(deck@podman-shell:~) $ podman ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
<id> docker.io/archlinux/archlinux:latest /usr/bin/bash 29 minutes ago Exited (0) 2 minutes ago <name>
You can either use the <id>
or the <name>
to target a container. Now commit the container to a new image:
(deck@podman-shell:~) $ podman commit -s <id> holo:latest
This creates a new squashed image called holo
with tag latest
which you can see with podman images
.
Now you can run it like any image:
(deck@podman-shell:~) $ podman run -ti holo:latest
Mount the current directory at /tmp/out
in the image:
(deck@podman-shell:~) $ podman run -v ./:/tmp/out -ti holo:latest
Become immediately the deck
user:
(deck@podman-shell:~) $ podman run -v ./:/tmp/out -u deck -ti holo:latest
You can also resume a stopped container at any moment without needing to commit a new image:
(deck@podman-shell:~) $ podman start -ai <id>
DISCLAIMER: the following section describes the hard manual way to get a fake root.
Check out Use podman to create a SteamOS/Arch development image instead for an easier and more flexible solution using podman.
To install software beyond flatpaks, AppImages like system packages it is practical to create a custom rootfs to build new software.
The main rootfs already uses arch but it's not a good idea and doesn't work very well to use it to install missing system development tools and headers that are needed for building some packages.
For this we'll be using two main tools and avoid using any root privileges at all: fakeroot
and bwrap
(bubblewrap).
fakeroot
for faking root root privilegesbwrap
for changing the rootfs and using a separate user namespace
We fetch fakeroot
and put it into our local folders:
mkdir -p ~/.local/bin
sudo pacman --cachedir /tmp -Sw --noconfirm fakeroot
tar -xf /tmp/fakeroot-*.pkg.tar.zst -C ~/.local --strip-components=1 usr/bin usr/lib
sudo rm -f /tmp/fakeroot*.pkg.*
Make sure export PATH="$PATH:$HOME/.local/bin"
is set in ~/.bash_profile
or set it for the session.
We'll be using ~/.local/mnt/arch
as the dev rootfs but it can be anywhere.
export myroot="$HOME/.local/mnt/arch"
mkdir -p "$myroot"
We set up the needed directories for pacman:
mkdir -m 0755 -p "$myroot"/var/{cache/pacman/pkg,lib/pacman,log} "$myroot"/{dev,run,etc/pacman.d,etc/pacman.d/gnupg}
mkdir -m 1777 -p "$myroot"/tmp
mkdir -m 0555 -p "$myroot"/{sys,proc}
Now we invoke fakeroot
with the explicit libfakeroot.so
library and faked
binary locations and set up the keyrings ("$myroot"/arch.fakeroot
will save the real file attributes):
Important Note:
If you see any errors like
could not change the root directory (Operation not permitted)
error: command failed to execute correctly
do not worry, you can ignore them as they are expected during the pacman operations in the fakeroot environment.
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
pacman-key --gpgdir "$myroot"/etc/pacman.d/gnupg --init
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
pacman-key --gpgdir "$myroot"/etc/pacman.d/gnupg --populate archlinux
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
pacman-key --gpgdir "$myroot"/etc/pacman.d/gnupg --populate holo
Install the base
and base-devel
groups:
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
pacman --root "$myroot" --gpgdir "$myroot"/etc/pacman.d/gnupg -Sy --noconfirm base base-devel
Note: If you see bwrap: execvp fakeroot: No such file or directory
when attempting to run the above commands you may need to tweak /etc/pacman.conf
to replace the jupiter-beta
repository with jupiter
as follows:
sudo sed -i 's/^\[jupiter-beta\]/[jupiter]/' /etc/pacman.conf
Copy over pacman.conf
, mirrorlist
and makepkg.conf
:
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
cp -a /etc/pacman.conf "$myroot"/etc/pacman.conf
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
cp -a /etc/pacman.d/mirrorlist "$myroot"/etc/pacman.d/mirrorlist
fakeroot --lib "$HOME/.local/lib/libfakeroot/libfakeroot.so" \
--faked "$HOME/.local/bin/faked" \
-i "$myroot"/arch.fakeroot -s "$myroot"/arch.fakeroot \
cp -a /etc/makepkg.conf "$myroot"/etc/makepkg.conf
We can now enter the new root using bwrap, and we use the fakeroot from inside the root:
bwrap --bind "$myroot" / --dev /dev --proc /proc --ro-bind /sys /sys --tmpfs /tmp --unshare-all --share-net --ro-bind /etc/resolv.conf /etc/resolv.conf --clearenv fakeroot -i /arch.fakeroot -s /arch.fakeroot -- env HOME=/root USER=root bash
We are now inside the new root as the fake root user and first thing we need to set up the CA certificates otherwise we won't be able to download anything:
update-ca-trust
We set up an "unprivileged" user deck
(the root user right now is already unprivileged) for using makepkg
. useradd
will fail to populate /etc/passwd
and /etc/shadow
so we'll to do it by hand:
useradd -m deck
echo 'deck:x:1000:1000::/home/deck:/bin/bash' >> /etc/passwd
echo 'deck:*:14871::::::' >> /etc/shadow
You can become this new user by:
su - deck
You can quit this environment at any time using exit
.
To enter again it is:
bwrap --bind "$myroot" / --dev /dev --proc /proc --ro-bind /sys /sys --tmpfs /tmp --unshare-all --share-net --ro-bind /etc/resolv.conf /etc/resolv.conf --clearenv fakeroot -i /arch.fakeroot -s /arch.fakeroot -- env HOME=/root USER=root bash
The development rootfs is now usable.
You can use the camera of your smartphone as webcam using Droidcam if you don't have anything else on hand. Quite useful if on the go with only a steamdeck and a smartphone.
- https://github.com/dev47apps/droidcam
- https://play.google.com/store/apps/details?id=com.dev47apps.droidcam
- https://play.google.com/store/apps/details?id=com.dev47apps.droidcamx
I managed to create an AppImage for the Steam Deck for a very easy way to use DroidCam:
On first launch it might take a few seconds to start because of the depmod
of the kernel module.
First follow Create a SteamOS/Arch development root in your home folder and set up a development ready rootfs.
Enter the development root:
bwrap --bind "$myroot" / --dev /dev --proc /proc --ro-bind /sys /sys --tmpfs /tmp --unshare-all --share-net --ro-bind /etc/resolv.conf /etc/resolv.conf --clearenv fakeroot -i /arch.fakeroot -s /arch.fakeroot -- env HOME=/root USER=root bash
Install git and clone droidcam from the AUR:
pacman -Sy --noconfirm --needed git
su - deck -c 'git clone https://aur.archlinux.org/droidcam.git /home/deck/droidcam'
We'll install the build dependencies manually since sudo
won't work in this environment (setting a password manually in /etc/shadow
might work by using su
as fallback):
sed -n 's/makedepends=(\([^)]\+\))/\1/p' /home/deck/droidcam/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
Build the droidcam
packages as deck
user using makepkg
:
su - deck -c 'cd /home/deck/droidcam ; makepkg'
You can exit the dev environment now with exit
.
On the real system we'll make the rootfs read-write, set up the keyrings, install the kernel headers, runtime dependencies and finally install our newly built packages:
sudo steamos-readonly disable
sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo pacman-key --populate holo
sudo pacman -Sy --noconfirm --needed linux-neptune-headers libappindicator-gtk3
sudo pacman -U --noconfirm "$myroot"/home/deck/droidcam/*.pkg.tar.zst
sudo steamos-readonly enable
Load the needed kernel modules into memory:
xargs -a /etc/modules-load.d/droidcam.conf -n 1 sudo modprobe
Start droidcam
from your applications or terminal:
On your smartphone install either Droidcam or DroidcamX
Enter the ip address shown on the smartphone in the Droidcam client on the Steam Deck and connect. The webcam is now ready.
Each update of SteamOS will reset the droidcam install but you can reinstall it quickly by doing:
sudo steamos-readonly disable
sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo pacman-key --populate holo
sudo pacman -Sy --noconfirm --needed linux-neptune-headers libappindicator-gtk3
sudo pacman -U --noconfirm "$myroot"/home/deck/droidcam/*.pkg.tar.zst
sudo steamos-readonly enable
Launching Android apps is possible using Waydroid.
Here's a guide on how to get it up and running on the Steam Deck.
First follow Create a SteamOS/Arch development root in your home folder and set up a development ready rootfs.
Enter the development root:
bwrap --bind "$myroot" / --dev /dev --proc /proc --ro-bind /sys /sys --tmpfs /tmp --unshare-all --share-net --ro-bind /etc/resolv.conf /etc/resolv.conf --clearenv fakeroot -i /arch.fakeroot -s /arch.fakeroot -- env HOME=/root USER=root bash
Install git and clone anbox-modules-git, waydroid and all their dependencies from the AUR:
pacman -Sy --noconfirm --asdeps --needed git dkms linux-neptune-headers
su - deck -c 'git clone https://aur.archlinux.org/anbox-modules-dkms-git.git /home/deck/anbox-modules-dkms-git'
# Patch PKGBUILD to comment out a sed that makes it fail to compile on the valve kernel
sed -i 's/\b\(sed\s\+.*#if\s\+1.*binder\.c\s*\)$/#\1/' /home/deck/anbox-modules-dkms-git/PKGBUILD
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/anbox-modules-dkms-git/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/anbox-modules-dkms-git ; makepkg'
su - deck -c 'git clone https://aur.archlinux.org/libglibutil.git /home/deck/libglibutil'
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/libglibutil/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/libglibutil ; makepkg'
pacman -U --noconfirm --needed --asdeps /home/deck/libglibutil/*.pkg.tar.zst
su - deck -c 'git clone https://aur.archlinux.org/libgbinder.git /home/deck/libgbinder'
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/libgbinder/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/libgbinder ; makepkg'
pacman -U --noconfirm --needed --asdeps /home/deck/libgbinder/*.pkg.tar.zst
su - deck -c 'git clone https://aur.archlinux.org/python-gbinder.git /home/deck/python-gbinder'
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/python-gbinder/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/python-gbinder ; makepkg'
pacman -U --noconfirm --needed --asdeps /home/deck/python-gbinder/*.pkg.tar.zst
su - deck -c 'git clone https://aur.archlinux.org/waydroid.git /home/deck/waydroid'
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/waydroid/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/waydroid ; makepkg'
su - deck -c 'git clone https://aur.archlinux.org/python-pyclip.git /home/deck/python-pyclip'
sed -n 's/\b\(make\)\?depends=(\([^)]\+\))/\2/p' /home/deck/python-pyclip/PKGBUILD | xargs -n 1 pacman -S --noconfirm --asdeps --needed
su - deck -c 'cd /home/deck/python-pyclip ; makepkg'
mv /home/deck/libglibutil/*.pkg.tar.zst /home/deck/libgbinder/*.pkg.tar.zst /home/deck/python-gbinder/*.pkg.tar.zst /home/deck/python-pyclip/*.pkg.tar.zst /home/deck/waydroid/
Exit the dev environment with exit
.
In the real rootfs install the anbox modules:
sudo steamos-readonly disable
sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo pacman-key --populate holo
sudo pacman -Sy --noconfirm --needed linux-neptune-headers libappindicator-gtk3
sudo pacman -U --noconfirm "$myroot"/home/deck/anbox-modules-dkms-git/*.pkg.tar.zst
sudo pacman -U --noconfirm --asdeps "$myroot"/home/deck/waydroid/*.pkg.tar.zst
sudo pacman -D --asexplicit waydroid
sudo pacman -S --needed --noconfirm weston
sudo steamos-readonly enable
Load the modules:
xargs -a /usr/lib/modules-load.d/anbox-modules.conf -n 1 sudo modprobe
Offload the waydroid folder onto the home partition:
sudo cp -a /usr/lib/systemd/system/var-lib-docker.mount /etc/systemd/system/var-lib-waydroid.mount
sudo sed -i 's|/var/lib/docker|/var/lib/waydroid|g' /etc/systemd/system/var-lib-waydroid.mount
sudo systemctl daemon-reload
sudo systemctl enable --now var-lib-waydroid.mount
It may be needed to add psi=1
to the kernel command line (needs more testing)
sudo mount /dev/disk/by-partsets/self/efi /mnt
sudo sed -i 's/\b\(steamenv_boot\s\+.*\)$/\1 psi=1/g' /mnt/EFI/steamos/grub.cfg
sudo umount /mnt
Reboot.
Initialize waydroid by downloading the latest image:
sudo waydroid init -s GAPPS -f
Start the service:
sudo systemctl start waydroid-container.service
Make waydroid folder writable for everyone:
sudo chmod g+w,o+w /var/lib/waydroid
You'll need to disable hardware acceleration for it to work currently. Edit /var/lib/waydroid/waydroid_base.prop
:
ro.hardware.gralloc=default
ro.hardware.egl=swiftshader
Launch weston
, it will offer a little sandboxed wayland envrironment.
Open a terminal by clicking on the terminal icon in the upper left corner and set the XDG session:
export XDG_SESSION_TYPE=wayland
Launch the waydroid session and wait until you get Android with user 0 is ready
:
waydroid session start
In another terminal in weston
launch:
waydroid show-full-ui
At this point you should see the android home screen.
WARNING! Attempting to access the system settings currently freezes the Steam Deck and it eventually reboots.
Converting the ext4
formatted /home
partition to btrfs
can bring many advantages like transparent compression, instant snapshotting, deduplication, efficient backups, etc...
It also adds support to mount btrfs
and f2fs
formatted sd cards.
I've been daily driving it, switching branches and updating the system frequently in order to write up the guides around the Steam Deck all without issues.
The gamescope integrated fps limiter might now work as expected on flatpak apps (yuzu, bottles...).
Using mangohud
and setting vsync=0
(FIFO_RELAXED_KHR
) seems to do the trick.
Install the mangohud
flatpak:
flatpak install org.freedesktop.Platform.VulkanLayer.MangoHud
With flatseal (install via Discover or flatpak install com.github.tchx84.Flatseal
) you can set persistent environment variables.
Select your flatpak in flatseal
and under Environment
set the following environment variables:
MANGOHUD=1
MANGOHUD_CONFIG=no_display,vsync=0
If you are using Bottles, you can set per bottle environment variables.
Go into your bottle
and under Preferences > Environment variables
add the variables:
MANGOHUD=1
MANGOHUD_CONFIG=no_display,vsync=0
Sometimes it is useful to be able to quickly toggle the internal display in desktop mode. You can use the following script to toggle the Steam Deck's display:
#!/bin/bash
# Count of lines returned by xrandr --listactivemonitors
active=$(xrandr --listactivemonitors | grep -w 'eDP' -c)
# Screen resolution
resolution=800x1280
# Position relative to your external monitor(s)
# KDEs display settings can be used to get the correct position
position=626x1440
# Refresh rate
refreshrate=60
if (($active == 1)); then
echo "Turning OFF internal monitor!"
xrandr --output eDP --off
else
echo "Turning ON internal monitor!"
xrandr --output eDP --mode $resolution --rate $refreshrate --pos $position --rotate right
fi
Make sure to set the position to match your screen setup.
To define a shortcut open System Settings and navigate to the "Shortcuts" section as seen on the screenshot below. Set the action and the trigger and you're good to go.