618 lines
18 KiB
Bash
Executable File
618 lines
18 KiB
Bash
Executable File
#!/bin/sh
|
||
set -eu
|
||
|
||
repository=imengying/NodeFlare
|
||
init_system=""
|
||
systemd_file=/etc/systemd/system/nodeflare.service
|
||
openrc_file=/etc/init.d/nodeflare
|
||
launchd_file=/Library/LaunchDaemons/nodeflare.plist
|
||
freebsd_rc_file=/usr/local/etc/rc.d/nodeflare
|
||
download_dir=""
|
||
package_dir=""
|
||
release_version=""
|
||
config_temp=""
|
||
tty_state=""
|
||
rollback_ready=false
|
||
backup_dir=""
|
||
previous_install=false
|
||
previous_share=false
|
||
previous_service=false
|
||
|
||
case "$(uname -s)" in
|
||
Linux)
|
||
platform=linux
|
||
config_dir=/etc/nodeflare
|
||
install_dir=/opt/nodeflare
|
||
share_dir=$install_dir/share
|
||
default_database_url=sqlite:///etc/nodeflare/nodeflare.db
|
||
;;
|
||
Darwin)
|
||
platform=macos
|
||
config_dir="/Library/Application Support/NodeFlare/Server"
|
||
install_dir=/usr/local/libexec/nodeflare
|
||
share_dir=$install_dir/share
|
||
default_database_url=sqlite://nodeflare.db
|
||
;;
|
||
FreeBSD)
|
||
platform=freebsd
|
||
config_dir=/var/db/nodeflare/server
|
||
install_dir=/usr/local/libexec/nodeflare
|
||
share_dir=/usr/local/share/nodeflare
|
||
default_database_url=sqlite://nodeflare.db
|
||
;;
|
||
*)
|
||
printf '[NodeFlare] 错误:当前系统不支持此安装脚本\n' >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
config_file=$config_dir/config.toml
|
||
server_binary=$install_dir/nodeflare
|
||
theme_dir=$config_dir/themes
|
||
public_frontend_dir=$share_dir/frontend
|
||
admin_frontend_dir=$share_dir/admin
|
||
agent_installer_dir=$share_dir/agent
|
||
|
||
usage() {
|
||
printf '%s\n' \
|
||
'NodeFlare 面板安装脚本' \
|
||
'' \
|
||
'用法:' \
|
||
' sudo sh install.sh' \
|
||
' sudo sh install.sh --uninstall' \
|
||
' sudo sh install.sh --uninstall --purge' \
|
||
'' \
|
||
'首次安装会询问管理员用户名、密码和数据库连接。' \
|
||
'安装和更新均使用 GitHub latest Release,并保留已有配置和数据库。' \
|
||
'--uninstall 保留配置和数据;只有同时指定 --purge 才彻底删除。'
|
||
}
|
||
|
||
log() {
|
||
printf '[NodeFlare] %s\n' "$1"
|
||
}
|
||
|
||
fail() {
|
||
printf '[NodeFlare] 错误:%s\n' "$1" >&2
|
||
exit 1
|
||
}
|
||
|
||
detect_glibc_version() {
|
||
[ -x "$glibc_loader" ] || return
|
||
LC_ALL=C "$glibc_loader" --version 2>/dev/null | awk '
|
||
tolower($0) ~ /glibc|gnu libc|gnu c library/ {
|
||
sub(/\.$/, "")
|
||
if (match($0, /[0-9]+\.[0-9]+(\.[0-9]+)?$/)) {
|
||
print substr($0, RSTART, RLENGTH)
|
||
exit
|
||
}
|
||
}
|
||
'
|
||
}
|
||
|
||
glibc_is_supported() {
|
||
glibc_major=${1%%.*}
|
||
glibc_minor=${1#*.}
|
||
glibc_minor=${glibc_minor%%.*}
|
||
case "$glibc_major:$glibc_minor" in
|
||
*[!0-9:]*|:*) return 1 ;;
|
||
esac
|
||
[ "$glibc_major" -gt 2 ] \
|
||
|| { [ "$glibc_major" -eq 2 ] && [ "$glibc_minor" -ge 28 ]; }
|
||
}
|
||
|
||
detect_init_system() {
|
||
case "$platform" in
|
||
linux)
|
||
if [ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1; then
|
||
printf systemd
|
||
elif command -v rc-service >/dev/null 2>&1 && [ -d /etc/init.d ]; then
|
||
printf openrc
|
||
else
|
||
printf unknown
|
||
fi
|
||
;;
|
||
macos) command -v launchctl >/dev/null 2>&1 && printf launchd || printf unknown ;;
|
||
freebsd)
|
||
command -v service >/dev/null 2>&1 && command -v sysrc >/dev/null 2>&1 \
|
||
&& printf freebsd || printf unknown
|
||
;;
|
||
esac
|
||
}
|
||
|
||
restore_tty() {
|
||
if [ -n "$tty_state" ]; then
|
||
stty "$tty_state" < /dev/tty 2>/dev/null || true
|
||
tty_state=""
|
||
fi
|
||
}
|
||
|
||
cleanup() {
|
||
restore_tty
|
||
if [ "$rollback_ready" = true ]; then
|
||
rollback_install
|
||
fi
|
||
[ -z "$config_temp" ] || rm -f "$config_temp"
|
||
[ -z "$download_dir" ] || rm -rf "$download_dir"
|
||
}
|
||
|
||
trap cleanup EXIT HUP INT TERM
|
||
|
||
toml_escape() {
|
||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
||
}
|
||
|
||
prompt_line() {
|
||
printf '%s' "$1" > /dev/tty
|
||
IFS= read -r prompt_value < /dev/tty || fail "无法读取输入"
|
||
}
|
||
|
||
prompt_secret() {
|
||
printf '%s' "$1" > /dev/tty
|
||
tty_state=$(stty -g < /dev/tty) || fail "无法读取终端状态"
|
||
stty -echo < /dev/tty
|
||
if ! IFS= read -r prompt_value < /dev/tty; then
|
||
restore_tty
|
||
printf '\n' > /dev/tty
|
||
fail "无法读取密码"
|
||
fi
|
||
restore_tty
|
||
printf '\n' > /dev/tty
|
||
}
|
||
|
||
prompt_credentials() {
|
||
[ -r /dev/tty ] && [ -w /dev/tty ] || fail "首次安装必须在交互式终端中运行"
|
||
while :; do
|
||
prompt_line "管理员用户名 [admin]: "
|
||
admin_username=${prompt_value:-admin}
|
||
username_length=$(printf '%s' "$admin_username" | wc -m | tr -d '[:space:]')
|
||
case "$admin_username" in
|
||
*[!A-Za-z0-9_.-]*) printf '%s\n' "用户名只能包含字母、数字、点、下划线和连字符。" > /dev/tty ;;
|
||
*) [ "$username_length" -le 64 ] && break || printf '%s\n' "用户名不能超过 64 个字符。" > /dev/tty ;;
|
||
esac
|
||
done
|
||
|
||
while :; do
|
||
prompt_secret "管理员密码(8-128 个字符): "
|
||
admin_password=$prompt_value
|
||
password_length=$(printf '%s' "$admin_password" | wc -m | tr -d '[:space:]')
|
||
if [ "$password_length" -lt 8 ] || [ "$password_length" -gt 128 ]; then
|
||
printf '%s\n' "密码长度必须在 8-128 个字符之间。" > /dev/tty
|
||
continue
|
||
fi
|
||
prompt_secret "再次输入密码: "
|
||
if [ "$admin_password" = "$prompt_value" ]; then
|
||
break
|
||
fi
|
||
printf '%s\n' "两次输入的密码不一致。" > /dev/tty
|
||
done
|
||
}
|
||
|
||
prompt_database() {
|
||
printf '%s\n' \
|
||
'SQLite:sqlite:///path/to/database.db' \
|
||
'PostgreSQL:postgres://用户:密码@ip:端口/数据库?sslmode=require' > /dev/tty
|
||
while :; do
|
||
prompt_line "数据库 URL [$default_database_url]: "
|
||
database_url=${prompt_value:-$default_database_url}
|
||
case "$database_url" in
|
||
sqlite://*|postgres://*|postgresql://*) break ;;
|
||
*) printf '%s\n' "数据库 URL 格式无效。" > /dev/tty ;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
write_config() {
|
||
escaped_database_url=$(toml_escape "$database_url")
|
||
escaped_username=$(toml_escape "$admin_username")
|
||
escaped_password=$(toml_escape "$admin_password")
|
||
escaped_frontend_dir=$(toml_escape "$public_frontend_dir")
|
||
escaped_admin_dir=$(toml_escape "$admin_frontend_dir")
|
||
escaped_agent_dir=$(toml_escape "$agent_installer_dir")
|
||
escaped_theme_dir=$(toml_escape "$theme_dir")
|
||
config_temp=$(mktemp "$config_dir/.config.toml.XXXXXX")
|
||
{
|
||
printf 'database_url = "%s"\n' "$escaped_database_url"
|
||
printf 'bind_addr = "127.0.0.1:8080"\n'
|
||
printf 'trusted_proxies = ["127.0.0.1/32", "::1/128"]\n'
|
||
printf 'admin_username = "%s"\n' "$escaped_username"
|
||
printf 'admin_password = "%s"\n' "$escaped_password"
|
||
printf 'turnstile_site_key = ""\n'
|
||
printf 'turnstile_secret_key = ""\n'
|
||
printf 'frontend_dir = "%s"\n' "$escaped_frontend_dir"
|
||
printf 'admin_frontend_dir = "%s"\n' "$escaped_admin_dir"
|
||
printf 'agent_dir = "%s"\n' "$escaped_agent_dir"
|
||
printf 'theme_dir = "%s"\n' "$escaped_theme_dir"
|
||
printf 'session_ttl_hours = 168\n'
|
||
} > "$config_temp"
|
||
chown root "$config_temp"
|
||
chmod 0600 "$config_temp"
|
||
mv -f "$config_temp" "$config_file"
|
||
config_temp=""
|
||
admin_password=""
|
||
database_url=""
|
||
prompt_value=""
|
||
}
|
||
|
||
download_file() {
|
||
url=$1
|
||
destination=$2
|
||
timeout=$3
|
||
if command -v curl >/dev/null 2>&1; then
|
||
curl --proto '=https' --proto-redir '=https' --tlsv1.2 \
|
||
--fail --location --silent --show-error --max-time "$timeout" \
|
||
"$url" -o "$destination"
|
||
else
|
||
fetch -q -T "$timeout" -o "$destination" "$url"
|
||
fi
|
||
}
|
||
|
||
download_stdout() {
|
||
url=$1
|
||
if command -v curl >/dev/null 2>&1; then
|
||
curl --proto '=https' --proto-redir '=https' --tlsv1.2 \
|
||
--fail --location --silent --show-error --max-time 30 \
|
||
-H 'Accept: application/vnd.github+json' \
|
||
-H 'User-Agent: nodeflare-installer' \
|
||
"$url"
|
||
else
|
||
fetch -q -T 30 -o - "$url"
|
||
fi
|
||
}
|
||
|
||
verify_checksum() {
|
||
case "$platform" in
|
||
linux) actual=$(sha256sum "$archive" | sed 's/[[:space:]].*//') ;;
|
||
macos) actual=$(shasum -a 256 "$archive" | sed 's/[[:space:]].*//') ;;
|
||
freebsd) actual=$(sha256 -q "$archive") ;;
|
||
esac
|
||
[ "$actual" = "$expected" ] || fail "Release SHA-256 校验失败"
|
||
}
|
||
|
||
download_release() {
|
||
case "$platform:$(uname -m)" in
|
||
linux:x86_64|linux:amd64) arch=x64; glibc_loader=/lib64/ld-linux-x86-64.so.2 ;;
|
||
linux:aarch64|linux:arm64) arch=aarch64; glibc_loader=/lib/ld-linux-aarch64.so.1 ;;
|
||
macos:arm64|macos:aarch64) arch=aarch64 ;;
|
||
freebsd:amd64|freebsd:x86_64) arch=x64 ;;
|
||
freebsd:arm64|freebsd:aarch64) arch=aarch64 ;;
|
||
*) fail "暂不支持当前 CPU 架构:$(uname -m)" ;;
|
||
esac
|
||
|
||
case "$platform" in
|
||
linux)
|
||
libc=musl
|
||
glibc_version=$(detect_glibc_version || true)
|
||
if [ -n "$glibc_version" ] && glibc_is_supported "$glibc_version"; then
|
||
libc=glibc
|
||
fi
|
||
asset="nodeflare-server-linux-$arch-$libc.tar.gz"
|
||
release_label="$platform $arch $libc"
|
||
;;
|
||
macos)
|
||
asset=nodeflare-server-macos-aarch64.tar.gz
|
||
release_label="macOS ARM64"
|
||
;;
|
||
freebsd)
|
||
asset="nodeflare-server-freebsd-$arch.tar.gz"
|
||
release_label="FreeBSD $arch"
|
||
;;
|
||
esac
|
||
|
||
download_dir=$(mktemp -d "${TMPDIR:-/tmp}/nodeflare-install.XXXXXX")
|
||
archive=$download_dir/$asset
|
||
release_api="https://api.github.com/repos/$repository/releases/latest"
|
||
log "获取 latest Release ($release_label)"
|
||
release_json=$(download_stdout "$release_api")
|
||
release_tag=$(printf '%s\n' "$release_json" | tr ',' '\n' | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')
|
||
printf '%s\n' "$release_tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' \
|
||
|| fail "GitHub 最新 Release 标签无效"
|
||
expected=$(printf '%s\n' "$release_json" | tr '{' '\n' | awk -v name="$asset" '
|
||
{
|
||
compact = $0
|
||
gsub(/[[:space:]]/, "", compact)
|
||
if (index(compact, "\"name\":\"" name "\"") > 0) selected = 1
|
||
else if (index(compact, "\"name\":") > 0) selected = 0
|
||
}
|
||
selected {
|
||
marker = "\"digest\":\"sha256:"
|
||
position = index(compact, marker)
|
||
if (position == 0) next
|
||
digest = substr(compact, position + length(marker), 64)
|
||
if (length(digest) == 64 && digest !~ /[^0-9a-fA-F]/) {
|
||
print tolower(digest)
|
||
exit
|
||
}
|
||
}
|
||
')
|
||
[ -n "$expected" ] || fail "Release 缺少 $asset 的 SHA-256 摘要"
|
||
release_base="https://github.com/$repository/releases/download/$release_tag"
|
||
log "下载 NodeFlare $release_tag ($release_label)"
|
||
download_file "$release_base/$asset" "$archive" 120
|
||
verify_checksum
|
||
|
||
package_dir=$download_dir/package
|
||
mkdir -p "$package_dir"
|
||
tar -xzf "$archive" -C "$package_dir"
|
||
[ -f "$package_dir/nodeflare" ] \
|
||
&& [ -f "$package_dir/share/frontend/index.html" ] \
|
||
&& [ -f "$package_dir/share/admin/admin.html" ] \
|
||
&& [ -f "$package_dir/share/agent/agent.sh" ] \
|
||
&& [ -f "$package_dir/LICENSE" ] \
|
||
|| fail "Release 文件不完整"
|
||
chmod 0755 "$package_dir/nodeflare"
|
||
binary_version=$("$package_dir/nodeflare" --version) || fail "服务端文件无法运行"
|
||
release_version=${binary_version##* }
|
||
printf '%s\n' "$release_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' \
|
||
|| fail "服务端返回了无效版本号"
|
||
[ "$release_version" = "${release_tag#v}" ] \
|
||
|| fail "Release $release_tag 与服务端版本 $release_version 不一致"
|
||
}
|
||
|
||
write_openrc_service() {
|
||
cat > "$openrc_file" <<EOF
|
||
#!/sbin/openrc-run
|
||
name="NodeFlare"
|
||
command="$server_binary"
|
||
command_args="--config $config_file"
|
||
command_user="root"
|
||
supervisor="supervise-daemon"
|
||
respawn_delay=5
|
||
depend() { need net; }
|
||
EOF
|
||
chmod 0755 "$openrc_file"
|
||
}
|
||
|
||
write_launchd_service() {
|
||
cat > "$launchd_file" <<EOF
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0"><dict>
|
||
<key>Label</key><string>nodeflare</string>
|
||
<key>ProgramArguments</key><array><string>$server_binary</string><string>--config</string><string>$config_file</string></array>
|
||
<key>WorkingDirectory</key><string>$config_dir</string>
|
||
<key>KeepAlive</key><true/><key>RunAtLoad</key><true/>
|
||
<key>StandardOutPath</key><string>/var/log/nodeflare.log</string>
|
||
<key>StandardErrorPath</key><string>/var/log/nodeflare.log</string>
|
||
</dict></plist>
|
||
EOF
|
||
chmod 0600 "$launchd_file"
|
||
}
|
||
|
||
write_freebsd_service() {
|
||
cat > "$freebsd_rc_file" <<EOF
|
||
#!/bin/sh
|
||
# PROVIDE: nodeflare
|
||
# REQUIRE: NETWORKING
|
||
# KEYWORD: shutdown
|
||
. /etc/rc.subr
|
||
name="nodeflare"
|
||
rcvar="nodeflare_enable"
|
||
pidfile="/var/run/\${name}.pid"
|
||
command="/usr/sbin/daemon"
|
||
command_args="-P \${pidfile} -r -R 5 -S -T \${name} $server_binary --config $config_file"
|
||
load_rc_config "\${name}"
|
||
: \${nodeflare_enable:="NO"}
|
||
run_rc_command "\$1"
|
||
EOF
|
||
chmod 0755 "$freebsd_rc_file"
|
||
}
|
||
|
||
stop_server() {
|
||
case "$init_system" in
|
||
systemd) systemctl stop nodeflare.service >/dev/null 2>&1 || true ;;
|
||
openrc) rc-service nodeflare stop >/dev/null 2>&1 || true ;;
|
||
launchd) launchctl bootout system "$launchd_file" >/dev/null 2>&1 || true ;;
|
||
freebsd) service nodeflare stop >/dev/null 2>&1 || true ;;
|
||
esac
|
||
}
|
||
|
||
service_definition() {
|
||
case "$init_system" in
|
||
systemd) printf '%s' "$systemd_file" ;;
|
||
openrc) printf '%s' "$openrc_file" ;;
|
||
launchd) printf '%s' "$launchd_file" ;;
|
||
freebsd) printf '%s' "$freebsd_rc_file" ;;
|
||
esac
|
||
}
|
||
|
||
snapshot_install() {
|
||
backup_dir=$download_dir/previous
|
||
mkdir -p "$backup_dir"
|
||
if [ -d "$install_dir" ]; then
|
||
cp -Rp "$install_dir" "$backup_dir/install"
|
||
previous_install=true
|
||
fi
|
||
if [ "$share_dir" != "$install_dir/share" ] && [ -d "$share_dir" ]; then
|
||
cp -Rp "$share_dir" "$backup_dir/share"
|
||
previous_share=true
|
||
fi
|
||
service_path=$(service_definition)
|
||
if [ -f "$service_path" ]; then
|
||
cp -p "$service_path" "$backup_dir/service"
|
||
previous_service=true
|
||
fi
|
||
rollback_ready=true
|
||
}
|
||
|
||
rollback_install() {
|
||
rollback_ready=false
|
||
log "安装未完成,正在恢复上一版本"
|
||
stop_server
|
||
rm -rf "$install_dir"
|
||
if [ "$previous_install" = true ]; then
|
||
cp -Rp "$backup_dir/install" "$install_dir"
|
||
fi
|
||
if [ "$share_dir" != "$install_dir/share" ]; then
|
||
rm -rf "$share_dir"
|
||
if [ "$previous_share" = true ]; then
|
||
cp -Rp "$backup_dir/share" "$share_dir"
|
||
fi
|
||
fi
|
||
service_path=$(service_definition)
|
||
rm -f "$service_path"
|
||
if [ "$previous_service" = true ]; then
|
||
cp -p "$backup_dir/service" "$service_path"
|
||
fi
|
||
if [ "$previous_install" = true ] && [ -f "$config_file" ]; then
|
||
if start_server; then
|
||
log "已恢复并重新启动上一版本"
|
||
else
|
||
printf '[NodeFlare] 警告:上一版本已恢复,但服务未能自动启动\n' >&2
|
||
fi
|
||
fi
|
||
}
|
||
|
||
install_service() {
|
||
case "$init_system" in
|
||
systemd) install -m 0644 "$package_dir/nodeflare.service" "$systemd_file" ;;
|
||
openrc) write_openrc_service ;;
|
||
launchd) write_launchd_service ;;
|
||
freebsd) write_freebsd_service ;;
|
||
esac
|
||
}
|
||
|
||
start_server() {
|
||
case "$init_system" in
|
||
systemd)
|
||
systemctl daemon-reload
|
||
systemctl enable nodeflare.service >/dev/null
|
||
systemctl restart nodeflare.service
|
||
systemctl is-active --quiet nodeflare.service
|
||
;;
|
||
openrc)
|
||
rc-update add nodeflare default >/dev/null
|
||
rc-service nodeflare start
|
||
rc-service nodeflare status >/dev/null
|
||
;;
|
||
launchd)
|
||
launchctl bootstrap system "$launchd_file"
|
||
launchctl print system/nodeflare >/dev/null
|
||
;;
|
||
freebsd)
|
||
sysrc nodeflare_enable=YES >/dev/null
|
||
service nodeflare start
|
||
service nodeflare status >/dev/null
|
||
;;
|
||
esac
|
||
}
|
||
|
||
uninstall_server() {
|
||
purge=$1
|
||
log "停止并移除 NodeFlare 面板服务"
|
||
stop_server
|
||
case "$init_system" in
|
||
systemd)
|
||
systemctl disable nodeflare.service >/dev/null 2>&1 || true
|
||
;;
|
||
openrc)
|
||
rc-update del nodeflare default >/dev/null 2>&1 || true
|
||
;;
|
||
launchd) ;;
|
||
freebsd)
|
||
sysrc -x nodeflare_enable >/dev/null 2>&1 || true
|
||
;;
|
||
esac
|
||
case "$platform" in
|
||
linux)
|
||
rm -f "$systemd_file" "$openrc_file"
|
||
command -v systemctl >/dev/null 2>&1 && systemctl daemon-reload >/dev/null 2>&1 || true
|
||
;;
|
||
macos) rm -f "$launchd_file" ;;
|
||
freebsd) rm -f "$freebsd_rc_file" ;;
|
||
esac
|
||
rm -f "$server_binary"
|
||
rm -rf "$share_dir"
|
||
rmdir "$install_dir" 2>/dev/null || true
|
||
|
||
if [ "$purge" = true ]; then
|
||
rm -rf "$config_dir"
|
||
log "已删除配置和全部服务端持久数据"
|
||
else
|
||
log "已保留配置和服务端持久数据:$config_dir"
|
||
fi
|
||
log "卸载完成"
|
||
}
|
||
|
||
mode=install
|
||
purge=false
|
||
case "$#:${1:-}:${2:-}" in
|
||
0::) ;;
|
||
1:-h:|1:--help:) usage; exit 0 ;;
|
||
1:--uninstall:) mode=uninstall ;;
|
||
2:--uninstall:--purge) mode=uninstall; purge=true ;;
|
||
*) usage >&2; fail "参数无效" ;;
|
||
esac
|
||
|
||
[ "$(id -u)" -eq 0 ] || fail "请使用 root 权限运行"
|
||
init_system=$(detect_init_system)
|
||
if [ "$mode" = uninstall ]; then
|
||
uninstall_server "$purge"
|
||
exit 0
|
||
fi
|
||
[ "$init_system" != unknown ] || fail "未检测到受支持的服务管理器"
|
||
|
||
for required_command in tar install mktemp sed grep wc tr id chown chmod mv cp stty uname cat; do
|
||
command -v "$required_command" >/dev/null 2>&1 || fail "缺少命令:$required_command"
|
||
done
|
||
case "$platform" in
|
||
linux)
|
||
command -v curl >/dev/null 2>&1 || fail "缺少命令:curl"
|
||
command -v sha256sum >/dev/null 2>&1 || fail "缺少命令:sha256sum"
|
||
;;
|
||
macos)
|
||
if ! command -v curl >/dev/null 2>&1 || ! command -v shasum >/dev/null 2>&1; then
|
||
fail "缺少 curl 或 shasum"
|
||
fi
|
||
;;
|
||
freebsd)
|
||
command -v fetch >/dev/null 2>&1 || command -v curl >/dev/null 2>&1 || fail "缺少 fetch 或 curl"
|
||
command -v sha256 >/dev/null 2>&1 || fail "缺少命令:sha256"
|
||
;;
|
||
esac
|
||
|
||
download_release
|
||
|
||
new_config=false
|
||
if [ ! -f "$config_file" ]; then
|
||
new_config=true
|
||
prompt_credentials
|
||
prompt_database
|
||
else
|
||
log "保留已有配置:$config_file"
|
||
fi
|
||
|
||
log "安装 NodeFlare $release_version"
|
||
snapshot_install
|
||
stop_server
|
||
install -d -m 0700 "$config_dir" "$theme_dir"
|
||
install -d -m 0755 "$install_dir" "$share_dir"
|
||
rm -rf "$public_frontend_dir" "$admin_frontend_dir" "$agent_installer_dir"
|
||
install -d -m 0755 "$public_frontend_dir" "$admin_frontend_dir" "$agent_installer_dir"
|
||
cp -R "$package_dir/share/frontend/." "$public_frontend_dir/"
|
||
cp -R "$package_dir/share/admin/." "$admin_frontend_dir/"
|
||
cp -R "$package_dir/share/agent/." "$agent_installer_dir/"
|
||
chmod 0755 "$agent_installer_dir/"*.sh
|
||
chmod 0644 "$agent_installer_dir/install.ps1"
|
||
install -m 0755 "$package_dir/nodeflare" "$server_binary"
|
||
install_service
|
||
|
||
if [ "$new_config" = true ]; then
|
||
write_config
|
||
else
|
||
chown root "$config_file"
|
||
chmod 0600 "$config_file"
|
||
fi
|
||
|
||
if ! start_server; then
|
||
fail "NodeFlare 服务启动失败"
|
||
fi
|
||
rollback_ready=false
|
||
|
||
log "安装完成"
|
||
printf '%s\n' \
|
||
"版本:$release_version" \
|
||
"配置和数据:$config_dir" \
|
||
"程序:$server_binary" \
|
||
"服务系统:$init_system" \
|
||
'默认访问:http://127.0.0.1:8080'
|