Files
configure-server/.includes/topspeed.sh
2026-04-18 14:46:01 -04:00

358 lines
10 KiB
Bash

#!/usr/bin/env bash
topspeedUser="topspeed"
topspeedHome="/var/lib/topspeed"
topspeedInstallDir="${topspeedHome}/server"
topspeedRuntimeDir="${topspeedInstallDir}/topspeed"
topspeedServiceFile="/etc/systemd/system/topspeed.service"
topspeedServiceScript="${topspeedInstallDir}/topspeed-service.sh"
topspeedStopScript="${topspeedInstallDir}/topspeed-stop.sh"
topspeedBinaryPath="${topspeedRuntimeDir}/TopSpeed.Server"
topspeedSessionName="topspeed"
topspeedRepoApi="https://api.github.com/repos/diamondStar35/top_speed/releases"
topspeedAssetPattern='^TopSpeed\.Server-linux-arm64-Release-v-.*\.zip$'
topspeedServerPort="28630"
topspeedDiscoveryPort="28631"
package_installed() {
local packageName="$1"
pacman -Q "$packageName" &> /dev/null
}
ufw_installed() {
pacman -Q ufw &> /dev/null
}
ensure_topspeed_dependencies() {
local packagesToInstall=()
local packageName=""
for packageName in curl jq tmux unzip; do
if ! package_installed "$packageName"; then
packagesToInstall+=("$packageName")
fi
done
if [[ "${#packagesToInstall[@]}" -gt 0 ]]; then
if ! install_package "${packagesToInstall[@]}"; then
msgbox "Failed to install the required Top Speed dependencies."
return 1
fi
fi
return 0
}
fetch_topspeed_asset() {
local tempDir=""
local metadataFile=""
local assetName=""
local assetUrl=""
tempDir="$(mktemp -d)"
metadataFile="${tempDir}/topspeed-release.json"
if ! curl -fsSL -H "Accept: application/vnd.github+json" "$topspeedRepoApi" -o "$metadataFile"; then
rm -rf "$tempDir"
msgbox "Failed to fetch Top Speed release metadata from GitHub."
return 1
fi
assetName="$(jq -r --arg assetPattern "$topspeedAssetPattern" '
([.[] | .assets[] | select(.name | test($assetPattern))] | .[0].name) // empty
' "$metadataFile")"
assetUrl="$(jq -r --arg assetPattern "$topspeedAssetPattern" '
([.[] | .assets[] | select(.name | test($assetPattern))] | .[0].browser_download_url) // empty
' "$metadataFile")"
if [[ -z "$assetName" || -z "$assetUrl" || "$assetName" == "null" || "$assetUrl" == "null" ]]; then
rm -rf "$tempDir"
msgbox "Unable to find the latest Top Speed arm64 server download in GitHub release metadata."
return 1
fi
if ! curl -fL --retry 5 -o "${tempDir}/${assetName}" "$assetUrl"; then
rm -rf "$tempDir"
msgbox "Failed to download the Top Speed server archive."
return 1
fi
printf '%s\n' "${tempDir}/${assetName}"
}
ensure_topspeed_user() {
if id -u "$topspeedUser" &> /dev/null; then
return 0
fi
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" useradd \
--system \
--home-dir "$topspeedHome" \
--create-home \
--shell /usr/bin/nologin \
"$topspeedUser"; then
msgbox "Failed to create the topspeed service user."
return 1
fi
return 0
}
stop_topspeed_service_if_present() {
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if sudo "${sudoFlags[@]}" systemctl cat topspeed.service &> /dev/null; then
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" systemctl stop topspeed.service &> /dev/null || true
fi
}
install_topspeed_server() {
local archivePath=""
local archiveDir=""
archivePath="$(fetch_topspeed_asset)" || return 1
archiveDir="$(dirname "$archivePath")"
stop_topspeed_service_if_present
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -d -o "$topspeedUser" -g "$topspeedUser" -m 755 "$topspeedRuntimeDir"; then
rm -rf "$archiveDir"
msgbox "Failed to create the Top Speed install directory."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" unzip -o -d "$topspeedRuntimeDir" "$archivePath"; then
rm -rf "$archiveDir"
msgbox "Failed to extract the Top Speed server archive."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" chown -R "${topspeedUser}:${topspeedUser}" "$topspeedHome"; then
rm -rf "$archiveDir"
msgbox "Failed to set ownership on the Top Speed files."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" test -f "$topspeedBinaryPath"; then
rm -rf "$archiveDir"
msgbox "The Top Speed server archive did not contain ${topspeedBinaryPath}."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" chmod 755 "$topspeedBinaryPath"; then
rm -rf "$archiveDir"
msgbox "Failed to make ${topspeedBinaryPath} executable."
return 1
fi
rm -rf "$archiveDir"
return 0
}
write_topspeed_runtime_scripts() {
local tempServiceScript=""
local tempStopScript=""
tempServiceScript="$(mktemp)"
tempStopScript="$(mktemp)"
cat > "$tempServiceScript" <<EOF
#!/usr/bin/env bash
sessionName="${topspeedSessionName}"
runtimeDir="${topspeedRuntimeDir}"
binaryPath="${topspeedBinaryPath}"
cd "\$runtimeDir" || exit 1
if tmux has-session -t "\$sessionName" &> /dev/null; then
tmux kill-session -t "\$sessionName"
fi
if [[ ! -x "\$binaryPath" ]]; then
exit 1
fi
tmux new-session -d -s "\$sessionName" /bin/bash -lc 'exec ./TopSpeed.Server' || exit 1
while tmux has-session -t "\$sessionName" &> /dev/null; do
sleep 5
done
exit 1
EOF
cat > "$tempStopScript" <<EOF
#!/usr/bin/env bash
sessionName="${topspeedSessionName}"
if tmux has-session -t "\$sessionName" &> /dev/null; then
tmux send-keys -t "\$sessionName" C-c
sleep 2
if tmux has-session -t "\$sessionName" &> /dev/null; then
tmux kill-session -t "\$sessionName"
fi
fi
EOF
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -o "$topspeedUser" -g "$topspeedUser" -m 755 "$tempServiceScript" "$topspeedServiceScript"; then
rm -f "$tempServiceScript" "$tempStopScript"
msgbox "Failed to install the Top Speed runtime script."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -o "$topspeedUser" -g "$topspeedUser" -m 755 "$tempStopScript" "$topspeedStopScript"; then
rm -f "$tempServiceScript" "$tempStopScript"
msgbox "Failed to install the Top Speed stop script."
return 1
fi
rm -f "$tempServiceScript" "$tempStopScript"
return 0
}
write_topspeed_service() {
local tempServiceFile=""
tempServiceFile="$(mktemp)"
cat > "$tempServiceFile" <<EOF
[Unit]
Description=Top Speed Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${topspeedUser}
Group=${topspeedUser}
WorkingDirectory=${topspeedRuntimeDir}
ExecStart=${topspeedServiceScript}
ExecStop=${topspeedStopScript}
Restart=on-failure
RestartSec=5
TimeoutStopSec=15
[Install]
WantedBy=multi-user.target
EOF
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempServiceFile" "$topspeedServiceFile"; then
rm -f "$tempServiceFile"
msgbox "Failed to write the Top Speed systemd service file."
return 1
fi
rm -f "$tempServiceFile"
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" systemctl daemon-reload; then
msgbox "Failed to reload systemd after writing the Top Speed service."
return 1
fi
return 0
}
configure_topspeed_firewall() {
if ! ufw_installed; then
return 0
fi
if [[ "$(yesno "ufw is installed. Open the default Top Speed ports ${topspeedServerPort} and ${topspeedDiscoveryPort} now?")" != "Yes" ]]; then
return 0
fi
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw allow "${topspeedServerPort}/udp"; then
msgbox "Failed to allow the Top Speed server port ${topspeedServerPort}/udp."
return 1
fi
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" ufw allow "${topspeedDiscoveryPort}/udp"; then
msgbox "Failed to allow the Top Speed discovery port ${topspeedDiscoveryPort}/udp."
return 1
fi
msgbox "The default Top Speed firewall rules were added."
return 0
}
enable_topspeed_service() {
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
if ! sudo "${sudoFlags[@]}" systemctl enable --now topspeed.service; then
msgbox "Top Speed was installed, but the service failed to enable or start."
return 1
fi
return 0
}
install_topspeed_flow() {
ensure_topspeed_dependencies || return 1
ensure_topspeed_user || return 1
install_topspeed_server || return 1
write_topspeed_runtime_scripts || return 1
write_topspeed_service || return 1
enable_topspeed_service || return 1
configure_topspeed_firewall || return 1
msgbox "Top Speed Server is installed. The service is enabled and started, and the console is available through the Top Speed Console menu entry."
return 0
}
topspeed_session_running() {
if ! id -u "$topspeedUser" &> /dev/null; then
return 1
fi
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" -u "$topspeedUser" tmux has-session -t "$topspeedSessionName" &> /dev/null
}
attach_topspeed_console() {
local attachStatus=0
if ! topspeed_session_running; then
msgbox "Top Speed is not running."
return 1
fi
clear
echo "Attaching to the Top Speed console."
echo "To detach from the server and leave it running, press Ctrl+b followed by d."
# `sudoFlags` is initialized by the main launcher before sourcing this file.
# shellcheck disable=SC2154
sudo "${sudoFlags[@]}" -u "$topspeedUser" tmux attach-session -t "$topspeedSessionName"
attachStatus=$?
reset
return "$attachStatus"
}
case "${1:-install}" in
install)
install_topspeed_flow
;;
console)
attach_topspeed_console
;;
*)
msgbox "Unknown Top Speed action: ${1}"
return 1
;;
esac