Skip to content

Instantly share code, notes, and snippets.

@Kowalski7
Last active May 21, 2025 15:54
Show Gist options
  • Save Kowalski7/8fbff7a18f720794b816bd3a49d9d572 to your computer and use it in GitHub Desktop.
Save Kowalski7/8fbff7a18f720794b816bd3a49d9d572 to your computer and use it in GitHub Desktop.
An easy to use installer for Tailscale on portable game consoles running Knulli OS.
#!/bin/bash
SCRIPT_VERSION="1.3"
INSTALL_DIR="/userdata/system/tailscale"
UA_STRING="Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:40.0) Gecko/20100101 Firefox/40.0"
# Function to check device compatibility
function device_check {
# Get OS_NAME variable from /etc/os-release
local os_name=$(grep "^OS_NAME" /etc/os-release | awk -F'=' '{print $2}' | tr -d '"')
if [[ "$os_name" == "knulli" ]]; then
return 0
fi
local confirmation
clear
echo " __ ___ ____ _ _ ___ _ _ ____ _ "
echo " \ \ / / \ | _ \| \ | |_ _| \ | |/ ___| | |"
echo " \ \ /\ / / _ \ | |_) | \| || || \| | | _ | |"
echo " \ V V / ___ \| _ <| |\ || || |\ | |_| | |_|"
echo " \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| (_)"
echo " "
echo "This script was designed to be used on handheld game consoles running the "
echo "Knulli operating system. It appears your device is not running Knulli OS."
echo ""
echo "If you believe this is a mistake and wish to continue using this script,"
echo "type \"continue anyway\". To abort and exit the script, press Enter."
echo ""
echo -n "> "
read confirmation
if [[ "$confirmation" == "continue anyway" ]]; then
return 0
fi
echo -e "Aborted!\n"
exit 0
}
# Function to display an error message that can either be manually dismissed
# by pressing Enter, or automatically after a certain amount of time (in seconds)
# has passed (amount is given as the second argument in the function call).
#
# Usage: show_error <error_message_string> [timeout]
function show_error {
if [[ ! -z $1 ]]; then
echo -e "\nERROR: $1"
fi
if [[ $2 > 0 ]]; then
sleep $2
else
echo -e "\nPress Enter to return to the main menu..."
read
fi
return 0
}
# Function that creates a service file for the Tailscale daemon to run at system startup
function ts_create_service {
echo -n "Installing service... "
mkdir -p "/userdata/system/services"
cat <<EOF > "/userdata/system/services/tailscale"
#!/bin/sh
RETVAL=0
start() {
echo "Starting tailscaled service..."
start-stop-daemon -S -b -q -x ${INSTALL_DIR}/tailscaled -- --state=${INSTALL_DIR}/tailscaled.state
RETVAL=\$?
echo "Done!"
return \$RETVAL
}
stop() {
echo -n "Shutting down tailscaled service: "
start-stop-daemon -K -q -n tailscaled
RETVAL=\$?
echo "Done!"
return \$RETVAL
}
restart() {
stop
start
}
reload() {
echo -n "Reloading tailscaled service: "
kill -HUP \$(pidof tailscaled)
RETVAL=\$?
echo "Done!"
return \$RETVAL
}
case "\$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
reload)
reload
;;
*)
echo "Usage: \$0 {start|stop|restart|reload}"
exit 1
esac
EOF
if [[ $? -ne 0 ]]; then
echo "FAIL!"
show_error "Failed to create service file!"
return 5
else
echo "OK!"
fi
chmod +x "/userdata/system/services/tailscale"
return 0;
}
# Function to get the latest version of Tailscale for ARM64 from Tailscale's
# official package repository: https://pkgs.tailscale.com/stable/
function ts_get_latest_version {
# Fetch the HTML page that lists the stable packages
local url="https://pkgs.tailscale.com/stable/"
local page=$(curl -s "$url")
# Extract the version numbers from the links
local versions=$(echo "$page" | grep -oP 'tailscale_\d+\.\d+\.\d+_arm64\.tgz' | sed 's/tailscale_//g' | sed 's/_arm64.tgz//g')
# Find the latest version
local latest_version=$(echo "$versions" | sort -V | tail -n 1)
echo "$latest_version"
return 0
}
# Function to download the latest version of Tailscale and extract the package
# into the installation directory.
function ts_install_package {
echo -n "Downloading latest version... "
local latest_version=$(ts_get_latest_version)
local tmp_download="/tmp/tailscale_${latest_version}_arm64.tgz"
wget -q --referer "https://pkgs.tailscale.com/" --user-agent "${UA_STRING}" -O "${tmp_download}" "https://pkgs.tailscale.com/stable/tailscale_${latest_version}_arm64.tgz"
if [[ $? -ne 0 ]]; then
echo "FAIL!"
show_error "Failed to download the latest version of Tailscale!"
rm -f "${tmp_download}" # downloaded package file
return 2
else
echo "OK!"
fi
# Extract package contents in /tmp because it contains a directory named
# after the current version, which we don't want
echo -n "Extracting package... "
tar -xvzf "${tmp_download}" -C "/tmp/" > /dev/null
if [[ $? -ne 0 ]]; then
echo "FAIL!"
show_error "Failed to extract the package!"
rm -f "${tmp_download}" # downloaded package file
rm -rf "/tmp/tailscale_${latest_version}_arm64/" # extracted package contents
return 3
else
echo "OK!"
fi
# Copy extracted files from the package to the installation directory
echo -n "Installing package... "
mkdir -p "${INSTALL_DIR}"
rsync -aq --remove-source-files "/tmp/tailscale_${latest_version}_arm64/" "${INSTALL_DIR}/"
if [[ $? -ne 0 ]]; then
echo "FAIL!"
show_error "Failed to install the package!"
rm -f "${tmp_download}" # downloaded package file
rm -rf "/tmp/tailscale_${latest_version}_arm64/" # extracted package contents
rm -rf "${INSTALL_DIR}/" # installation directory
return 4
else
echo "OK!"
fi
# Clean up downloaded package after installation
echo -n "Cleaning up... "
rm -f "${tmp_download}"
if [[ $? -ne 0 ]]; then
echo "FAIL!"
else
echo "OK!"
fi
# Make the "tailscale" and "tailscaled" binaries executable
chmod +x "${INSTALL_DIR}/tailscale"
chmod +x "${INSTALL_DIR}/tailscaled"
}
# Menu option: Install Tailscale on this device
#
# This function starts the installation process for Tailscale and guides the user
# through the initial setup process.
function ts_install {
echo -n "Checking for existing Tailscale installation... "
if [[ -d "${INSTALL_DIR}" || -e "/userdata/system/services/tailscale" ]]; then
echo "FAIL!"
echo "Tailscale already appears to be installed! If you previously tried to "
echo "install Tailscale and the process was unsuccessful, first uninstall it "
echo "by choosing option 3 in the main menu, then retry the installation."
echo ""
echo "To update an existing installation, choose option 2 from the main menu."
echo ""
echo "Press Enter to return to the main menu..."
read
return 1
else
echo "OK!"
fi
# Call ts_install_package and abort installation on failure
ts_install_package
local ret=$?
if [[ $ret -ne 0 ]]; then
return $ret
fi
# Call ts_create_service and abort installation on failure
ts_create_service
local ret=$?
if [[ $ret -ne 0 ]]; then
rm -rf "${INSTALL_DIR}/"
return $ret
fi
echo ""
echo "Tailscale installed successfully!"
echo "The installation directory is \"${INSTALL_DIR}\"."
echo "If you need to make changes to your Tailscale configuration or reauthenticate, "
echo "you will need to navigate to this directory and run the \"tailscale\" binary."
# Setup: Step 1 - Service activation
echo ""
echo "On your console, open the Main Menu and navigate to System Settings > Services, "
echo "and enable \"tailscale\" from the list of services."
echo ""
echo -n "Once you're done, press Enter to continue to the setup..."
read
# Setup: Step 2 - Authenticate into Tailscale
echo ""
echo "A URL will be shown below that you need to visit in your browser in order to "
echo "add your game console to the list of devices in your Tailscale account. If you "
echo "don't already have an account, you can create one for free after visiting the "
echo "link."
${INSTALL_DIR}/tailscale up
if [[ $? -ne 0 ]]; then
echo "Something went wrong during the setup!"
echo "Please manually navigate to ${INSTALL_DIR} and run"
echo "\"./tailscale up\" to try authenticating again."
echo ""
echo "Press Enter to return to the main menu..."
read
return 6
else
echo "Setup complete!"
sleep 5
fi
return 0
}
# Menu option: Uninstall Tailscale
#
# This function uninstalls Tailscale from the device.
# NOTE: On failures, this function doesn't exit and proceeds regardless. This
# has been done in order to allow the user to clean up after broken
# installation attempts.
function ts_uninstall {
# Stop the Tailscale daemon service or kill its process
if [[ -e "/userdata/system/services/tailscale" ]]; then
/userdata/system/services/tailscale stop
if [[ $? -ne 0 ]]; then
echo -n "Killing tailscaled processes... "
killall -qw tailscaled
echo "OK!"
fi
else
echo -n "Killing tailscaled processes... "
killall -qw tailscaled
echo "OK!"
fi
# Delete service file from "/userdata/system/services/"
echo -n "Removing service file... "
rm -f "/userdata/system/services/tailscale"
if [[ $? -ne 0 ]]; then
echo "FAIL!"
else
echo "OK!"
fi
# Delete Tailscale's install folder from "/userdata/system/"
echo -n "Removing program files... "
rm -rf "${INSTALL_DIR}"
if [[ $? -ne 0 ]]; then
echo "FAIL!"
else
echo "OK!"
fi
echo ""
echo "Tailscale has been uninstalled!"
sleep 5
return 0
}
# Menu option: Update the existing Tailscale installation
#
# This function installs the latest version of Tailscale on top of the old
# one's files. This ensures that the session located in the installation
# folder is not lost and the user doesn't need to reauthenticate.
function ts_update {
echo -n "Checking for existing Tailscale installation... "
if [[ ! -d "${INSTALL_DIR}" || ! -e "/userdata/system/services/tailscale" ]]; then
echo "FAIL!"
echo "Tailscale does not appear to be installed! If you previously tried to "
echo "install Tailscale and the process was unsuccessful, first uninstall it "
echo "by choosing option 3 in the main menu, then retry the installation."
echo ""
echo "Press Enter to return to the main menu..."
read
return 1
else
echo "OK!"
fi
# Stop the Tailscale daemon service or kill its process
/userdata/system/services/tailscale stop
if [[ $? -ne 0 ]]; then
echo -n "Killing tailscaled processes... "
killall -qw tailscaled
echo "OK!"
fi
# Call ts_install_package and abort installation on failure
ts_install_package
local ret=$?
if [[ $ret -ne 0 ]]; then
return $ret
fi
# Restart the Tailscale daemon service
/userdata/system/services/tailscale start
echo ""
echo "Update complete!"
sleep 5
return 0
}
function main {
device_check
local choice
while true; do
clear
echo " _ __ _ _ _ _____ _ _ _ "
echo " | |/ /_ __ _ _| | (_) |_ _|_ _(_) |___ ___ __ _| | ___ "
echo " | ' /| '_ \| | | | | | | | |/ _\` | | / __|/ __/ _\` | |/ _ \\"
echo " | . \| | | | |_| | | | | | | (_| | | \__ \ (_| (_| | | __/"
echo " |_|\_\_| |_|\__,_|_|_|_| |_|\__,_|_|_|___/\___\__,_|_|\___|"
echo " ___ _ _ _ "
echo " |_ _|_ __ ___| |_ __ _| | | ___ _ __ "
echo " | || '_ \/ __| __/ _\` | | |/ _ \ '__| "
echo " | || | | \__ \ || (_| | | | __/ | "
echo " |___|_| |_|___/\__\__,_|_|_|\___|_| "
echo " "
echo "Developed by Kowalski7 Version: $SCRIPT_VERSION"
echo ""
echo "Choose an action to perform:"
echo "1. Install Tailscale on this device"
echo "2. Update the existing Tailscale installation"
echo "3. Uninstall Tailscale"
echo ""
echo "0. Exit"
echo ""
echo -n "> "
read choice
echo ""
case "$choice" in
1)
ts_install
;;
2)
ts_update
;;
3)
ts_uninstall
;;
0)
break
;;
*)
echo "Invalid option! Try again."
sleep 2
;;
esac
done
echo -e "Goodbye!\n"
exit 0
}
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment