mirror of
https://github.com/snltty/linker.git
synced 2025-09-26 21:15:57 +08:00
安装脚本
This commit is contained in:
@@ -107,11 +107,13 @@ namespace linker.messenger.flow
|
||||
|
||||
private void OnlineTask()
|
||||
{
|
||||
UdpClient udpClient = new UdpClient(AddressFamily.InterNetwork);
|
||||
udpClient.Client.WindowsUdpBug();
|
||||
TimerHelper.SetIntervalLong(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Report();
|
||||
Report(udpClient);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -122,7 +124,7 @@ namespace linker.messenger.flow
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
private void Report()
|
||||
private void Report(UdpClient udpClient)
|
||||
{
|
||||
List<FlowReportNetInfo> nets = signCaching.Get().Where(c => c.Args.ContainsKey("tunnelNet")).Select(c => c.Args["tunnelNet"].DeJson<SignInArgsNetInfo>()).GroupBy(c => c.City).Select(c => new FlowReportNetInfo
|
||||
{
|
||||
@@ -139,16 +141,11 @@ namespace linker.messenger.flow
|
||||
total.ToBytes(buffer.Slice(5));
|
||||
netBytes.CopyTo(buffer.Slice(9));
|
||||
|
||||
using UdpClient udpClient = new UdpClient(AddressFamily.InterNetwork);
|
||||
udpClient.Client.WindowsUdpBug();
|
||||
|
||||
string domain = "linker.snltty.com";
|
||||
#if DEBUG
|
||||
domain = "127.0.0.1";
|
||||
#endif
|
||||
udpClient.Send(buffer.Slice(0, 9 + netBytes.Length), domain, 1802);
|
||||
|
||||
udpClient.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -201,7 +201,7 @@ namespace linker.plugins.sforward.proxy
|
||||
serverUdp.WindowsUdpBug();
|
||||
|
||||
using IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent(65535);
|
||||
|
||||
//回复服务器
|
||||
flagBytes.AsMemory().CopyTo(buffer.Memory);
|
||||
id.ToBytes(buffer.Memory.Slice(flagBytes.Length));
|
||||
await serverUdp.SendToAsync(buffer.Memory, server).ConfigureAwait(false);
|
||||
|
@@ -7,6 +7,7 @@ using linker.messenger.relay.client;
|
||||
using linker.messenger.signin;
|
||||
using linker.messenger.pcp;
|
||||
using linker.messenger.tuntap.cidr;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace linker.messenger.tuntap
|
||||
{
|
||||
@@ -120,11 +121,12 @@ namespace linker.messenger.tuntap
|
||||
}, ip);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ushort ackLength = 0;
|
||||
if (connection.PacketBuffer.Length > 0 && fakeAckTransfer.Read(packet.IPPacket, connection.PacketBuffer, connection.SendBufferFree, out ackLength)) return;
|
||||
await connection.SendAsync(packet.Buffer, packet.Offset, packet.Length).ConfigureAwait(false);
|
||||
if (ackLength > 0) Callback.Receive(connection, connection.PacketBuffer.AsMemory(0, ackLength));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using linker.libs;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
@@ -19,7 +18,7 @@ namespace linker.tun
|
||||
/// </summary>
|
||||
/// <param name="packet">一个完整的TCP/IP包</param>
|
||||
/// <param name="fakeBuffer">一个能容纳ACK包的缓冲区,如果需要伪造ACK则写入到这里</param>
|
||||
/// <param name="bufferFree">窗口大小</param>
|
||||
/// <param name="bufferFree">缓冲区可用字节数,会根据这个来计算ack的窗口大小</param>
|
||||
/// <param name="fakeLength">ack包长度</param>
|
||||
/// <returns>是否丢包</returns>
|
||||
public bool Read(ReadOnlyMemory<byte> packet, ReadOnlyMemory<byte> fakeBuffer, long bufferFree, out ushort fakeLength)
|
||||
@@ -77,6 +76,7 @@ namespace linker.tun
|
||||
FaceAckKey key = new() { srcAddr = originPacket.SrcAddr, srcPort = originPacket.SrcPort, dstAddr = originPacket.DstAddr, dstPort = originPacket.DstPort };
|
||||
|
||||
/*
|
||||
//更新序列号
|
||||
if (originPacket.TcpFlagAck && dic.TryGetValue(key, out FackAckState state))
|
||||
{
|
||||
state.Cq = originPacket.Cq;
|
||||
@@ -283,28 +283,41 @@ namespace linker.tun
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从TCP的SYN包或SYN+ACK包中,获取窗口缩放比例
|
||||
/// </summary>
|
||||
/// <param name="ipPtr">一个完整TCP/IP包</param>
|
||||
/// <returns></returns>
|
||||
public int FindOptionWindowScale(byte* ipPtr)
|
||||
{
|
||||
//指针移动到TCP头开始位置
|
||||
byte* tcpPtr = ipPtr + ((*ipPtr & 0b1111) * 4);
|
||||
|
||||
int index = 20, end = (*(tcpPtr + 12) >> 4) * 4;
|
||||
//tcp头固定20,所以option从这里开始
|
||||
int index = 20;
|
||||
//tcp头结束位置,就是option结束位置
|
||||
int end = (*(tcpPtr + 12) >> 4) * 4;
|
||||
while (index < end)
|
||||
{
|
||||
byte kind = *(tcpPtr + index);
|
||||
if (kind == 0) break;
|
||||
//EOF结束符
|
||||
if (kind == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
byte length = *(tcpPtr + index + 1);
|
||||
//NOP 空选项
|
||||
if (kind == 1)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
//Window Scale 1kind 1length 1shiftCount
|
||||
else if (kind == 3 && length == 3)
|
||||
{
|
||||
byte shiftCount = *(tcpPtr + index + 2);
|
||||
if (shiftCount > 14) break;
|
||||
|
||||
return 1 << shiftCount;
|
||||
return shiftCount > 14 ? 1 : 1 << shiftCount;
|
||||
}
|
||||
index += length;
|
||||
}
|
||||
|
@@ -46,17 +46,17 @@ namespace linker.tunnel.connection
|
||||
|
||||
public bool Receive { get; init; }
|
||||
|
||||
public Socket uUdpClient;
|
||||
public Socket udpClient;
|
||||
[JsonIgnore]
|
||||
public Socket UdpClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return uUdpClient;
|
||||
return udpClient;
|
||||
}
|
||||
init
|
||||
{
|
||||
uUdpClient = value;
|
||||
udpClient = value;
|
||||
}
|
||||
}
|
||||
[JsonIgnore]
|
||||
@@ -331,7 +331,7 @@ namespace linker.tunnel.connection
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (uUdpClient == null) return;
|
||||
if (udpClient == null) return;
|
||||
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
LoggerHelper.Instance.Error($"tunnel connection {this.GetHashCode()} writer offline {ToString()}");
|
||||
@@ -343,7 +343,7 @@ namespace linker.tunnel.connection
|
||||
LastTicks.Clear();
|
||||
if (Receive == true)
|
||||
UdpClient?.SafeClose();
|
||||
uUdpClient = null;
|
||||
udpClient = null;
|
||||
|
||||
cancellationTokenSource?.Cancel();
|
||||
callback?.Closed(this, userToken);
|
||||
|
@@ -1,21 +1,262 @@
|
||||
#!/bin/bash
|
||||
|
||||
LINKER_DOWNLOAD_URL="https://static.snltty.com/downloads/linker"
|
||||
LINKER_DOWNLOAD_VERSION=""
|
||||
LINKER_FILE_NAME="linker-linux-"
|
||||
# Linker 安装管理脚本
|
||||
set -e
|
||||
|
||||
LINKER_INSTALL_PATH=$1
|
||||
if [ -z "$LINKER_INSTALL_PATH" ]; then
|
||||
LINKER_INSTALL_PATH="/usr/local/bin"
|
||||
fi
|
||||
echo -e "安装位置:${LINKER_INSTALL_PATH}"
|
||||
# 配置变量
|
||||
BASE_URL="https://static.snltty.com/downloads/linker"
|
||||
VERSION_URL="${BASE_URL}/version.txt"
|
||||
TEMP_DIR="/tmp/linker-install"
|
||||
INSTALL_DIR="/usr/local/bin/linker"
|
||||
SERVICE_NAME="linker"
|
||||
DOCKER_IMAGE="snltty/linker-musl"
|
||||
DOCKER_CONTAINER="linker"
|
||||
|
||||
red='\033[0;31m'
|
||||
green='\033[0;32m'
|
||||
yellow='\033[0;33m'
|
||||
plain='\033[0m'
|
||||
export PATH=$PATH:/usr/local/bin
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 输出彩色信息
|
||||
info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
debug() {
|
||||
echo -e "${BLUE}[DEBUG]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查命令是否存在
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# 检测系统架构
|
||||
detect_arch() {
|
||||
local arch
|
||||
arch=$(uname -m)
|
||||
case "$arch" in
|
||||
x86_64|amd64)
|
||||
echo "x64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
echo "arm64"
|
||||
;;
|
||||
armv7l|armv8l|arm)
|
||||
echo "arm"
|
||||
;;
|
||||
*)
|
||||
error "不支持的架构: $arch"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 检测libc类型
|
||||
detect_libc() {
|
||||
if ldd --version 2>&1 | grep -q musl; then
|
||||
echo "musl"
|
||||
elif [ -f /etc/alpine-release ]; then
|
||||
echo "musl"
|
||||
else
|
||||
echo "gnu"
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取最新版本号
|
||||
get_latest_version() {
|
||||
if command_exists curl; then
|
||||
curl -sSL --connect-timeout 10 "$VERSION_URL" | head -n 1 | tr -d '\r'
|
||||
elif command_exists wget; then
|
||||
wget -qO- --timeout=10 "$VERSION_URL" | head -n 1 | tr -d '\r'
|
||||
else
|
||||
error "需要 curl 或 wget 来获取版本信息"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 安装Docker
|
||||
install_docker() {
|
||||
info "开始安装Docker..."
|
||||
|
||||
if command_exists docker; then
|
||||
info "Docker 已经安装"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 使用官方Docker安装脚本
|
||||
if command_exists curl; then
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
elif command_exists wget; then
|
||||
wget -q https://get.docker.com -O get-docker.sh
|
||||
else
|
||||
error "需要 curl 或 wget 来下载Docker安装脚本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装Docker
|
||||
chmod +x get-docker.sh
|
||||
sh get-docker.sh
|
||||
|
||||
# 启动Docker服务
|
||||
if command_exists systemctl; then
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
elif command_exists service; then
|
||||
service docker start
|
||||
fi
|
||||
|
||||
# 验证Docker安装
|
||||
if docker --version; then
|
||||
info "Docker 安装成功"
|
||||
rm -f get-docker.sh
|
||||
return 0
|
||||
else
|
||||
error "Docker 安装失败"
|
||||
rm -f get-docker.sh
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查Docker容器是否存在
|
||||
docker_container_exists() {
|
||||
docker ps -a --filter "name=^/${DOCKER_CONTAINER}$" --format '{{.Names}}' | grep -q "^${DOCKER_CONTAINER}$"
|
||||
}
|
||||
|
||||
# 设置Docker容器
|
||||
setup_docker() {
|
||||
info "配置Docker容器..."
|
||||
|
||||
# 安装Docker(如果不存在)
|
||||
if ! command_exists docker; then
|
||||
warn "Docker 未安装,开始自动安装..."
|
||||
if ! install_docker; then
|
||||
error "Docker 安装失败,无法继续Docker安装"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查容器是否已存在
|
||||
if docker_container_exists; then
|
||||
error "Docker容器 ${DOCKER_CONTAINER} 已存在,请先卸载或删除现有容器"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "拉取Docker镜像: ${DOCKER_IMAGE}"
|
||||
if ! docker pull "${DOCKER_IMAGE}"; then
|
||||
error "拉取Docker镜像失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建并运行容器
|
||||
info "创建并启动Docker容器: ${DOCKER_CONTAINER}"
|
||||
|
||||
local docker_run_cmd="docker run -it -d \
|
||||
--name ${DOCKER_CONTAINER} \
|
||||
-v $INSTALL_DIR/configs:/app/configs \
|
||||
-v $INSTALL_DIR/logs:/app/logs \
|
||||
---device /dev/net/tun \
|
||||
--restart=always \
|
||||
--privileged=true \
|
||||
--network host \
|
||||
${DOCKER_IMAGE}"
|
||||
|
||||
debug "执行命令: ${docker_run_cmd}"
|
||||
|
||||
if eval "${docker_run_cmd}"; then
|
||||
info "Docker容器创建成功"
|
||||
|
||||
# 等待容器启动
|
||||
sleep 3
|
||||
|
||||
# 检查容器状态
|
||||
if docker ps --filter "name=^/${DOCKER_CONTAINER}$" --format '{{.Status}}' | grep -q "Up"; then
|
||||
info "Docker容器运行正常"
|
||||
else
|
||||
warn "容器已创建但可能未正常运行,请检查日志: docker logs ${DOCKER_CONTAINER}"
|
||||
fi
|
||||
else
|
||||
error "Docker容器创建失败"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 下载并安装Linker
|
||||
download_install() {
|
||||
local version=$1
|
||||
local arch=$2
|
||||
local libc=$3
|
||||
|
||||
# 确定文件名
|
||||
local filename="${SERVICE_NAME}-linux-${arch}.zip"
|
||||
local pathname="${SERVICE_NAME}-linux-${arch}"
|
||||
|
||||
os_alpine="0"
|
||||
[ -e /etc/os-release ] && cat /etc/os-release | grep -i "PRETTY_NAME" | grep -qi "alpine" && os_alpine='1'
|
||||
if [ "$os_alpine" == 1 ]; then
|
||||
filename="${SERVICE_NAME}-linux-musl-${arch}.zip"
|
||||
pathname="${SERVICE_NAME}-linux-musl-${arch}"
|
||||
fi
|
||||
|
||||
local download_url="${BASE_URL}/${version}/${filename}"
|
||||
|
||||
info "下载链接: $download_url"
|
||||
|
||||
# 创建临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# 下载文件
|
||||
if command_exists curl; then
|
||||
if ! curl -sSL --connect-timeout 30 -o "${TEMP_DIR}/${filename}" "$download_url"; then
|
||||
error "下载失败: $download_url"
|
||||
exit 1
|
||||
fi
|
||||
elif command_exists wget; then
|
||||
if ! wget -q --timeout=30 -O "${TEMP_DIR}/${filename}" "$download_url"; then
|
||||
error "下载失败: $download_url"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
error "需要 curl 或 wget 来下载文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查下载是否成功
|
||||
if [ ! -f "${TEMP_DIR}/${filename}" ]; then
|
||||
error "下载失败,文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 解压文件
|
||||
if command_exists unzip; then
|
||||
if ! unzip -qo "${TEMP_DIR}/${filename}" -d "$TEMP_DIR"; then
|
||||
error "解压失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
error "需要 unzip 来解压文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装到系统目录
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cp -r "${TEMP_DIR}/${pathname}/." "$INSTALL_DIR/"
|
||||
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
info "Linker 已安装到 ${INSTALL_DIR}/"
|
||||
}
|
||||
|
||||
install_base() {
|
||||
(command -v git >/dev/null 2>&1 && command -v curl >/dev/null 2>&1 && command -v wget >/dev/null 2>&1 && command -v unzip >/dev/null 2>&1 && command -v getenforce >/dev/null 2>&1) ||
|
||||
@@ -28,204 +269,283 @@ install_soft() {
|
||||
(command -v apt-get >/dev/null 2>&1 && apt-get update >/dev/null 2>&1 && apt-get install $* iproute2 dmidecode net-tools curl traceroute iptables ca-certificates -y >/dev/null 2>&1) ||
|
||||
(command -v apk >/dev/null 2>&1 && apk update >/dev/null 2>&1 && apk add --no-cache net-tools iproute2 numactl-dev iputils iptables dmidecode -f >/dev/null 2>&1)
|
||||
}
|
||||
install_systemd() {
|
||||
# 安装系统依赖
|
||||
install_dependencies() {
|
||||
info "安装系统依赖..."
|
||||
|
||||
os_arch=""
|
||||
os_alpine="0"
|
||||
[ -e /etc/os-release ] && cat /etc/os-release | grep -i "PRETTY_NAME" | grep -qi "alpine" && os_alpine='1'
|
||||
[ "$os_alpine" != 1 ] && ! command -v systemctl >/dev/null 2>&1 && echo "不支持此系统:未找到 systemctl 命令" && exit 1
|
||||
# check root
|
||||
[[ $EUID -ne 0 ]] && echo -e "${yellow}===================================================\n${red}错误: 必须使用root用户运行此脚本!${plain}" && exit 1
|
||||
|
||||
## os_arch
|
||||
if [[ $(uname -m | grep 'x86_64') != "" ]]; then
|
||||
os_arch="x64"
|
||||
elif [[ $(uname -m | grep 'aarch64\|armv8b\|armv8l') != "" ]]; then
|
||||
os_arch="arm64"
|
||||
elif [[ $(uname -m | grep 'arm') != "" ]]; then
|
||||
os_arch="arm"
|
||||
fi
|
||||
if [ -z "$os_arch" ]; then
|
||||
echo -e "${yellow}===================================================\n${red} 仅支持arm arm64 amd64 ${plain}" && exit 1
|
||||
fi
|
||||
|
||||
LINKER_FILE_NAME="$LINKER_FILE_NAME$os_arch"
|
||||
if [ "$os_alpine" == 1 ]; then
|
||||
LINKER_FILE_NAME="$LINKER_FILE_NAME-musl"
|
||||
fi
|
||||
|
||||
if [ -e "/etc/systemd/system/linker.service" ]; then
|
||||
echo -e "${yellow}===================================================\n${plain}docker已存在linker服务,请自选择:${yellow}\n1. 继续安装\n2. 卸载\n3. 退出${plain}"
|
||||
while true; do
|
||||
read -e -r -p "请输入选择 [1-2]:" option
|
||||
case "${option}" in
|
||||
1)
|
||||
break
|
||||
;;
|
||||
2)
|
||||
echo -e "${yellow}===================================================${plain}\n正在移除服务!"
|
||||
systemctl disable linker >/dev/null 2>&1
|
||||
systemctl stop linker >/dev/null 2>&1
|
||||
rm -rf /etc/systemd/system/linker.service
|
||||
echo -e "${green}已移除服务${plain}"
|
||||
exit 1
|
||||
;;
|
||||
3)
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "${red}请输入正确的数字 [1-3]${plain}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
systemctl disable linker >/dev/null 2>&1
|
||||
systemctl stop linker >/dev/null 2>&1
|
||||
rm -rf /etc/systemd/system/linker.service >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n正在安装依赖..."
|
||||
install_base
|
||||
install_soft
|
||||
echo -e "${green}已安装依赖${plain}"
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n正在获取版本..."
|
||||
LINKER_DOWNLOAD_VERSION=$(curl -m 10 -s $LINKER_DOWNLOAD_URL/version.txt | head -n 1 | tr -d '[:space:]')
|
||||
if [ "${LINKER_DOWNLOAD_VERSION:0:1}" != "v" ]; then
|
||||
echo -e "${red}获取版本号失败${plain}" && exit 1
|
||||
fi
|
||||
echo -e "${green}版本号:$LINKER_DOWNLOAD_VERSION${plain}"
|
||||
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n正在下载程序..."
|
||||
wget -t 2 -T 60 -O ${LINKER_INSTALL_PATH}/${LINKER_FILE_NAME}-${LINKER_DOWNLOAD_VERSION}.zip ${LINKER_DOWNLOAD_URL}/${LINKER_DOWNLOAD_VERSION}/${LINKER_FILE_NAME}.zip >/dev/null 2>&1
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${red}下载程序失败,请检查本机能否连接 ${LINKER_DOWNLOAD_URL}/${LINKER_DOWNLOAD_VERSION}/${LINKER_FILE_NAME}.zip${plain}" && exit 1
|
||||
fi
|
||||
echo -e "${green}下载程序完成${plain}"
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n正在解压..."
|
||||
unzip -qo -O UTF-8 "${LINKER_INSTALL_PATH}/${LINKER_FILE_NAME}-${LINKER_DOWNLOAD_VERSION}.zip" -d $LINKER_INSTALL_PATH
|
||||
chmod 777 -R ${LINKER_INSTALL_PATH}/$LINKER_FILE_NAME
|
||||
echo -e "${green}解压完成${plain}"
|
||||
rm -rf ${LINKER_INSTALL_PATH}/${LINKER_FILE_NAME}-${LINKER_DOWNLOAD_VERSION}.zip
|
||||
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n正在下载服务文件..."
|
||||
wget -t 2 -T 60 -O ${LINKER_INSTALL_PATH}/${LINKER_FILE_NAME}/linker.service ${LINKER_DOWNLOAD_URL}/linker.service >/dev/null 2>&1
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${red}下载服务文件失败,请检查本机能否连接 ${LINKER_DOWNLOAD_URL}/linker.service${plain}" && exit 1
|
||||
fi
|
||||
echo -e "${green}下载服务文件完成${plain}"
|
||||
|
||||
cp -f ${LINKER_INSTALL_PATH}/${LINKER_FILE_NAME}/linker.service /etc/systemd/system/linker.service
|
||||
sed -i "s|{dir}|$LINKER_INSTALL_PATH/$LINKER_FILE_NAME|g" /etc/systemd/system/linker.service
|
||||
systemctl daemon-reload >/dev/null 2>&1
|
||||
systemctl enable linker >/dev/null 2>&1
|
||||
systemctl restart linker >/dev/null 2>&1
|
||||
|
||||
echo -e "${yellow}===================================================\n1、已安装linker"
|
||||
echo -e "2、如果你希望运行为客户端,请使用浏览器打开0.0.0.0:1804,进行初始化,或者systemctl stop linker,然后从别处导出配置将configs文件夹覆盖替换"
|
||||
echo -e "3、如果你希望运行为服务端,可以systemctl stop linker,修改configs/server.json配置,然后systemctl start linker再次运行"
|
||||
echo -e "4、如果服务启动失败,可以尝试手动运行./linkerk看报错信息,缺什么就装什么${plain}"
|
||||
}
|
||||
install_docker() {
|
||||
LINKER_FILE_NAME="linker"
|
||||
os_alpine="0"
|
||||
[ -e /etc/os-release ] && cat /etc/os-release | grep -i "PRETTY_NAME" | grep -qi "alpine" && os_alpine='1'
|
||||
command -v docker >/dev/null 2>&1
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${yellow}===================================================${plain}\n正在安装 Docker"
|
||||
if [ "$os_alpine" != 1 ]; then
|
||||
bash <(curl -sL https://get.docker.com -o get-docker.sh) >/dev/null 2>&1
|
||||
if [[ $? != 0 ]]; then
|
||||
echo -e "${red}下载脚本失败,请检查本机能否连接 https://get.docker.com${plain}"
|
||||
return 0
|
||||
fi
|
||||
systemctl enable docker.service >/dev/null 2>&1
|
||||
systemctl start docker.service >/dev/null 2>&1
|
||||
else
|
||||
apk add docker docker-compose >/dev/null 2>&1
|
||||
rc-update add docker
|
||||
rc-service docker start
|
||||
fi
|
||||
echo -e "${green}Docker 安装成功${plain}"
|
||||
fi
|
||||
|
||||
LINKER_IMAGES=$(docker ps | grep -w "linker")
|
||||
if [ -n "$LINKER_IMAGES" ]; then
|
||||
echo -e "${yellow}===================================================${plain}\ndocker已存在linker容器,请自选择:${yellow}\n1. 继续安装\n2. 卸载\n3. 退出${plain}"
|
||||
while true; do
|
||||
read -e -r -p "请输入选择 [1-3]:" option
|
||||
case "${option}" in
|
||||
1)
|
||||
break
|
||||
;;
|
||||
2)
|
||||
echo -e "${yellow}===================================================${plain}\n正在移除容器!"
|
||||
docker stop linker >/dev/null 2>&1
|
||||
docker rm linker >/dev/null 2>&1
|
||||
echo -e "${green}已移除容器${plain}"
|
||||
exit 1
|
||||
;;
|
||||
3)
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "${red}请输入正确的数字 [1-2]${plain}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
docker stop linker >/dev/null 2>&1
|
||||
docker rm linker >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo -e "${yellow}===================================================${plain}\n开始拉取镜像并运行容器,如果镜像拉取失败,你可能需要更换镜像源"
|
||||
|
||||
docker run -it -d --name linker \
|
||||
-v $LINKER_INSTALL_PATH/linker/configs:/app/configs \
|
||||
-v $LINKER_INSTALL_PATH/linker/logs:/app/logs \
|
||||
--device /dev/net/tun \
|
||||
--restart=always \
|
||||
--privileged=true \
|
||||
--network host \
|
||||
snltty/linker-musl
|
||||
|
||||
NEZHA_IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "snltty/linker")
|
||||
if [ -n "$NEZHA_IMAGES" ]; then
|
||||
echo -e "${green}docker容器启动成功${plain}" && exit 1
|
||||
if command_exists apt-get; then
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl wget unzip iproute2 net-tools iptables bash traceroute >/dev/null 2>&1
|
||||
elif command_exists yum; then
|
||||
yum install -y curl wget unzip iproute net-tools iptables bash traceroute >/dev/null 2>&1
|
||||
elif command_exists dnf; then
|
||||
dnf install -y curl wget unzip iproute net-tools iptables bash traceroute >/dev/null 2>&1
|
||||
elif command_exists apk; then
|
||||
apk add curl wget unzip iproute2 net-tools iptables bash traceroute >/dev/null 2>&1
|
||||
else
|
||||
echo -e "${red}docker容器启动失败${plain}" && exit 1
|
||||
warn "无法确定包管理器,跳过依赖安装"
|
||||
warn "请确保已安装: curl/wget, unzip, ip, ifconfig, iptables, bash, traceroute"
|
||||
fi
|
||||
}
|
||||
|
||||
select_version() {
|
||||
if [[ -z $LINKER_IS_DOCKER ]]; then
|
||||
echo -e "${yellow}===================================================\n${plain}请自行选择您的安装方式:${yellow}\n1. Docker\n2. 独立安装${plain}"
|
||||
while true; do
|
||||
read -e -r -p "请输入选择 [1-2]:" option
|
||||
case "${option}" in
|
||||
# 配置systemctl服务
|
||||
setup_systemctl() {
|
||||
info "配置systemctl服务..."
|
||||
|
||||
local service_file="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
|
||||
cat > "$service_file" << EOF
|
||||
[Unit]
|
||||
Description=${SERVICE_NAME}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
ExecStartPre=/bin/chmod +x ${INSTALL_DIR}/${SERVICE_NAME}
|
||||
ExecStart=${INSTALL_DIR}/${SERVICE_NAME}
|
||||
ExecStop=/bin/kill $MAINPID
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
systemctl start "$SERVICE_NAME"
|
||||
|
||||
info "systemctl服务已配置并启动"
|
||||
}
|
||||
|
||||
# 配置supervisor
|
||||
setup_supervisor() {
|
||||
info "配置supervisor..."
|
||||
|
||||
if ! command_exists supervisorctl; then
|
||||
error "Supervisor未安装,请先安装supervisor"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local supervisor_conf="/etc/supervisor/conf.d/${SERVICE_NAME}.conf"
|
||||
|
||||
cat > "$supervisor_conf" << EOF
|
||||
[program:${SERVICE_NAME}]
|
||||
command=${INSTALL_DIR}/${SERVICE_NAME}
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/log/${SERVICE_NAME}.log
|
||||
EOF
|
||||
|
||||
supervisorctl update
|
||||
supervisorctl start "$SERVICE_NAME"
|
||||
|
||||
info "supervisor配置已完成"
|
||||
}
|
||||
|
||||
# 安装Linker
|
||||
install_linker() {
|
||||
local install_type=$1
|
||||
|
||||
info "开始安装${SERVICE_NAME}..."
|
||||
|
||||
# 安装依赖(如果不是Docker方式)
|
||||
if [ "$install_type" != "3" ]; then
|
||||
install_dependencies
|
||||
fi
|
||||
|
||||
# 根据安装类型进行配置
|
||||
case "$install_type" in
|
||||
1|2|4)
|
||||
# 获取版本和系统信息
|
||||
info "获取最新版本..."
|
||||
local version
|
||||
version=$(get_latest_version)
|
||||
info "最新版本: $version"
|
||||
|
||||
local arch
|
||||
arch=$(detect_arch)
|
||||
info "系统架构: $arch"
|
||||
|
||||
local libc
|
||||
libc=$(detect_libc)
|
||||
info "Libc类型: $libc"
|
||||
|
||||
# 下载并安装
|
||||
download_install "$version" "$arch" "$libc"
|
||||
|
||||
# 配置服务
|
||||
case "$install_type" in
|
||||
1)
|
||||
LINKER_IS_DOCKER=1
|
||||
break
|
||||
setup_systemctl
|
||||
;;
|
||||
2)
|
||||
LINKER_IS_DOCKER=0
|
||||
break
|
||||
setup_supervisor
|
||||
;;
|
||||
*)
|
||||
echo "${red}请输入正确的数字 [1-2]${plain}"
|
||||
4)
|
||||
info "直接安装完成,请手动运行: ${INSTALL_DIR}/${SERVICE_NAME}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
3)
|
||||
setup_docker
|
||||
;;
|
||||
*)
|
||||
error "无效的安装类型"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
info "安装完成!"
|
||||
}
|
||||
|
||||
# 卸载Docker容器
|
||||
uninstall_docker() {
|
||||
info "卸载Docker容器..."
|
||||
|
||||
if command_exists docker; then
|
||||
if docker_container_exists; then
|
||||
info "停止并删除Docker容器: ${DOCKER_CONTAINER}"
|
||||
docker stop "$DOCKER_CONTAINER" 2>/dev/null || true
|
||||
docker rm "$DOCKER_CONTAINER" 2>/dev/null || true
|
||||
info "Docker容器已移除"
|
||||
else
|
||||
info "Docker容器不存在,无需移除"
|
||||
fi
|
||||
|
||||
# 可选:删除镜像
|
||||
read -rp "是否删除Docker镜像 ${DOCKER_IMAGE}?(y/N): " choice
|
||||
if [[ "$choice" =~ ^[Yy]$ ]]; then
|
||||
docker rmi "$DOCKER_IMAGE" 2>/dev/null || true
|
||||
info "Docker镜像已移除"
|
||||
fi
|
||||
else
|
||||
info "Docker 未安装,跳过Docker相关卸载"
|
||||
fi
|
||||
}
|
||||
|
||||
install_base
|
||||
select_version
|
||||
# 卸载Linker
|
||||
uninstall_linker() {
|
||||
info "开始卸载${SERVICE_NAME}..."
|
||||
|
||||
# 停止服务
|
||||
if systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null; then
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
systemctl disable "$SERVICE_NAME"
|
||||
rm -f "/etc/systemd/system/${SERVICE_NAME}.service"
|
||||
systemctl daemon-reload
|
||||
info "已停止并移除systemctl服务"
|
||||
fi
|
||||
|
||||
# 移除supervisor配置
|
||||
if [ -f "/etc/supervisor/conf.d/${SERVICE_NAME}.conf" ]; then
|
||||
supervisorctl stop "$SERVICE_NAME" 2>/dev/null || true
|
||||
rm -f "/etc/supervisor/conf.d/${SERVICE_NAME}.conf"
|
||||
supervisorctl update 2>/dev/null || true
|
||||
info "已移除supervisor配置"
|
||||
fi
|
||||
|
||||
# 移除安装文件
|
||||
if [ -f "${INSTALL_DIR}/${SERVICE_NAME}/${SERVICE_NAME}" ]; then
|
||||
rm -f "${INSTALL_DIR}/${SERVICE_NAME}"
|
||||
info "已移除 ${INSTALL_DIR}/${SERVICE_NAME}"
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# 如果是Docker安装方式,卸载Docker容器
|
||||
read -rp "是否卸载Docker容器?(y/N): " choice
|
||||
if [[ "$choice" =~ ^[Yy]$ ]]; then
|
||||
uninstall_docker
|
||||
fi
|
||||
|
||||
info "卸载完成!"
|
||||
}
|
||||
|
||||
# 显示菜单
|
||||
show_menu() {
|
||||
echo "========================================"
|
||||
echo " ${SERVICE_NAME} 安装管理脚本 "
|
||||
echo "========================================"
|
||||
echo "请选择操作:"
|
||||
echo " 1) 安装 ${SERVICE_NAME}"
|
||||
echo " 2) 卸载 ${SERVICE_NAME}"
|
||||
echo " 3) 退出"
|
||||
echo "========================================"
|
||||
read -rp "请输入选择 [1-3]: " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
echo "请选择安装方式:"
|
||||
echo " 1) 使用 systemctl 管理"
|
||||
echo " 2) 使用 supervisor 管理"
|
||||
echo " 3) Docker 容器安装"
|
||||
echo " 4) 直接安装(手动运行)"
|
||||
echo "========================================"
|
||||
read -rp "请输入选择 [1-4]: " install_type
|
||||
case $install_type in
|
||||
1|2|3|4)
|
||||
install_linker "$install_type"
|
||||
;;
|
||||
*)
|
||||
error "无效选择"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
2)
|
||||
uninstall_linker
|
||||
;;
|
||||
3)
|
||||
info "退出脚本"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
error "无效选择"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ $LINKER_IS_DOCKER == 1 ]]; then
|
||||
install_docker
|
||||
elif [[ $LINKER_IS_DOCKER == 0 ]]; then
|
||||
install_systemd
|
||||
# 检查root权限
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "此脚本需要root权限运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 主程序
|
||||
if [ $# -eq 0 ]; then
|
||||
show_menu
|
||||
else
|
||||
case $1 in
|
||||
install)
|
||||
if [ $# -gt 1 ]; then
|
||||
case $2 in
|
||||
systemctl) install_linker 1 ;;
|
||||
supervisor) install_linker 2 ;;
|
||||
docker) install_linker 3 ;;
|
||||
direct) install_linker 4 ;;
|
||||
*)
|
||||
error "无效的安装类型"
|
||||
echo "用法: $0 install [systemctl|supervisor|docker|direct]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "用法: $0 install [systemctl|supervisor|docker|direct]"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
uninstall_linker
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 [install [systemctl|supervisor|docker|direct] | uninstall]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
@@ -1,5 +1,5 @@
|
||||
v1.9.1
|
||||
2025-09-16 15:37:57
|
||||
2025-09-17 00:19:08
|
||||
1. 一些累计更新
|
||||
2. 服务器转发多节点
|
||||
3. 虚拟网卡下伪造ACK为TCP-in-TCP隧道提速
|
||||
|
Reference in New Issue
Block a user