Initial commit.
This commit is contained in:
454
.includes/copyparty.sh
Normal file
454
.includes/copyparty.sh
Normal file
@@ -0,0 +1,454 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
copypartyConfigFile="/etc/copyparty/copyparty.conf"
|
||||
copypartyPort="3923"
|
||||
|
||||
copyparty_installed() {
|
||||
pacman -Q copyparty &> /dev/null
|
||||
}
|
||||
|
||||
ufw_installed() {
|
||||
pacman -Q ufw &> /dev/null
|
||||
}
|
||||
|
||||
valid_ipv4_subnet() {
|
||||
local subnetValue="$1"
|
||||
|
||||
[[ "$subnetValue" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] || return 1
|
||||
awk -F '[./]' '
|
||||
{
|
||||
for (index = 1; index <= 4; index++) {
|
||||
if ($index < 0 || $index > 255) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
' <<< "$subnetValue"
|
||||
}
|
||||
|
||||
detect_private_subnets() {
|
||||
ip -o -4 addr show up scope global | awk '
|
||||
{
|
||||
split($4, parts, "/")
|
||||
split(parts[1], octets, ".")
|
||||
|
||||
if (octets[1] == 10) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else if (octets[1] == 192 && octets[2] == 168) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else if (octets[1] == 172 && octets[2] >= 16 && octets[2] <= 31) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else {
|
||||
next
|
||||
}
|
||||
|
||||
if (!seen[subnet]++) {
|
||||
print subnet
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
choose_lan_subnet() {
|
||||
local detectedSubnet=""
|
||||
local subnetChoice=""
|
||||
|
||||
detectedSubnet="$(detect_private_subnets | head -n 1)"
|
||||
subnetChoice="$(inputbox "Confirm the LAN subnet for Copyparty access." "${detectedSubnet:-192.168.1.0/24}")" || return 1
|
||||
if valid_ipv4_subnet "$subnetChoice"; then
|
||||
printf '%s\n' "$subnetChoice"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "Enter a valid IPv4 subnet such as 192.168.1.0/24."
|
||||
return 1
|
||||
}
|
||||
|
||||
install_copyparty() {
|
||||
local packageList=(
|
||||
copyparty
|
||||
ffmpeg
|
||||
cfssl
|
||||
python-mutagen
|
||||
python-pillow
|
||||
python-pyvips
|
||||
libkeyfinder
|
||||
python-pyopenssl
|
||||
python-pyzmq
|
||||
python-argon2-cffi
|
||||
)
|
||||
|
||||
if copyparty_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! install_package "${packageList[@]}"; then
|
||||
msgbox "Failed to install Copyparty and its optional feature packages."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "Copyparty and its optional feature packages are installed."
|
||||
return 0
|
||||
}
|
||||
|
||||
list_local_users() {
|
||||
awk -F ':' '
|
||||
$3 >= 1000 && $3 < 60000 && $7 !~ /(nologin|false)$/ {
|
||||
print $1
|
||||
}
|
||||
' /etc/passwd | sort
|
||||
}
|
||||
|
||||
choose_copyparty_user() {
|
||||
local userEntries=()
|
||||
local userName=""
|
||||
local selectedUser=""
|
||||
|
||||
while IFS= read -r userName; do
|
||||
[[ -n "$userName" ]] && userEntries+=("$userName")
|
||||
done < <(list_local_users)
|
||||
|
||||
if [[ "${#userEntries[@]}" -eq 0 ]]; then
|
||||
msgbox "No regular local users were found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
selectedUser="$(menulist "${userEntries[@]}")" || return 1
|
||||
printf '%s\n' "$selectedUser"
|
||||
return 0
|
||||
}
|
||||
|
||||
account_name_exists() {
|
||||
local accountsFile="$1"
|
||||
local username="$2"
|
||||
|
||||
awk -F ':' -v username="$username" '
|
||||
/^[[:space:]]+[A-Za-z0-9._-]+:/ {
|
||||
accountName = $1
|
||||
gsub(/^[[:space:]]+/, "", accountName)
|
||||
if (accountName == username) {
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
END {
|
||||
exit 1
|
||||
}
|
||||
' "$accountsFile"
|
||||
}
|
||||
|
||||
collect_copyparty_accounts() {
|
||||
local tempAccountsFile=""
|
||||
local username=""
|
||||
local password=""
|
||||
local addAnother="No"
|
||||
|
||||
tempAccountsFile="$(mktemp)"
|
||||
while true; do
|
||||
username="$(inputbox "Enter a Copyparty username.")" || {
|
||||
rm -f "$tempAccountsFile"
|
||||
return 1
|
||||
}
|
||||
if [[ -z "$username" || "$username" =~ [[:space:]:] ]]; then
|
||||
msgbox "Usernames cannot be blank and cannot contain spaces or colons."
|
||||
continue
|
||||
fi
|
||||
if account_name_exists "$tempAccountsFile" "$username"; then
|
||||
msgbox "That Copyparty username already exists in this configuration."
|
||||
continue
|
||||
fi
|
||||
|
||||
password="$(passwordbox "Enter the password for ${username}.")" || {
|
||||
rm -f "$tempAccountsFile"
|
||||
return 1
|
||||
}
|
||||
if [[ -z "$password" ]]; then
|
||||
msgbox "Passwords cannot be blank."
|
||||
continue
|
||||
fi
|
||||
|
||||
printf ' %s: %s\n' "$username" "$password" >> "$tempAccountsFile"
|
||||
addAnother="$(yesno "Add another Copyparty account?")"
|
||||
if [[ "$addAnother" != "Yes" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%s\n' "$tempAccountsFile"
|
||||
return 0
|
||||
}
|
||||
|
||||
choose_share_mode() {
|
||||
local shareMode=""
|
||||
|
||||
shareMode="$(menulist "Private read-write" "Private read-only" "Public read-only")" || return 1
|
||||
printf '%s\n' "$shareMode"
|
||||
return 0
|
||||
}
|
||||
|
||||
append_share_config() {
|
||||
local outputFile="$1"
|
||||
local sharePath="$2"
|
||||
local urlPath="$3"
|
||||
local shareMode="$4"
|
||||
local usernamesCsv="$5"
|
||||
|
||||
{
|
||||
printf '\n[%s]\n' "$urlPath"
|
||||
printf ' %s\n' "$sharePath"
|
||||
printf ' accs:\n'
|
||||
case "$shareMode" in
|
||||
"Private read-write")
|
||||
printf ' rwmd: %s\n' "$usernamesCsv"
|
||||
printf ' flags:\n'
|
||||
printf ' daw\n'
|
||||
;;
|
||||
"Private read-only")
|
||||
printf ' r: %s\n' "$usernamesCsv"
|
||||
;;
|
||||
"Public read-only")
|
||||
printf ' r: *\n'
|
||||
;;
|
||||
esac
|
||||
} >> "$outputFile"
|
||||
}
|
||||
|
||||
share_url_exists() {
|
||||
local sharesFile="$1"
|
||||
local urlPath="$2"
|
||||
|
||||
awk -v urlPath="$urlPath" '
|
||||
/^\[[^]]+\]$/ {
|
||||
sectionName = $0
|
||||
sub(/^\[/, "", sectionName)
|
||||
sub(/\]$/, "", sectionName)
|
||||
if (sectionName == urlPath) {
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
END {
|
||||
exit 1
|
||||
}
|
||||
' "$sharesFile"
|
||||
}
|
||||
|
||||
service_user_can_read_share() {
|
||||
local serviceUser="$1"
|
||||
local sharePath="$2"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" -u "$serviceUser" test -r "$sharePath" &&
|
||||
sudo "${sudoFlags[@]}" -u "$serviceUser" test -x "$sharePath"
|
||||
}
|
||||
|
||||
service_user_can_write_share() {
|
||||
local serviceUser="$1"
|
||||
local sharePath="$2"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" -u "$serviceUser" test -w "$sharePath" &&
|
||||
sudo "${sudoFlags[@]}" -u "$serviceUser" test -x "$sharePath"
|
||||
}
|
||||
|
||||
collect_copyparty_shares() {
|
||||
local usernamesCsv="$1"
|
||||
local serviceUser="$2"
|
||||
local tempSharesFile=""
|
||||
local sharePath=""
|
||||
local urlPath=""
|
||||
local shareMode=""
|
||||
local addAnother="No"
|
||||
|
||||
tempSharesFile="$(mktemp)"
|
||||
while true; do
|
||||
sharePath="$(inputbox "Enter the local path to share.")" || {
|
||||
rm -f "$tempSharesFile"
|
||||
return 1
|
||||
}
|
||||
if [[ -z "$sharePath" ]]; then
|
||||
msgbox "A local path is required."
|
||||
continue
|
||||
fi
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" test -d "$sharePath"; then
|
||||
msgbox "The path ${sharePath} does not exist or is not a directory."
|
||||
continue
|
||||
fi
|
||||
if ! service_user_can_read_share "$serviceUser" "$sharePath"; then
|
||||
msgbox "The selected service user ${serviceUser} cannot read ${sharePath}. Adjust permissions or choose another path."
|
||||
continue
|
||||
fi
|
||||
|
||||
urlPath="$(inputbox "Enter the Copyparty URL path for this share, for example /files.")" "/files" || {
|
||||
rm -f "$tempSharesFile"
|
||||
return 1
|
||||
}
|
||||
if [[ ! "$urlPath" =~ ^/[^[:space:]]*$ ]]; then
|
||||
msgbox "Enter a URL path that starts with / and contains no spaces."
|
||||
continue
|
||||
fi
|
||||
if share_url_exists "$tempSharesFile" "$urlPath"; then
|
||||
msgbox "That Copyparty URL path is already defined in this configuration."
|
||||
continue
|
||||
fi
|
||||
|
||||
shareMode="$(choose_share_mode)" || {
|
||||
rm -f "$tempSharesFile"
|
||||
return 1
|
||||
}
|
||||
if [[ "$shareMode" == "Private read-write" ]] &&
|
||||
! service_user_can_write_share "$serviceUser" "$sharePath"; then
|
||||
msgbox "The selected service user ${serviceUser} cannot write to ${sharePath}. Choose another path or adjust permissions."
|
||||
continue
|
||||
fi
|
||||
append_share_config "$tempSharesFile" "$sharePath" "$urlPath" "$shareMode" "$usernamesCsv"
|
||||
addAnother="$(yesno "Add another Copyparty share?")"
|
||||
if [[ "$addAnother" != "Yes" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%s\n' "$tempSharesFile"
|
||||
return 0
|
||||
}
|
||||
|
||||
join_account_names() {
|
||||
local accountsFile="$1"
|
||||
|
||||
awk -F ':' '
|
||||
BEGIN {
|
||||
first = 1
|
||||
}
|
||||
/^[[:space:]]+[A-Za-z0-9._-]+:/ {
|
||||
gsub(/^[[:space:]]+/, "", $1)
|
||||
if (!first) {
|
||||
printf ", "
|
||||
}
|
||||
printf "%s", $1
|
||||
first = 0
|
||||
}
|
||||
END {
|
||||
printf "\n"
|
||||
}
|
||||
' "$accountsFile"
|
||||
}
|
||||
|
||||
write_copyparty_config() {
|
||||
local serviceUser="$1"
|
||||
local accountsFile="$2"
|
||||
local sharesFile="$3"
|
||||
local tempConfigFile=""
|
||||
local serviceGroup=""
|
||||
|
||||
tempConfigFile="$(mktemp)"
|
||||
serviceGroup="$(id -gn "$serviceUser")"
|
||||
{
|
||||
printf '[global]\n'
|
||||
printf ' i: 0.0.0.0\n'
|
||||
printf ' p: %s\n' "$copypartyPort"
|
||||
printf ' e2dsa\n'
|
||||
printf '\n[accounts]\n'
|
||||
cat "$accountsFile"
|
||||
cat "$sharesFile"
|
||||
} > "$tempConfigFile"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -d -m 755 /etc/copyparty; then
|
||||
rm -f "$tempConfigFile"
|
||||
msgbox "Failed to create /etc/copyparty."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -o root -g "$serviceGroup" -m 640 "$tempConfigFile" "$copypartyConfigFile"; then
|
||||
rm -f "$tempConfigFile"
|
||||
msgbox "Failed to write ${copypartyConfigFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$tempConfigFile"
|
||||
return 0
|
||||
}
|
||||
|
||||
enable_copyparty_service() {
|
||||
local serviceUser="$1"
|
||||
local unitName="copyparty@${serviceUser}"
|
||||
local configuredUnit=""
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
while IFS= read -r configuredUnit; do
|
||||
[[ -z "$configuredUnit" || "$configuredUnit" == "$unitName" ]] && continue
|
||||
sudo "${sudoFlags[@]}" systemctl disable --now "$configuredUnit" &> /dev/null || true
|
||||
done < <(
|
||||
sudo "${sudoFlags[@]}" find /etc/systemd/system -type l -name 'copyparty@*.service' -printf '%f\n' 2> /dev/null | sort -u
|
||||
)
|
||||
|
||||
if ! sudo "${sudoFlags[@]}" systemctl enable "$unitName"; then
|
||||
msgbox "Copyparty was configured, but the service failed to enable."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if sudo "${sudoFlags[@]}" systemctl is-active --quiet "$unitName"; then
|
||||
if ! sudo "${sudoFlags[@]}" systemctl restart "$unitName"; then
|
||||
msgbox "Copyparty was configured, but the running service failed to restart."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! sudo "${sudoFlags[@]}" systemctl start "$unitName"; then
|
||||
msgbox "Copyparty was configured, but the service failed to start."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_copyparty_firewall() {
|
||||
local lanSubnet=""
|
||||
|
||||
if ! ufw_installed; then
|
||||
msgbox "ufw is not installed, so Copyparty will listen on ${copypartyPort} without helper-managed LAN restrictions. Install and configure ufw if you need local-only enforcement."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(yesno "ufw is installed. Allow LAN-only access to Copyparty on port ${copypartyPort}?")" != "Yes" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
lanSubnet="$(choose_lan_subnet)" || return 1
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow from "$lanSubnet" to any port "$copypartyPort" proto tcp; then
|
||||
msgbox "Failed to allow Copyparty access for ${lanSubnet}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "Copyparty is allowed on ${copypartyPort}/tcp for ${lanSubnet}."
|
||||
return 0
|
||||
}
|
||||
|
||||
serviceUser="$(choose_copyparty_user)" || return 1
|
||||
install_copyparty || return 1
|
||||
accountsFile="$(collect_copyparty_accounts)" || return 1
|
||||
usernamesCsv="$(join_account_names "$accountsFile")"
|
||||
sharesFile="$(collect_copyparty_shares "$usernamesCsv" "$serviceUser")" || {
|
||||
rm -f "$accountsFile"
|
||||
return 1
|
||||
}
|
||||
|
||||
write_copyparty_config "$serviceUser" "$accountsFile" "$sharesFile" || {
|
||||
rm -f "$accountsFile" "$sharesFile"
|
||||
return 1
|
||||
}
|
||||
rm -f "$accountsFile" "$sharesFile"
|
||||
|
||||
enable_copyparty_service "$serviceUser" || return 1
|
||||
configure_copyparty_firewall || return 1
|
||||
|
||||
msgbox "Copyparty is configured. For advanced tuning, edit ${copypartyConfigFile} as root."
|
||||
298
.includes/firewall.sh
Normal file
298
.includes/firewall.sh
Normal file
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sshConfigDropIn="/etc/ssh/sshd_config.d/99-stormux-server.conf"
|
||||
|
||||
ufw_installed() {
|
||||
pacman -Q ufw &> /dev/null
|
||||
}
|
||||
|
||||
ufw_status_output() {
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" ufw status 2>&1
|
||||
}
|
||||
|
||||
ensure_ufw() {
|
||||
if ufw_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(yesno "ufw is not installed. Install it now and continue?")" != "Yes" ]]; then
|
||||
msgbox "Firewall action cancelled."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! install_package ufw; then
|
||||
msgbox "Failed to install ufw."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "ufw installed."
|
||||
return 0
|
||||
}
|
||||
|
||||
valid_port() {
|
||||
local portValue="$1"
|
||||
[[ "$portValue" =~ ^[0-9]+$ ]] && (( portValue >= 1 && portValue <= 65535 ))
|
||||
}
|
||||
|
||||
valid_port_rule() {
|
||||
local ruleValue="$1"
|
||||
|
||||
if [[ "$ruleValue" =~ ^[0-9]+$ ]]; then
|
||||
valid_port "$ruleValue"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if [[ "$ruleValue" =~ ^([0-9]+)/(tcp|udp)$ ]]; then
|
||||
valid_port "${BASH_REMATCH[1]}"
|
||||
return $?
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
resolve_ssh_port() {
|
||||
local configuredPort=""
|
||||
|
||||
if [[ -r "$sshConfigDropIn" ]]; then
|
||||
configuredPort="$(awk '
|
||||
BEGIN { IGNORECASE = 1 }
|
||||
/^[[:space:]]*Port[[:space:]]+[0-9]+([[:space:]]*#.*)?$/ {
|
||||
port = $2
|
||||
}
|
||||
END {
|
||||
if (port != "") {
|
||||
print port
|
||||
}
|
||||
}
|
||||
' "$sshConfigDropIn")"
|
||||
fi
|
||||
|
||||
if valid_port "$configuredPort"; then
|
||||
printf '%s\n' "$configuredPort"
|
||||
return 0
|
||||
fi
|
||||
|
||||
configuredPort="$(inputbox "Unable to confirm the SSH port automatically. Enter the SSH port to allow before changing the firewall.")" || return 1
|
||||
if valid_port "$configuredPort"; then
|
||||
printf '%s\n' "$configuredPort"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "A valid SSH port is required before enabling firewall rules."
|
||||
return 1
|
||||
}
|
||||
|
||||
allow_rule() {
|
||||
local ruleValue="$1"
|
||||
local description="$2"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow "$ruleValue"; then
|
||||
msgbox "Failed to allow ${description}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "${description} allowed."
|
||||
return 0
|
||||
}
|
||||
|
||||
delete_rule() {
|
||||
local ruleValue="$1"
|
||||
local description="$2"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw delete allow "$ruleValue"; then
|
||||
msgbox "Failed to remove ${description}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "${description} removed."
|
||||
return 0
|
||||
}
|
||||
|
||||
allow_ssh_port() {
|
||||
local sshPort=""
|
||||
|
||||
ensure_ufw || return 1
|
||||
sshPort="$(resolve_ssh_port)" || {
|
||||
msgbox "Firewall change cancelled because the SSH port could not be confirmed."
|
||||
return 1
|
||||
}
|
||||
|
||||
allow_rule "${sshPort}/tcp" "SSH port ${sshPort}/tcp"
|
||||
}
|
||||
|
||||
enable_firewall() {
|
||||
ensure_ufw || return 1
|
||||
allow_ssh_port || return 1
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if sudo "${sudoFlags[@]}" systemctl enable --now ufw; then
|
||||
msgbox "Firewall enabled."
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "Failed to enable ufw."
|
||||
return 1
|
||||
}
|
||||
|
||||
disable_firewall() {
|
||||
ensure_ufw || return 1
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if sudo "${sudoFlags[@]}" systemctl disable --now ufw; then
|
||||
msgbox "Firewall disabled."
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "Failed to disable ufw."
|
||||
return 1
|
||||
}
|
||||
|
||||
open_custom_port() {
|
||||
local portNumber=""
|
||||
local protocolChoice=""
|
||||
|
||||
ensure_ufw || return 1
|
||||
portNumber="$(inputbox "Enter the port number to open.")" || return 1
|
||||
if ! valid_port "$portNumber"; then
|
||||
msgbox "Enter a valid port number from 1 to 65535."
|
||||
return 1
|
||||
fi
|
||||
|
||||
protocolChoice="$(menulist "TCP" "UDP" "Both")" || return 1
|
||||
case "$protocolChoice" in
|
||||
"TCP")
|
||||
allow_rule "${portNumber}/tcp" "Port ${portNumber}/tcp"
|
||||
;;
|
||||
"UDP")
|
||||
allow_rule "${portNumber}/udp" "Port ${portNumber}/udp"
|
||||
;;
|
||||
"Both")
|
||||
allow_rule "${portNumber}/tcp" "Port ${portNumber}/tcp" || return 1
|
||||
allow_rule "${portNumber}/udp" "Port ${portNumber}/udp"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
list_simple_allow_rules() {
|
||||
local sshPort=""
|
||||
local statusLine=""
|
||||
declare -A tcpPorts=()
|
||||
declare -A udpPorts=()
|
||||
|
||||
sshPort="$(resolve_ssh_port 2> /dev/null || true)"
|
||||
|
||||
while IFS= read -r statusLine; do
|
||||
if [[ "$statusLine" =~ ^[[:space:]]*([0-9]+)/(tcp|udp)[[:space:]]+ALLOW[[:space:]]+Anywhere[[:space:]]*$ ]]; then
|
||||
if [[ "${BASH_REMATCH[1]}" == "$sshPort" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
case "${BASH_REMATCH[2]}" in
|
||||
tcp)
|
||||
tcpPorts["${BASH_REMATCH[1]}"]=1
|
||||
;;
|
||||
udp)
|
||||
udpPorts["${BASH_REMATCH[1]}"]=1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done < <(ufw_status_output)
|
||||
|
||||
for portNumber in "${!tcpPorts[@]}"; do
|
||||
if [[ -n "${udpPorts[$portNumber]:-}" ]]; then
|
||||
printf '%s/both\n' "$portNumber"
|
||||
unset 'udpPorts[$portNumber]'
|
||||
else
|
||||
printf '%s/tcp\n' "$portNumber"
|
||||
fi
|
||||
done
|
||||
|
||||
for portNumber in "${!udpPorts[@]}"; do
|
||||
printf '%s/udp\n' "$portNumber"
|
||||
done | sort -n -t / -k 1,1
|
||||
}
|
||||
|
||||
close_port() {
|
||||
local removableRules=()
|
||||
local selectedRule=""
|
||||
local ruleValue=""
|
||||
|
||||
ensure_ufw || return 1
|
||||
while IFS= read -r ruleValue; do
|
||||
[[ -n "$ruleValue" ]] && removableRules+=("$ruleValue")
|
||||
done < <(list_simple_allow_rules | sort -n -t / -k 1,1)
|
||||
|
||||
if [[ "${#removableRules[@]}" -eq 0 ]]; then
|
||||
msgbox "No simple removable port rules were found."
|
||||
return 0
|
||||
fi
|
||||
|
||||
selectedRule="$(menulist "${removableRules[@]}")" || return 0
|
||||
case "$selectedRule" in
|
||||
*/tcp|*/udp)
|
||||
delete_rule "$selectedRule" "Port ${selectedRule}"
|
||||
;;
|
||||
*/both)
|
||||
ruleValue="${selectedRule%/both}"
|
||||
delete_rule "${ruleValue}/tcp" "Port ${ruleValue}/tcp" || return 1
|
||||
delete_rule "${ruleValue}/udp" "Port ${ruleValue}/udp"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
view_firewall_status() {
|
||||
local tempFile=""
|
||||
local statusText=""
|
||||
|
||||
ensure_ufw || return 1
|
||||
tempFile="$(mktemp)"
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
statusText="$(sudo "${sudoFlags[@]}" ufw status verbose 2>&1)"
|
||||
printf '%s\n' "$statusText" > "$tempFile"
|
||||
textbox "$tempFile"
|
||||
rm -f "$tempFile"
|
||||
}
|
||||
|
||||
while true; do
|
||||
firewallChoice="$(menulist \
|
||||
"Enable firewall" \
|
||||
"Disable firewall" \
|
||||
"Allow SSH" \
|
||||
"Open port" \
|
||||
"Close port" \
|
||||
"View status" \
|
||||
"Back")" || break
|
||||
|
||||
case "$firewallChoice" in
|
||||
"Enable firewall")
|
||||
enable_firewall
|
||||
;;
|
||||
"Disable firewall")
|
||||
disable_firewall
|
||||
;;
|
||||
"Allow SSH")
|
||||
allow_ssh_port
|
||||
;;
|
||||
"Open port")
|
||||
open_custom_port
|
||||
;;
|
||||
"Close port")
|
||||
close_port
|
||||
;;
|
||||
"View status")
|
||||
view_firewall_status
|
||||
;;
|
||||
"Back")
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
9
.includes/functions.sh
Normal file
9
.includes/functions.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
install_package() {
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
yay --sudoflags "${sudoFlags[@]}" --sudoloop --noconfirm -Syu
|
||||
# shellcheck disable=SC2154
|
||||
yay --sudoflags "${sudoFlags[@]}" --sudoloop --needed --noconfirm -Sy "$@"
|
||||
}
|
||||
174
.includes/minidlna.sh
Normal file
174
.includes/minidlna.sh
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
minidlnaConfigFile="/etc/minidlna.conf"
|
||||
|
||||
minidlna_installed() {
|
||||
pacman -Q minidlna &> /dev/null
|
||||
}
|
||||
|
||||
ufw_installed() {
|
||||
pacman -Q ufw &> /dev/null
|
||||
}
|
||||
|
||||
valid_port() {
|
||||
local portValue="$1"
|
||||
[[ "$portValue" =~ ^[0-9]+$ ]] && (( portValue >= 1 && portValue <= 65535 ))
|
||||
}
|
||||
|
||||
install_minidlna() {
|
||||
if minidlna_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! install_package minidlna; then
|
||||
msgbox "Failed to install minidlna."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
enable_minidlna_service() {
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" systemctl enable minidlna; then
|
||||
msgbox "Failed to enable minidlna."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
read_minidlna_port() {
|
||||
local configuredPort=""
|
||||
local fallbackPort=""
|
||||
|
||||
if [[ -r "$minidlnaConfigFile" ]]; then
|
||||
configuredPort="$(awk -F '=' '
|
||||
BEGIN { IGNORECASE = 1 }
|
||||
/^[[:space:]]*port[[:space:]]*=/ {
|
||||
value = $2
|
||||
sub(/[[:space:]]*#.*$/, "", value)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
port = value
|
||||
}
|
||||
END {
|
||||
if (port != "") {
|
||||
print port
|
||||
}
|
||||
}
|
||||
' "$minidlnaConfigFile")"
|
||||
fi
|
||||
|
||||
if valid_port "$configuredPort"; then
|
||||
printf '%s\n' "$configuredPort"
|
||||
return 0
|
||||
fi
|
||||
|
||||
fallbackPort="$(inputbox "Unable to confirm the MiniDLNA port from /etc/minidlna.conf. Enter the TCP port to allow." "8200")" || return 1
|
||||
if valid_port "$fallbackPort"; then
|
||||
printf '%s\n' "$fallbackPort"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "A valid MiniDLNA port is required before adding firewall rules."
|
||||
return 1
|
||||
}
|
||||
|
||||
detect_private_subnets() {
|
||||
ip -o -4 addr show up scope global | awk '
|
||||
{
|
||||
split($4, parts, "/")
|
||||
split(parts[1], octets, ".")
|
||||
prefix = parts[2] + 0
|
||||
|
||||
if (octets[1] == 10) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else if (octets[1] == 192 && octets[2] == 168) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else if (octets[1] == 172 && octets[2] >= 16 && octets[2] <= 31) {
|
||||
subnet = octets[1] "." octets[2] "." octets[3] ".0/24"
|
||||
} else {
|
||||
next
|
||||
}
|
||||
|
||||
if (!seen[subnet]++) {
|
||||
print subnet
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
valid_ipv4_subnet() {
|
||||
local subnetValue="$1"
|
||||
|
||||
[[ "$subnetValue" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] || return 1
|
||||
awk -F '[./]' '
|
||||
{
|
||||
for (index = 1; index <= 4; index++) {
|
||||
if ($index < 0 || $index > 255) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
exit 0
|
||||
}
|
||||
' <<< "$subnetValue"
|
||||
}
|
||||
|
||||
choose_lan_subnet() {
|
||||
local detectedSubnet=""
|
||||
local subnetChoice=""
|
||||
|
||||
detectedSubnet="$(detect_private_subnets | head -n 1)"
|
||||
subnetChoice="$(inputbox "Confirm the LAN subnet for MiniDLNA firewall access." "${detectedSubnet:-192.168.1.0/24}")" || return 1
|
||||
if valid_ipv4_subnet "$subnetChoice"; then
|
||||
printf '%s\n' "$subnetChoice"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msgbox "Enter a valid IPv4 subnet such as 192.168.1.0/24."
|
||||
return 1
|
||||
}
|
||||
|
||||
configure_minidlna_firewall() {
|
||||
local minidlnaPort=""
|
||||
local lanSubnet=""
|
||||
|
||||
minidlnaPort="$(read_minidlna_port)" || return 1
|
||||
lanSubnet="$(choose_lan_subnet)" || return 1
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow from "$lanSubnet" to any port "$minidlnaPort" proto tcp; then
|
||||
msgbox "Failed to allow MiniDLNA TCP access for ${lanSubnet}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow from "$lanSubnet" to any port 1900 proto udp; then
|
||||
msgbox "Failed to allow SSDP discovery for ${lanSubnet}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw reload; then
|
||||
msgbox "MiniDLNA firewall rules were added, but ufw reload failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "MiniDLNA firewall rules were added for ${lanSubnet}."
|
||||
return 0
|
||||
}
|
||||
|
||||
install_minidlna || return 1
|
||||
enable_minidlna_service || return 1
|
||||
|
||||
msgbox "MiniDLNA is installed and enabled for future boots. Edit /etc/minidlna.conf to set your media paths before rebooting or before manually starting the service. It will start automatically on the next server reboot."
|
||||
|
||||
if ufw_installed; then
|
||||
if [[ "$(yesno "ufw is installed. Configure LAN-only firewall rules for MiniDLNA now?")" == "Yes" ]]; then
|
||||
configure_minidlna_firewall
|
||||
fi
|
||||
fi
|
||||
242
.includes/mumble.sh
Normal file
242
.includes/mumble.sh
Normal file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
mumbleConfigFile="/etc/mumble/mumble-server.ini"
|
||||
mumbleWelcomeFile="/etc/mumble/welcomefile.html"
|
||||
mumbleDefaultPort="64738"
|
||||
|
||||
mumble_installed() {
|
||||
pacman -Q mumble-server &> /dev/null
|
||||
}
|
||||
|
||||
ufw_installed() {
|
||||
pacman -Q ufw &> /dev/null
|
||||
}
|
||||
|
||||
valid_port() {
|
||||
local portValue="$1"
|
||||
[[ "$portValue" =~ ^[0-9]+$ ]] && (( portValue >= 1 && portValue <= 65535 ))
|
||||
}
|
||||
|
||||
install_mumble_server() {
|
||||
if mumble_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! install_package mumble-server; then
|
||||
msgbox "Failed to install mumble-server."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
set_mumble_config_value() {
|
||||
local keyName="$1"
|
||||
local keyValue="$2"
|
||||
local tempFile=""
|
||||
|
||||
tempFile="$(mktemp)"
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2024,SC2154
|
||||
if ! sudo "${sudoFlags[@]}" awk -v keyName="$keyName" -v keyValue="$keyValue" '
|
||||
BEGIN {
|
||||
updated = 0
|
||||
}
|
||||
$0 ~ "^[;#]?[[:space:]]*" keyName "[[:space:]]*=" {
|
||||
print keyName "=" keyValue
|
||||
updated = 1
|
||||
next
|
||||
}
|
||||
{
|
||||
print
|
||||
}
|
||||
END {
|
||||
if (!updated) {
|
||||
print keyName "=" keyValue
|
||||
}
|
||||
}
|
||||
' "$mumbleConfigFile" > "$tempFile"; then
|
||||
rm -f "$tempFile"
|
||||
msgbox "Failed to update ${keyName} in ${mumbleConfigFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! sudo "${sudoFlags[@]}" install -m 640 "$tempFile" "$mumbleConfigFile"; then
|
||||
rm -f "$tempFile"
|
||||
msgbox "Failed to save ${mumbleConfigFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$tempFile"
|
||||
return 0
|
||||
}
|
||||
|
||||
clear_mumble_config_value() {
|
||||
local keyName="$1"
|
||||
set_mumble_config_value "$keyName" ""
|
||||
}
|
||||
|
||||
read_mumble_port() {
|
||||
local configuredPort=""
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if sudo "${sudoFlags[@]}" test -r "$mumbleConfigFile"; then
|
||||
configuredPort="$(sudo "${sudoFlags[@]}" awk -F '=' '
|
||||
BEGIN { IGNORECASE = 1 }
|
||||
/^[[:space:]]*port[[:space:]]*=/ {
|
||||
value = $2
|
||||
sub(/[[:space:]]*#.*$/, "", value)
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
||||
port = value
|
||||
}
|
||||
END {
|
||||
if (port != "") {
|
||||
print port
|
||||
}
|
||||
}
|
||||
' "$mumbleConfigFile")"
|
||||
fi
|
||||
|
||||
if valid_port "$configuredPort"; then
|
||||
printf '%s\n' "$configuredPort"
|
||||
else
|
||||
printf '%s\n' "$mumbleDefaultPort"
|
||||
fi
|
||||
}
|
||||
|
||||
capture_welcome_message() {
|
||||
local tempFile=""
|
||||
local messageSize=0
|
||||
|
||||
if [[ "$(yesno "Would you like to create a welcome message now?")" != "Yes" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
tempFile="$(mktemp)"
|
||||
clear
|
||||
cat <<'EOF'
|
||||
Enter the Mumble welcome message below.
|
||||
HTML is supported.
|
||||
Press Ctrl+D on a new line when you are finished.
|
||||
Leave it blank and press Ctrl+D to skip it.
|
||||
EOF
|
||||
cat > "$tempFile"
|
||||
messageSize="$(wc -c < "$tempFile")"
|
||||
if [[ "$messageSize" -eq 0 ]]; then
|
||||
rm -f "$tempFile"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$tempFile"
|
||||
return 0
|
||||
}
|
||||
|
||||
install_welcome_message() {
|
||||
local messageFile="$1"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -m 644 "$messageFile" "$mumbleWelcomeFile"; then
|
||||
msgbox "Failed to write ${mumbleWelcomeFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
set_mumble_config_value "welcometextfile" "$mumbleWelcomeFile"
|
||||
}
|
||||
|
||||
set_mumble_superuser_password() {
|
||||
local superuserPassword="$1"
|
||||
|
||||
if [[ -z "$superuserPassword" ]]; then
|
||||
msgbox "The SuperUser password cannot be blank."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! printf '%s\n' "$superuserPassword" | sudo "${sudoFlags[@]}" mumble-server -ini "$mumbleConfigFile" -readsupw; then
|
||||
msgbox "Failed to set the Mumble SuperUser password."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
enable_mumble_service() {
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" systemctl enable --now mumble-server; then
|
||||
msgbox "Mumble was configured, but the service failed to enable or start."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_mumble_firewall() {
|
||||
local mumblePort=""
|
||||
|
||||
if ! ufw_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(yesno "ufw is installed. Open the default Mumble port now?")" != "Yes" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
mumblePort="$(read_mumble_port)"
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow "${mumblePort}/tcp"; then
|
||||
msgbox "Failed to allow ${mumblePort}/tcp for Mumble."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow "${mumblePort}/udp"; then
|
||||
msgbox "Failed to allow ${mumblePort}/udp for Mumble."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "The default Mumble firewall rules were added."
|
||||
return 0
|
||||
}
|
||||
|
||||
install_mumble_server || return 1
|
||||
|
||||
superuserPassword="$(passwordbox "Enter the Mumble SuperUser password.")" || return 1
|
||||
serverPassword="$(passwordbox "Enter a server password, or leave blank for no server password.")" || return 1
|
||||
allowRecordingChoice="$(yesno "Allow users to record audio on this server?")"
|
||||
|
||||
welcomeMessageFile=""
|
||||
welcomeMessageFile="$(capture_welcome_message || true)"
|
||||
reset
|
||||
|
||||
set_mumble_superuser_password "$superuserPassword" || return 1
|
||||
if [[ -n "$serverPassword" ]]; then
|
||||
set_mumble_config_value "serverpassword" "$serverPassword" || return 1
|
||||
else
|
||||
clear_mumble_config_value "serverpassword" || return 1
|
||||
fi
|
||||
|
||||
if [[ "$allowRecordingChoice" == "Yes" ]]; then
|
||||
set_mumble_config_value "allowRecording" "true" || return 1
|
||||
else
|
||||
set_mumble_config_value "allowRecording" "false" || return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$welcomeMessageFile" ]]; then
|
||||
install_welcome_message "$welcomeMessageFile" || {
|
||||
rm -f "$welcomeMessageFile"
|
||||
return 1
|
||||
}
|
||||
rm -f "$welcomeMessageFile"
|
||||
else
|
||||
clear_mumble_config_value "welcometextfile" || return 1
|
||||
fi
|
||||
|
||||
enable_mumble_service || return 1
|
||||
configure_mumble_firewall || return 1
|
||||
|
||||
msgbox "Basic Mumble configuration is complete. To fine tune your setup, edit ${mumbleConfigFile} as root."
|
||||
501
.includes/nginx.sh
Normal file
501
.includes/nginx.sh
Normal file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
nginxConfigFile="/etc/nginx/nginx.conf"
|
||||
nginxBackupFile="/etc/nginx/nginx.conf.configure-server.bak"
|
||||
nginxSitesAvailable="/etc/nginx/sites-available"
|
||||
nginxSitesEnabled="/etc/nginx/sites-enabled"
|
||||
nginxDefaultSite="${nginxSitesAvailable}/default.conf"
|
||||
nginxDefaultSiteLink="${nginxSitesEnabled}/default.conf"
|
||||
clacksInfoUrl="https://www.gnuterrypratchett.com/"
|
||||
nginxManagedInclude="include /etc/nginx/sites-enabled/*.conf;"
|
||||
|
||||
nginx_installed() {
|
||||
pacman -Q nginx &> /dev/null
|
||||
}
|
||||
|
||||
ufw_installed() {
|
||||
pacman -Q ufw &> /dev/null
|
||||
}
|
||||
|
||||
ensure_ufw() {
|
||||
if ufw_installed; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(yesno "ufw is not installed. Install it now and continue?")" != "Yes" ]]; then
|
||||
msgbox "Firewall action cancelled."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! install_package ufw; then
|
||||
msgbox "Failed to install ufw."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "ufw installed."
|
||||
return 0
|
||||
}
|
||||
|
||||
install_nginx() {
|
||||
local clacksHeader=""
|
||||
|
||||
if ! nginx_installed; then
|
||||
if ! install_package nginx; then
|
||||
msgbox "Failed to install nginx."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
clacksHeader="$(prompt_clacks_header || true)"
|
||||
setup_nginx_layout "$clacksHeader" || return 1
|
||||
if ! test_nginx_config; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" systemctl enable --now nginx; then
|
||||
msgbox "nginx was configured, but the service failed to enable or start."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "nginx is installed and running."
|
||||
return 0
|
||||
}
|
||||
|
||||
prompt_clacks_header() {
|
||||
local rawNames=""
|
||||
local formattedNames=()
|
||||
local nameEntry=""
|
||||
local clacksHeader=""
|
||||
|
||||
if [[ "$(yesno "Would you like to add an optional X-Clacks-Overhead header for all nginx sites?")" != "Yes" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "X-Clacks-Overhead is a tribute header inspired by Terry Pratchett's Clacks. For more information, visit ${clacksInfoUrl}"
|
||||
rawNames="$(inputbox "Enter a comma-separated list of names for the X-Clacks-Overhead header, or leave blank to skip.")" || return 1
|
||||
if [[ -z "${rawNames//[[:space:]]/}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r nameEntry; do
|
||||
nameEntry="${nameEntry#"${nameEntry%%[![:space:]]*}"}"
|
||||
nameEntry="${nameEntry%"${nameEntry##*[![:space:]]}"}"
|
||||
if [[ -n "$nameEntry" ]]; then
|
||||
formattedNames+=("GNU ${nameEntry}")
|
||||
fi
|
||||
done < <(printf '%s\n' "$rawNames" | tr ',' '\n')
|
||||
|
||||
if [[ "${#formattedNames[@]}" -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
clacksHeader="$(printf '%s, ' "${formattedNames[@]}")"
|
||||
clacksHeader="${clacksHeader%, }"
|
||||
printf '%s\n' "$clacksHeader"
|
||||
return 0
|
||||
}
|
||||
|
||||
setup_nginx_layout() {
|
||||
local clacksHeader="$1"
|
||||
local generatedConfig=""
|
||||
local tempConfig=""
|
||||
|
||||
if ! sudo "${sudoFlags[@]}" test -r "$nginxConfigFile"; then
|
||||
msgbox "Unable to read ${nginxConfigFile}. Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
generatedConfig="$(generate_nginx_config "$clacksHeader")" || {
|
||||
msgbox "Failed to generate nginx.conf."
|
||||
return 1
|
||||
}
|
||||
tempConfig="$(mktemp)"
|
||||
printf '%s\n' "$generatedConfig" > "$tempConfig"
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -d -m 755 "$nginxSitesAvailable" "$nginxSitesEnabled"; then
|
||||
rm -f "$tempConfig"
|
||||
msgbox "Failed to create the nginx site directories."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" test -e "$nginxBackupFile"; then
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" cp "$nginxConfigFile" "$nginxBackupFile" 2> /dev/null || true
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempConfig" "$nginxConfigFile"; then
|
||||
rm -f "$tempConfig"
|
||||
msgbox "Failed to write ${nginxConfigFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$tempConfig"
|
||||
ensure_default_site
|
||||
}
|
||||
|
||||
generate_nginx_config() {
|
||||
local clacksHeader="$1"
|
||||
|
||||
sudo "${sudoFlags[@]}" awk -v clacksHeader="$clacksHeader" -v managedInclude="$nginxManagedInclude" '
|
||||
BEGIN {
|
||||
skippingServer = 0
|
||||
braceDepth = 0
|
||||
insertedSitesInclude = 0
|
||||
skipManagedComment = 0
|
||||
}
|
||||
{
|
||||
if ($0 == "# Managed by configure-server") {
|
||||
skipManagedComment = 1
|
||||
next
|
||||
}
|
||||
|
||||
if ($0 ~ /^[[:space:]]*add_header X-Clacks-Overhead /) {
|
||||
next
|
||||
}
|
||||
|
||||
if ($0 ~ /^[[:space:]]*include \/etc\/nginx\/sites-enabled\/\*\.conf;$/) {
|
||||
next
|
||||
}
|
||||
|
||||
if (skippingServer) {
|
||||
opens = gsub(/\{/, "{")
|
||||
closes = gsub(/\}/, "}")
|
||||
braceDepth += opens - closes
|
||||
if (braceDepth <= 0) {
|
||||
skippingServer = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if ($0 ~ /^ server \{$/) {
|
||||
skippingServer = 1
|
||||
braceDepth = 1
|
||||
next
|
||||
}
|
||||
|
||||
if (skipManagedComment) {
|
||||
skipManagedComment = 0
|
||||
}
|
||||
|
||||
print
|
||||
|
||||
if (!insertedSitesInclude && $0 ~ /^[[:space:]]*#gzip[[:space:]]+on;/) {
|
||||
if (clacksHeader != "") {
|
||||
print ""
|
||||
print " add_header X-Clacks-Overhead \"" clacksHeader "\" always;"
|
||||
}
|
||||
print ""
|
||||
print " " managedInclude
|
||||
insertedSitesInclude = 1
|
||||
}
|
||||
}
|
||||
' "$nginxConfigFile"
|
||||
}
|
||||
|
||||
ensure_default_site() {
|
||||
local tempSiteFile=""
|
||||
|
||||
if [[ -e "$nginxDefaultSite" ]]; then
|
||||
if [[ ! -L "$nginxDefaultSiteLink" ]]; then
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" ln -sf "$nginxDefaultSite" "$nginxDefaultSiteLink"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
tempSiteFile="$(mktemp)"
|
||||
cat > "$tempSiteFile" <<'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempSiteFile" "$nginxDefaultSite"; then
|
||||
rm -f "$tempSiteFile"
|
||||
msgbox "Failed to create the default nginx site."
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$tempSiteFile"
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ln -sf "$nginxDefaultSite" "$nginxDefaultSiteLink"; then
|
||||
msgbox "Failed to enable the default nginx site."
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
create_site() {
|
||||
local siteName=""
|
||||
local serverNames=""
|
||||
local siteRoot=""
|
||||
local tempSiteFile=""
|
||||
local siteConfigFile=""
|
||||
local defaultIndexFile=""
|
||||
|
||||
if ! nginx_installed; then
|
||||
msgbox "Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
siteName="$(inputbox "Enter a short site name for the config file, for example example.com.")" || return 1
|
||||
if [[ -z "$siteName" || ! "$siteName" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
msgbox "Enter a valid site name using letters, numbers, dots, dashes, or underscores."
|
||||
return 1
|
||||
fi
|
||||
|
||||
serverNames="$(inputbox "Enter the server_name value, using spaces between names." "$siteName")" || return 1
|
||||
siteRoot="$(inputbox "Enter the site document root." "/srv/http/${siteName}")" || return 1
|
||||
if [[ -z "$siteRoot" ]]; then
|
||||
msgbox "A document root is required."
|
||||
return 1
|
||||
fi
|
||||
|
||||
siteConfigFile="${nginxSitesAvailable}/${siteName}.conf"
|
||||
tempSiteFile="$(mktemp)"
|
||||
cat > "$tempSiteFile" <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${serverNames};
|
||||
|
||||
root ${siteRoot};
|
||||
index index.html index.htm;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ =404;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -d -m 755 "$siteRoot"; then
|
||||
rm -f "$tempSiteFile"
|
||||
msgbox "Failed to create ${siteRoot}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
defaultIndexFile="$(mktemp)"
|
||||
cat > "$defaultIndexFile" <<EOF
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>${siteName}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${siteName}</h1>
|
||||
<p>nginx is serving ${siteName} from ${siteRoot}.</p>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" install -m 644 "$tempSiteFile" "$siteConfigFile"; then
|
||||
rm -f "$tempSiteFile" "$defaultIndexFile"
|
||||
msgbox "Failed to write ${siteConfigFile}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" test -e "${siteRoot}/index.html"; then
|
||||
# shellcheck disable=SC2154
|
||||
sudo "${sudoFlags[@]}" install -m 644 "$defaultIndexFile" "${siteRoot}/index.html" || true
|
||||
fi
|
||||
|
||||
rm -f "$tempSiteFile" "$defaultIndexFile"
|
||||
msgbox "Site ${siteName} was created in ${nginxSitesAvailable}. Enable it from the nginx menu when you are ready."
|
||||
return 0
|
||||
}
|
||||
|
||||
select_site_file() {
|
||||
local searchPath="$1"
|
||||
local filePattern="$2"
|
||||
local selectedSite=""
|
||||
local siteEntries=()
|
||||
local sitePath=""
|
||||
|
||||
while IFS= read -r sitePath; do
|
||||
[[ -n "$sitePath" ]] && siteEntries+=("$(basename "$sitePath")")
|
||||
done < <(find "$searchPath" -maxdepth 1 -type "$filePattern" -name '*.conf' | sort)
|
||||
|
||||
if [[ "${#siteEntries[@]}" -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
selectedSite="$(menulist "${siteEntries[@]}")" || return 1
|
||||
printf '%s\n' "$selectedSite"
|
||||
}
|
||||
|
||||
enable_site() {
|
||||
local siteName=""
|
||||
|
||||
if ! nginx_installed; then
|
||||
msgbox "Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
siteName="$(select_site_file "$nginxSitesAvailable" f)" || {
|
||||
msgbox "No available site configs were found."
|
||||
return 1
|
||||
}
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ln -sf "${nginxSitesAvailable}/${siteName}" "${nginxSitesEnabled}/${siteName}"; then
|
||||
msgbox "Failed to enable ${siteName}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "${siteName} is enabled. Test and reload nginx when you are ready."
|
||||
return 0
|
||||
}
|
||||
|
||||
disable_site() {
|
||||
local siteName=""
|
||||
|
||||
if ! nginx_installed; then
|
||||
msgbox "Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
siteName="$(select_site_file "$nginxSitesEnabled" l)" || {
|
||||
msgbox "No enabled sites were found."
|
||||
return 1
|
||||
}
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" rm -f "${nginxSitesEnabled}/${siteName}"; then
|
||||
msgbox "Failed to disable ${siteName}."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "${siteName} is disabled. Test and reload nginx when you are ready."
|
||||
return 0
|
||||
}
|
||||
|
||||
test_nginx_config() {
|
||||
local tempFile=""
|
||||
local status=0
|
||||
local statusText=""
|
||||
|
||||
if ! nginx_installed; then
|
||||
msgbox "Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
tempFile="$(mktemp)"
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
statusText="$(sudo "${sudoFlags[@]}" nginx -t 2>&1)"
|
||||
status=$?
|
||||
printf '%s\n' "$statusText" > "$tempFile"
|
||||
textbox "$tempFile"
|
||||
rm -f "$tempFile"
|
||||
return "$status"
|
||||
}
|
||||
|
||||
reload_nginx() {
|
||||
if ! nginx_installed; then
|
||||
msgbox "Install nginx first."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! test_nginx_config; then
|
||||
msgbox "nginx was not reloaded because the config test failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" systemctl reload nginx; then
|
||||
msgbox "Failed to reload nginx."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "nginx reloaded successfully."
|
||||
return 0
|
||||
}
|
||||
|
||||
open_web_ports() {
|
||||
ensure_ufw || return 1
|
||||
|
||||
# `sudoFlags` is initialized by the main launcher before sourcing this file.
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow 80/tcp; then
|
||||
msgbox "Failed to open 80/tcp."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
if ! sudo "${sudoFlags[@]}" ufw allow 443/tcp; then
|
||||
msgbox "Failed to open 443/tcp."
|
||||
return 1
|
||||
fi
|
||||
|
||||
msgbox "Web ports 80/tcp and 443/tcp are allowed."
|
||||
return 0
|
||||
}
|
||||
|
||||
while true; do
|
||||
nginxChoice="$(menulist \
|
||||
"Install nginx" \
|
||||
"Create site" \
|
||||
"Enable site" \
|
||||
"Disable site" \
|
||||
"Test config" \
|
||||
"Reload nginx" \
|
||||
"Open web ports" \
|
||||
"Back")" || break
|
||||
|
||||
case "$nginxChoice" in
|
||||
"Install nginx")
|
||||
install_nginx
|
||||
;;
|
||||
"Create site")
|
||||
create_site
|
||||
;;
|
||||
"Enable site")
|
||||
enable_site
|
||||
;;
|
||||
"Disable site")
|
||||
disable_site
|
||||
;;
|
||||
"Test config")
|
||||
test_nginx_config
|
||||
;;
|
||||
"Reload nginx")
|
||||
reload_nginx
|
||||
;;
|
||||
"Open web ports")
|
||||
open_web_ports
|
||||
;;
|
||||
"Back")
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
373
.includes/topspeed.sh
Normal file
373
.includes/topspeed.sh
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
topspeedUser="topspeed"
|
||||
topspeedHome="/var/lib/topspeed"
|
||||
topspeedInstallDir="${topspeedHome}/server"
|
||||
topspeedServiceFile="/etc/systemd/system/topspeed.service"
|
||||
topspeedServiceScript="${topspeedInstallDir}/topspeed-service.sh"
|
||||
topspeedStopScript="${topspeedInstallDir}/topspeed-stop.sh"
|
||||
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 "$topspeedInstallDir"; 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 "$topspeedInstallDir" "$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
|
||||
sudo "${sudoFlags[@]}" find "$topspeedInstallDir" -type f -name 'TopSpeed.Server*' -exec chmod 755 {} + || true
|
||||
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}"
|
||||
installDir="${topspeedInstallDir}"
|
||||
stopFlagPath="${topspeedHome}/.stopping"
|
||||
binaryPath=""
|
||||
|
||||
find_topspeed_binary() {
|
||||
local exactBinary=""
|
||||
local fallbackBinary=""
|
||||
|
||||
exactBinary="\$(find "\$installDir" -type f -name 'TopSpeed.Server' | sort | head -n 1)"
|
||||
if [[ -n "\$exactBinary" ]]; then
|
||||
printf '%s\n' "\$exactBinary"
|
||||
return 0
|
||||
fi
|
||||
|
||||
fallbackBinary="\$(find "\$installDir" -type f -name 'TopSpeed.Server*' | sort | head -n 1)"
|
||||
if [[ -n "\$fallbackBinary" ]]; then
|
||||
printf '%s\n' "\$fallbackBinary"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
rm -f "\$stopFlagPath"
|
||||
if tmux has-session -t "\$sessionName" &> /dev/null; then
|
||||
tmux kill-session -t "\$sessionName"
|
||||
fi
|
||||
|
||||
binaryPath="\$(find_topspeed_binary)" || exit 1
|
||||
tmux new-session -d -s "\$sessionName" "\$binaryPath" || exit 1
|
||||
|
||||
while tmux has-session -t "\$sessionName" &> /dev/null; do
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -f "\$stopFlagPath" ]]; then
|
||||
rm -f "\$stopFlagPath"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
cat > "$tempStopScript" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sessionName="${topspeedSessionName}"
|
||||
stopFlagPath="${topspeedHome}/.stopping"
|
||||
|
||||
touch "\$stopFlagPath"
|
||||
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=${topspeedInstallDir}
|
||||
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}/tcp"; then
|
||||
msgbox "Failed to allow the Top Speed server port ${topspeedServerPort}/tcp."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 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. Detach with Ctrl-b 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
|
||||
59
.includes/ui.sh
Normal file
59
.includes/ui.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export DIALOGOPTS='--insecure --no-lines --visit-items'
|
||||
|
||||
inputbox() {
|
||||
local promptText="$1"
|
||||
local initialValue="${2:-}"
|
||||
|
||||
dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--inputbox "$promptText" 0 0 "$initialValue" --stdout
|
||||
}
|
||||
|
||||
passwordbox() {
|
||||
local promptText="$1"
|
||||
local initialValue="${2:-}"
|
||||
|
||||
dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--passwordbox "$promptText" 0 0 "$initialValue" --stdout
|
||||
}
|
||||
|
||||
msgbox() {
|
||||
dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--msgbox "$*" 10 72
|
||||
}
|
||||
|
||||
yesno() {
|
||||
if dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--yesno "$*" 10 80 --stdout; then
|
||||
echo "Yes"
|
||||
else
|
||||
echo "No"
|
||||
fi
|
||||
}
|
||||
|
||||
menulist() {
|
||||
local menuList=()
|
||||
local menuItem=""
|
||||
|
||||
for menuItem in "$@"; do
|
||||
menuList+=("$menuItem" "$menuItem")
|
||||
done
|
||||
|
||||
dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--no-tags \
|
||||
--menu "Please select an option" 0 0 0 "${menuList[@]}" --stdout
|
||||
}
|
||||
|
||||
textbox() {
|
||||
local filePath="$1"
|
||||
|
||||
dialog --backtitle "Configure Server" \
|
||||
--clear \
|
||||
--textbox "$filePath" 0 0
|
||||
}
|
||||
Reference in New Issue
Block a user