1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
|
#!/bin/bash
# if a password was set by decky, this will run when the program closes
temp_pass_cleanup() {
echo $PASS | sudo -S -k passwd -d deck
}
# removes unhelpful GTK warnings
zen_nospam() {
zenity 2> >(grep -v 'Gtk' >&2) "$@"
}
# check if JQ is installed
if ! command -v jq &> /dev/null
then
echo "JQ could not be found, please install it"
echo "Info on how to install it can be found at https://stedolan.github.io/jq/download/"
exit 1
fi
# check if github.com is reachable
if ! curl -Is https://github.com | head -1 | grep 200 > /dev/null
then
echo "Github appears to be unreachable, you may not be connected to the internet"
exit 1
fi
# if the script is not root yet, get the password and rerun as root
if (( $EUID != 0 )); then
PASS_STATUS=$(passwd -S deck 2> /dev/null)
if [ "$PASS_STATUS" = "" ]; then
echo "Deck user not found. Continuing anyway, as it probably just means user is on a non-steamos system."
fi
if [ "${PASS_STATUS:5:2}" = "NP" ]; then # if no password is set
if ( zen_nospam --title="Decky Installer" --width=300 --height=200 --question --text="You appear to have not set an admin password.\nDecky can still install by temporarily setting your password to 'Decky!' and continuing, then removing it when the installer finishes\nAre you okay with that?" ); then
yes "Decky!" | passwd deck # set password to Decky!
trap temp_pass_cleanup EXIT # make sure that password is removed when application closes
PASS="Decky!"
else exit 1; fi
else
# get password
FINISHED="false"
while [ "$FINISHED" != "true" ]; do
PASS=$(zen_nospam --title="Decky Installer" --width=300 --height=100 --entry --hide-text --text="Enter your sudo/admin password")
if [[ $? -eq 1 ]] || [[ $? -eq 5 ]]; then
exit 1
fi
if ( echo "$PASS" | sudo -S -k true ); then
FINISHED="true"
else
zen_nospam --title="Decky Installer" --width=150 --height=40 --info --text "Incorrect Password"
fi
done
fi
if ! [ $USER = "deck" ]; then
zen_nospam --title="Decky Installer" --width=300 --height=100 --warning --text "You appear to not be on a deck.\nDecky should still mostly work, but you may not get full functionality."
fi
echo "$PASS" | sudo -S -k bash "$0" "$@" # rerun script as root
exit 1
fi
# all code below should be run as root
USER_DIR="$(getent passwd $SUDO_USER | cut -d: -f6)"
HOMEBREW_FOLDER="${USER_DIR}/homebrew"
# if decky is already installed, then add 'uninstall' and 'wipe' option
if [[ -f "${USER_DIR}/homebrew/services/PluginLoader" ]] ; then
OPTION=$(zen_nospam --title="Decky Installer" --width=420 --height=200 --list --radiolist --text "Select Option:" --hide-header --column "Buttons" --column "Choice" --column "Info" \
TRUE "update to latest release" "Recommended option" \
FALSE "update to latest prerelease" "May be unstable" \
FALSE "uninstall decky loader" "Will keep config intact" \
FALSE "wipe decky loader" "Will NOT keep config intact")
else
OPTION=$(zen_nospam --title="Decky Installer" --width=300 --height=100 --list --radiolist --text "Select branch to install:" --hide-header --column "Buttons" --column "Choice" --column "Info" \
TRUE "release" "(Recommended option)" \
FALSE "prerelease" "(May be unstable)")
fi
if [[ $? -eq 1 ]] || [[ $? -eq 5 ]]; then
exit 1
fi
# uninstall if uninstall option was selected
if [[ "$OPTION" == "uninstall decky loader" || "$OPTION" == "wipe decky loader" ]] ; then
(
echo "20" ; echo "# Disabling and removing services" ;
sudo systemctl disable --now plugin_loader.service > /dev/null
sudo rm -f "${USER_DIR}/.config/systemd/user/plugin_loader.service"
sudo rm -f "/etc/systemd/system/plugin_loader.service"
echo "40" ; echo "# Removing Temporary Files" ;
rm -rf "/tmp/plugin_loader"
rm -rf "/tmp/user_install_script.sh"
if [ "$OPTION" == "wipe decky loader" ]; then
echo "60" ; echo "# Deleting homebrew folder" ;
sudo rm -r "${HOMEBREW_FOLDER}"
else
echo "60" ; echo "# Cleaning services folder" ;
sudo rm "${HOMEBREW_FOLDER}/services/PluginLoader"
fi
echo "80" ; echo "# Disabling CEF debugging" ;
sudo rm "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
sudo rm "${USER_DIR}/.var/app/com.valvesoftware.Steam/data/Steam/.cef-enable-remote-debugging" 2> /dev/null
echo "100" ; echo "# Uninstall finished, installer can now be closed";
) |
zen_nospam --progress \
--title="Decky Installer" \
--width=300 --height=100 \
--text="Uninstalling..." \
--percentage=0 \
--no-cancel
exit 0
fi
# otherwise, install decky
if [[ "$OPTION" =~ "pre" ]]; then
BRANCH="prerelease"
else
BRANCH="release"
fi
(
echo "15" ; echo "# Creating file structure" ;
rm -rf "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/services"
sudo -u $SUDO_USER mkdir -p "${HOMEBREW_FOLDER}/plugins"
sudo -u $SUDO_USER touch "${USER_DIR}/.steam/steam/.cef-enable-remote-debugging"
# if installed as flatpak, put .cef-enable-remote-debugging there
[ -d "${USER_DIR}/.var/app/com.valvesoftware.Steam/data/Steam/" ] && sudo -u $SUDO_USER touch "${USER_DIR}/.var/app/com.valvesoftware.Steam/data/Steam/.cef-enable-remote-debugging"
echo "30" ; echo "# Finding latest $BRANCH";
if [ "$BRANCH" = 'prerelease' ] ; then
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))")
else
RELEASE=$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "false"))")
fi
VERSION=$(jq -r '.tag_name' <<< ${RELEASE} )
DOWNLOADURL=$(jq -r '.assets[].browser_download_url | select(endswith("PluginLoader"))' <<< ${RELEASE})
echo "45" ; echo "# Installing version $VERSION" ;
# make another zenity prompt while downloading the PluginLoader file, I do not know how this works
curl -L $DOWNLOADURL -o ${HOMEBREW_FOLDER}/services/PluginLoader 2>&1 | stdbuf -oL tr '\r' '\n' | sed -u 's/^ *\([0-9][0-9]*\).*\( [0-9].*$\)/\1\n#Download Speed\:\2/' | zen_nospam --progress --title "Downloading Decky" --text="Download Speed: 0" --width=300 --height=100 --auto-close --no-cancel
chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader
echo "60"; echo "Running SELinux fix if it is enabled"
hash getenforce 2>/dev/null && getenforce | grep "Enforcing" >/dev/null && chcon -t bin_t ${HOMEBREW_FOLDER}/services/PluginLoader
echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version
echo "70" ; echo "# Kiling plugin_loader if it exists" ;
systemctl --user stop plugin_loader 2> /dev/null
systemctl --user disable plugin_loader 2> /dev/null
systemctl stop plugin_loader 2> /dev/null
systemctl disable plugin_loader 2> /dev/null
echo "85" ; echo "# Setting up systemd" ;
curl -L https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-${BRANCH}.service --output ${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service
cat > "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" <<- EOM
[Unit]
Description=SteamDeck Plugin Loader
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Restart=always
ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader
WorkingDirectory=${HOMEBREW_FOLDER}/services
KillSignal=SIGKILL
Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins
Environment=LOG_LEVEL=INFO
[Install]
WantedBy=multi-user.target
EOM
# if .service file doesn't exist for whatever reason, use backup file instead
if [[ -f "${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service" ]]; then
printf "Grabbed latest ${BRANCH} service.\n"
sed -i -e "s|\${HOMEBREW_FOLDER}|${HOMEBREW_FOLDER}|" "${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service"
cp -f "${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service" "/etc/systemd/system/plugin_loader.service"
else
printf "Could not curl latest ${BRANCH} systemd service, using built-in service as a backup!\n"
rm -f "/etc/systemd/system/plugin_loader.service"
cp "${HOMEBREW_FOLDER}/services/plugin_loader-backup.service" "/etc/systemd/system/plugin_loader.service"
fi
mkdir -p ${HOMEBREW_FOLDER}/services/.systemd
cp ${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-${BRANCH}.service
cp ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/.systemd/plugin_loader-backup.service
rm ${HOMEBREW_FOLDER}/services/plugin_loader-backup.service ${HOMEBREW_FOLDER}/services/plugin_loader-${BRANCH}.service
systemctl daemon-reload
systemctl start plugin_loader
systemctl enable plugin_loader
# this (retroactively) fixes a bug where users who ran the installer would have homebrew owned by root instead of their user
# will likely be removed at some point in the future
if [ "$SUDO_USER" = "deck" ]; then
sudo chown -R deck:deck "${HOMEBREW_FOLDER}"
sudo chown -R root:root "${HOMEBREW_FOLDER}"/services/*
fi
echo "100" ; echo "# Install finished, installer can now be closed";
) |
zen_nospam --progress \
--title="Decky Installer" \
--width=300 --height=100 \
--text="Installing..." \
--percentage=0 \
--no-cancel # not actually sure how to make the cancel work properly, so it's just not there unless someone else can figure it out
if [ "$?" = -1 ] ; then
zen_nospam --title="Decky Installer" --width=150 --height=70 --error --text="Download interrupted."
fi
|