354 lines
8.4 KiB
Bash
Executable File
354 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
|
|
red='\033[0;31m'
|
|
green='\033[0;32m'
|
|
yellow='\033[0;33m'
|
|
plain='\033[0m'
|
|
|
|
service_name='ssr-shell'
|
|
config_dir='/etc/shadowsocks-rust'
|
|
config_file="${config_dir}/server.json"
|
|
service_file="/etc/systemd/system/${service_name}.service"
|
|
method='2022-blake3-aes-256-gcm'
|
|
node_name='HKIXhuoshan-akcdn'
|
|
|
|
info() {
|
|
echo -e "[${green}Info${plain}] $*"
|
|
}
|
|
|
|
warn() {
|
|
echo -e "[${yellow}Warn${plain}] $*"
|
|
}
|
|
|
|
fail() {
|
|
echo -e "[${red}Error${plain}] $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_root() {
|
|
[ "${EUID}" -eq 0 ] || fail "Please run as root."
|
|
}
|
|
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
detect_package_manager() {
|
|
if command_exists apt-get; then
|
|
echo apt
|
|
elif command_exists dnf; then
|
|
echo dnf
|
|
elif command_exists yum; then
|
|
echo yum
|
|
else
|
|
echo unknown
|
|
fi
|
|
}
|
|
|
|
install_base_dependencies() {
|
|
local pm
|
|
pm=$(detect_package_manager)
|
|
|
|
case "${pm}" in
|
|
apt)
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get update
|
|
apt-get install -y ca-certificates curl tar xz-utils openssl iproute2
|
|
;;
|
|
dnf)
|
|
dnf install -y ca-certificates curl tar xz openssl iproute
|
|
;;
|
|
yum)
|
|
yum install -y ca-certificates curl tar xz openssl iproute
|
|
;;
|
|
*)
|
|
fail "Unsupported package manager. Please install curl, tar, xz, openssl manually."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
try_install_from_package_manager() {
|
|
local pm
|
|
pm=$(detect_package_manager)
|
|
|
|
case "${pm}" in
|
|
apt)
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get install -y shadowsocks-rust || true
|
|
;;
|
|
dnf)
|
|
dnf install -y shadowsocks-rust || true
|
|
;;
|
|
yum)
|
|
yum install -y shadowsocks-rust || true
|
|
;;
|
|
esac
|
|
}
|
|
|
|
detect_release_target() {
|
|
local arch
|
|
arch=$(uname -m)
|
|
|
|
case "${arch}" in
|
|
x86_64|amd64)
|
|
echo x86_64-unknown-linux-gnu
|
|
;;
|
|
aarch64|arm64)
|
|
echo aarch64-unknown-linux-gnu
|
|
;;
|
|
armv7l|armv7)
|
|
echo armv7-unknown-linux-gnueabihf
|
|
;;
|
|
*)
|
|
fail "Unsupported CPU architecture: ${arch}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_from_github_release() {
|
|
local target api url tmp archive ssserver_path ssservice_path
|
|
target=$(detect_release_target)
|
|
api='https://api.github.com/repos/shadowsocks/shadowsocks-rust/releases/latest'
|
|
tmp=$(mktemp -d)
|
|
archive="${tmp}/shadowsocks-rust.tar"
|
|
|
|
info "Downloading official shadowsocks-rust release for ${target}..."
|
|
url=$(curl -fsSL "${api}" \
|
|
| grep -E '"browser_download_url":' \
|
|
| grep "${target}" \
|
|
| grep -E 'tar\.(xz|gz)"' \
|
|
| head -n 1 \
|
|
| sed -E 's/.*"([^"]+)".*/\1/')
|
|
|
|
[ -n "${url}" ] || fail "Cannot find a shadowsocks-rust release asset for ${target}."
|
|
curl -fL "${url}" -o "${archive}"
|
|
tar -xf "${archive}" -C "${tmp}"
|
|
|
|
ssserver_path=$(find "${tmp}" -type f -name ssserver | head -n 1)
|
|
ssservice_path=$(find "${tmp}" -type f -name ssservice | head -n 1)
|
|
|
|
[ -n "${ssserver_path}" ] || fail "ssserver binary not found in release archive."
|
|
install -m 0755 "${ssserver_path}" /usr/local/bin/ssserver
|
|
|
|
if [ -n "${ssservice_path}" ]; then
|
|
install -m 0755 "${ssservice_path}" /usr/local/bin/ssservice
|
|
fi
|
|
|
|
rm -rf "${tmp}"
|
|
}
|
|
|
|
install_shadowsocks_rust() {
|
|
if command_exists ssserver; then
|
|
info "ssserver already installed: $(command -v ssserver)"
|
|
return 0
|
|
fi
|
|
|
|
try_install_from_package_manager
|
|
|
|
if command_exists ssserver; then
|
|
info "ssserver installed: $(command -v ssserver)"
|
|
return 0
|
|
fi
|
|
|
|
install_from_github_release
|
|
command_exists ssserver || fail "Failed to install ssserver."
|
|
}
|
|
|
|
is_valid_port() {
|
|
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
|
|
}
|
|
|
|
random_port() {
|
|
echo $((10000 + RANDOM % 50000))
|
|
}
|
|
|
|
public_ip() {
|
|
local ip
|
|
ip=$(curl -4fsS --max-time 3 https://api.ipify.org 2>/dev/null || true)
|
|
if [ -z "${ip}" ] && command_exists ip; then
|
|
ip=$(ip -4 addr show scope global | awk '/inet / { sub(/\/.*/, "", $2); print $2; exit }')
|
|
fi
|
|
echo "${ip}"
|
|
}
|
|
|
|
prompt_value() {
|
|
local prompt default value
|
|
prompt="$1"
|
|
default="$2"
|
|
read -r -p "${prompt} (Default: ${default}): " value
|
|
if [ -z "${value}" ]; then
|
|
value="${default}"
|
|
fi
|
|
echo "${value}"
|
|
}
|
|
|
|
prompt_port() {
|
|
local default_port value
|
|
default_port=$(random_port)
|
|
while true; do
|
|
value=$(prompt_value "Please enter Shadowsocks server port" "${default_port}")
|
|
if is_valid_port "${value}"; then
|
|
echo "${value}"
|
|
return 0
|
|
fi
|
|
warn "Invalid port: ${value}"
|
|
done
|
|
}
|
|
|
|
generate_key() {
|
|
if command_exists ssservice; then
|
|
if ssservice genkey -m "${method}" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
openssl rand -base64 32
|
|
}
|
|
|
|
write_config() {
|
|
local port password
|
|
port="$1"
|
|
password="$2"
|
|
|
|
mkdir -p "${config_dir}"
|
|
chmod 0755 "${config_dir}"
|
|
|
|
cat > "${config_file}" <<EOF
|
|
{
|
|
"server": "0.0.0.0",
|
|
"server_port": ${port},
|
|
"password": "${password}",
|
|
"method": "${method}",
|
|
"mode": "tcp_and_udp",
|
|
"timeout": 300,
|
|
"fast_open": false,
|
|
"no_delay": true
|
|
}
|
|
EOF
|
|
|
|
chmod 0600 "${config_file}"
|
|
}
|
|
|
|
write_service() {
|
|
local ssserver_bin
|
|
ssserver_bin=$(command -v ssserver)
|
|
|
|
cat > "${service_file}" <<EOF
|
|
[Unit]
|
|
Description=ssr-shell Shadowsocks-rust server
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=${ssserver_bin} -c ${config_file}
|
|
Restart=on-failure
|
|
RestartSec=3
|
|
LimitNOFILE=1048576
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
}
|
|
|
|
open_firewall() {
|
|
local port
|
|
port="$1"
|
|
|
|
if command_exists firewall-cmd && systemctl is-active --quiet firewalld; then
|
|
firewall-cmd --permanent --add-port="${port}/tcp" >/dev/null 2>&1 || true
|
|
firewall-cmd --permanent --add-port="${port}/udp" >/dev/null 2>&1 || true
|
|
firewall-cmd --reload >/dev/null 2>&1 || true
|
|
info "firewalld opened ${port}/tcp and ${port}/udp."
|
|
elif command_exists ufw && ufw status 2>/dev/null | grep -qi active; then
|
|
ufw allow "${port}/tcp" >/dev/null 2>&1 || true
|
|
ufw allow "${port}/udp" >/dev/null 2>&1 || true
|
|
info "ufw opened ${port}/tcp and ${port}/udp."
|
|
else
|
|
warn "No active firewalld/ufw detected. Open ${port}/tcp and ${port}/udp manually if needed."
|
|
fi
|
|
}
|
|
|
|
start_service() {
|
|
systemctl daemon-reload
|
|
systemctl enable --now "${service_name}"
|
|
systemctl --no-pager --full status "${service_name}" || true
|
|
}
|
|
|
|
print_node() {
|
|
local server port password name
|
|
server="$1"
|
|
port="$2"
|
|
password="$3"
|
|
name="$4"
|
|
|
|
echo
|
|
echo "Your Mihomo ss node:"
|
|
printf ' - {name: %s, type: ss, server: %s, port: %s, cipher: "%s", password: "%s", udp: true}\n' "${name}" "${server}" "${port}" "${method}" "${password}"
|
|
}
|
|
|
|
install_action() {
|
|
local port password default_server client_server name
|
|
|
|
require_root
|
|
install_base_dependencies
|
|
install_shadowsocks_rust
|
|
|
|
port=$(prompt_port)
|
|
password=$(generate_key)
|
|
default_server=$(public_ip)
|
|
[ -n "${default_server}" ] || default_server='your.server.ip'
|
|
client_server=$(prompt_value "Please enter server IP/domain for Mihomo node" "${default_server}")
|
|
name=$(prompt_value "Please enter Mihomo node name" "${node_name}")
|
|
|
|
write_config "${port}" "${password}"
|
|
write_service
|
|
open_firewall "${port}"
|
|
start_service
|
|
|
|
echo
|
|
info "Config file: ${config_file}"
|
|
info "Service name: ${service_name}"
|
|
info "Cipher: ${method}"
|
|
info "Port: ${port}"
|
|
print_node "${client_server}" "${port}" "${password}" "${name}"
|
|
}
|
|
|
|
uninstall_action() {
|
|
require_root
|
|
systemctl disable --now "${service_name}" >/dev/null 2>&1 || true
|
|
rm -f "${service_file}"
|
|
rm -rf "${config_dir}"
|
|
systemctl daemon-reload
|
|
info "Uninstalled ${service_name}."
|
|
}
|
|
|
|
status_action() {
|
|
systemctl --no-pager --full status "${service_name}"
|
|
}
|
|
|
|
usage() {
|
|
echo "Usage: $(basename "$0") [install|uninstall|status]"
|
|
}
|
|
|
|
action="${1:-install}"
|
|
|
|
case "${action}" in
|
|
install)
|
|
install_action
|
|
;;
|
|
uninstall)
|
|
uninstall_action
|
|
;;
|
|
status)
|
|
status_action
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|