This commit is contained in:
starry
2025-07-16 12:35:21 +00:00
commit 1f7b4314c3
49 changed files with 18341 additions and 0 deletions

41
.github/workflows/docker-ghcr.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: ghcr镜像构建
on:
workflow_dispatch:
inputs:
version:
description: 'Version number'
required: true
default: 'ansible'
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Docker Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set version from input
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
- name: Build and push Docker image
run: |
docker buildx build --push \
--platform linux/amd64,linux/arm64 \
--tag ghcr.io/${{ github.repository }}:${{ env.VERSION }} \
--build-arg VERSION=${{ env.VERSION }} \
./ansible
env:
GHCR_PUBLIC: true

44
README.md Normal file
View File

@@ -0,0 +1,44 @@
- [Github文件加速](https://yishijie.gitlab.io/ziyuan/)
---
- Linux一键更换系统源脚本
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/mirrors.sh)
```
- Linux一键切换官方源
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/mirrors.sh) --use-official-source true
```
- 一键安装Docker和配置镜像地址
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/docker.sh)
```
- 一键自动部署异次元发卡
> 适用于`Debian 11+` `Ubuntu 18.04+`系统 基于`Caddy` `php` `mariadb`环境
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/web/acgfaka.sh)
```
- 一键自动安装WordPress
> 适用于`Debian 11+` `Ubuntu 18.04+`系统 基于`Caddy` `php` `mariadb`环境
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/web/wp.sh)
```
- acme.sh 证书一键申请脚本
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/acme.sh)
```
- sing-box一键脚本多协议
```
bash <(curl -sSL https://cdn.jsdelivr.net/gh/admin8800/sing-box@main/install.sh)
```
> **使用`sb`命令查看管理菜单**

8
ansible/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM alpine
RUN apk add --update --no-cache ansible bash openssh sshpass rsync py3-passlib
COPY ansible /etc/ansible
ENTRYPOINT []
CMD ["ansible", "--help"]

235
ansible/README.md Normal file
View File

@@ -0,0 +1,235 @@
### 1Ansibler容器环境搭建
#### 运行示例
```
docker run --rm -it \
-v ./ansible:/etc/ansible \
-w /etc/ansible \
ghcr.io/sky22333/shell:ansible \
ansible-playbook renwu.yml
```
| 参数 | 作用 |
|------|------|
| `docker run` | 启动一个容器运行指定镜像 |
| `--rm` | 容器退出后自动删除(保持系统干净) |
| `-it` | 启用交互终端模式,允许输出彩色日志和提示信息 |
| `-v ./ansible:/etc/ansible` | 把你本地的 `./ansible` 目录挂载到容器的 `/etc/ansible` 路径下 |
| `-w /etc/ansible` | 设置容器的工作目录为 `/etc/ansible`,也就是你挂载的目录 |
| `ghcr.io/sky22333/shell:ansible` | 使用此镜像,预装了 ansible |
| `ansible-playbook renwu.yml` | 容器中执行的命令,也就是运行挂载进去的 `renwu.yml` 剧本文件 |
#### ansible 目录结构
```
.
├── ansible/
│ ├── renwu.yml
│ ├── hosts
│ └── ansible.cfg
```
`ansible`有如下示例配置
> `ansible.cfg` 配置Ansible的全局设置。
> `hosts` 定义要管理的主机和主机组。
> `renwu.yml或playbook` 描述要在主机上执行的任务和操作步骤。
---
---
### 2禁用被控主机密钥检查
`ansible.cfg`中添加以下配置
```
[defaults]
host_key_checking = False
ansible_ssh_common_args = '-o StrictHostKeyChecking=no'
```
### 3配置被控主机清单
`hosts`中添加被控主机示例
```
[myservers]
1 ansible_host=192.168.1.1 ansible_user=root ansible_port=22 ansible_ssh_pass=password1
2 ansible_host=192.168.1.2 ansible_user=root ansible_port=22 ansible_ssh_pass=password2
3 ansible_host=192.168.1.3 ansible_user=root ansible_port=22 ansible_ssh_pass=password3
4 ansible_host=192.168.1.4 ansible_user=root ansible_port=22 ansible_ssh_pass=password4
5 ansible_host=192.168.1.5 ansible_user=root ansible_port=22 ansible_ssh_pass=password5
```
### 4使用ping模块测试所有被控主机连通性
> (可选)查看所有被控机的信息 `ansible-inventory --list -i /etc/ansible/hosts`
```
ansible -m ping all
```
### 5创建被控主机任务配置文件
`renwu.yml`中添加任务示例
```
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes
tasks:
- name: 更新包列表
apt:
update_cache: yes
- name: 安装所需的软件包
apt:
name:
- curl
- wget
- zip
- tar
state: present
- name: 将脚本复制到远程主机
copy:
# 本地脚本路径
src: /etc/ansible/ss.sh
# 远程主机上的目标路径
dest: /tmp/ss.sh
# 设置脚本权限为可执行
mode: '0755'
- name: 在远程主机上执行脚本
shell: /tmp/ss.sh # 在远程主机上执行脚本
```
或者直接执行远程脚本示例
```
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes # 以管理员权限运行命令
tasks:
- name: 更新包列表
apt:
update_cache: yes
- name: 安装所需的软件包
apt:
name:
- curl
- wget
- zip
- tar
state: present
- name: 在被控主机上执行远程脚本
shell: bash <(wget -qO- https://github.com/user/shell/raw/main/dev.sh)
args:
executable: /bin/bash # 确保使用bash执行命令
```
### 6用法示例
- 对所有被控机器运行`renwu.yml`中的任务
```
ansible-playbook renwu.yml
```
- 临时对所有主机执行普通命令
```
ansible all -a "pwd"
```
- 临时对所有主机运行远程脚本
```
ansible all -m shell -a "bash <(wget -qO- https://github.com/user/shell/raw/main/dev.sh)"
```
- 临时将本地脚本复制给所有被控主机并执行
```
ansible all -m copy -a "src=/etc/ansible/script.sh dest=/tmp/script.sh mode=0755"
ansible all -m shell -a "/tmp/script.sh"
```
- 临时对13号主机执行shell命令
```
ansible 1,3 -m shell -a "你的命令"
```
- 临时对13号主机执行普通命令
```
ansible 1,3 -a "pwd"
```
> 命令结尾后面追加`-v`选项会显示被控机器详细的执行信息
---
#### 命令解释
> `-m` 用于指定 Ansible 模块
> `-a` 用于指定传递给模块的参数或命令
| 模块 | 指令 | 中文解释 | 用法示例 |
|-------------------|---------|----------------------------------------------|---------------------------------------------------|
| `shell` | `-a` | 执行 shell 命令。支持管道、重定向等 shell 特性。 | `ansible all -m shell -a "pwd"` |
| `command` | `-a` | 执行命令,不通过 shell。默认模块 | `ansible all -m command -a "ls -l"` |
| `copy` | `-a` | 复制文件或目录到目标主机。 | `ansible all -m copy -a "src=/local/file dest=/remote/file mode=0644"` |
| `file` | `-a` | 管理文件和目录的属性(如权限、所有权等)。 | `ansible all -m file -a "path=/remote/file state=absent"` |
| `yum` | `-a` | 使用 Yum 包管理器安装、更新或删除软件包(适用于 RHEL/CentOS。 | `ansible all -m yum -a "name=nginx state=present"` |
| `apt` | `-a` | 使用 APT 包管理器安装、更新或删除软件包(适用于 Debian/Ubuntu。 | `ansible all -m apt -a "name=nginx state=latest"` |
| `service` | `-a` | 管理服务(如启动、停止、重启服务)。 | `ansible all -m service -a "name=nginx state=started"` |
| `systemd` | `-a` | 管理 systemd 服务(如启动、停止、重启服务)。| `ansible all -m systemd -a "name=nginx state=started"` |
| `user` | `-a` | 管理用户账户(如创建、删除用户)。 | `ansible all -m user -a "name=alice state=present"` |
| `group` | `-a` | 管理用户组(如创建、删除组)。 | `ansible all -m group -a "name=admin state=present"` |
| `git` | `-a` | 管理 Git 仓库(如克隆、拉取、提交等)。 | `ansible all -m git -a "repo=https://github.com/user/repo.git dest=/path/to/repo"` |
| `template` | `-a` | 使用 Jinja2 模板引擎渲染模板文件。 | `ansible all -m template -a "src=template.j2 dest=/etc/config"` |
| `cron` | `-a` | 管理 cron 任务。 | `ansible all -m cron -a "name='Backup' minute='0' hour='2' job='/usr/bin/backup.sh'"` |
| `wait_for` | `-a` | 等待某个条件满足(如端口开放、文件存在等)。 | `ansible all -m wait_for -a "port=80 delay=10 timeout=300"` |
| `docker_container`| `-a` | 管理 Docker 容器(如启动、停止、删除容器)。 | `ansible all -m docker_container -a "name=my_container state=started"` |
| `docker_image` | `-a` | 管理 Docker 镜像(如拉取、删除镜像)。 | `ansible all -m docker_image -a "name=nginx tag=latest state=present"` |
| `lineinfile` | `-a` | 在文件中插入、删除或修改行。 | `ansible all -m lineinfile -a "path=/etc/hosts line='127.0.0.1 localhost' state=present"` |
| `ini_file` | `-a` | 修改 INI 配置文件。 | `ansible all -m ini_file -a "path=/etc/myconfig.ini section=database option=host value=localhost"` |
| `debug` | `-a` | 打印调试信息。 | `ansible all -m debug -a "msg='This is a debug message'"` |
---
---
#### 执行结果解释
- **ok**: 表示在该主机上成功完成的任务数。
- **changed**: 表示在该主机上有多少任务进行了更改(如文件被复制、脚本被执行)。
- **unreachable**: 表示无法连接的主机数量。
- **failed**: 表示任务失败的数量。
- **skipped**: 表示被跳过的任务数量。
- **rescued**: 表示在任务失败后被恢复的数量。
- **ignored**: 表示被忽略的任务数量。
- 绿色:任务顺利完成
- 橙色:任务执行后有变化,比如文件被修改或某些服务被重启。
- 红色:任务执行失败,一般会终止剩余的所有任务。
#### 如果所有被控机端口和密码都一样
`/etc/ansible/hosts`配置可以这样写
```
[all:vars]
ansible_user=root
ansible_ssh_pass=your_password
ansible_port=22
[myservers]
1 ansible_host=192.168.1.101
2 ansible_host=192.168.1.102
3 ansible_host=192.168.1.103
```

View File

@@ -0,0 +1,3 @@
[defaults]
host_key_checking = False
ansible_ssh_common_args = '-o StrictHostKeyChecking=no'

4
ansible/ansible/hosts Normal file
View File

@@ -0,0 +1,4 @@
[myservers]
1 ansible_host=192.168.1.1 ansible_user=root ansible_port=22 ansible_ssh_pass=password1
2 ansible_host=192.168.1.2 ansible_user=root ansible_port=22 ansible_ssh_pass=password2
3 ansible_host=192.168.1.3 ansible_user=root ansible_port=22 ansible_ssh_pass=password3

17
ansible/ansible/renwu.yml Normal file
View File

@@ -0,0 +1,17 @@
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes
gather_facts: no # 禁用事实收集以避免依赖 Python
tasks:
- name: 将脚本复制到远程主机
copy:
# 本地脚本路径
src: ./shell.sh
# 远程主机上的目标路径
dest: /tmp/shell.sh
# 设置脚本权限为可执行
mode: '0755'
- name: 在远程主机上执行脚本
raw: /tmp/shell.sh # 在远程主机上执行脚本

2
ansible/ansible/shell.sh Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
echo "Hello, world!"

55
dev/README.md Normal file
View File

@@ -0,0 +1,55 @@
### 一键安装ansible
见 [ansible.md](./ansible.md)
---
### 常用运维脚本
- 一键切换系统源脚本
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/mirrors.sh)
```
- 切换官方系统源
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/mirrors.sh) --use-official-source true
```
- 一键安装Docker和配置镜像地址
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/docker.sh)
```
- acme.sh 证书一键申请脚本
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/acme.sh)
```
- Linux切换到标准内核
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/image.sh)
```
- 一键安装go环境
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/go.sh)
```
- 一键启用BBR
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/bbr.sh)
```
- 一键内网穿透(无需域名和服务器)
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/cf-tunnel.sh)
```
- `win`系统`PowerShell`在线脚本,需要以管理员模式打开`PowerShell`
```
iwr -useb https://ghproxy.net/https://raw.githubusercontent.com/sky22333/shell/main/dev/cf-setup.ps1 | iex
```

157
dev/acme.sh Normal file
View File

@@ -0,0 +1,157 @@
#!/bin/bash
if [ "$(id -u)" != "0" ]; then
echo -e "\033[0;31m请以 root 用户运行此脚本\033[0m"
exit 1
fi
install_requirements() {
local install_cmd=""
local pkg_manager=""
local os_type=$(grep '^ID=' /etc/os-release | cut -d'=' -f2)
if [ "$os_type" == "ubuntu" ] || [ "$os_type" == "debian" ]; then
pkg_manager="apt"
install_cmd="apt install -y"
elif [ "$os_type" == "centos" ]; then
pkg_manager="yum"
install_cmd="yum install -y"
else
echo -e "\033[0;31m不支持的操作系统: $os_type\033[0m"
exit 1
fi
if ! command -v lsof &> /dev/null; then
$install_cmd lsof
fi
if ! command -v curl &> /dev/null; then
$install_cmd curl
fi
# 检查并安装 socat
if ! command -v socat &> /dev/null; then
echo -e "\033[0;32msocat 未安装,正在安装...\033[0m"
$install_cmd socat
else
echo -e "\033[0;32msocat 已安装\033[0m"
fi
}
# 生成12位纯英文的随机邮箱
generate_random_email() {
local random_email=$(tr -dc 'a-z' < /dev/urandom | fold -w 12 | head -n 1)
echo "${random_email}@gmail.com"
}
check_acme_installation() {
if ! command -v acme.sh &> /dev/null; then
echo -e "\033[0;32macme.sh 未安装,正在安装...\033[0m"
curl https://get.acme.sh | sh
source ~/.bashrc
else
echo -e "\033[0;32macme.sh 已安装\033[0m"
fi
}
# 检查端口 80 是否被占用,并提供释放端口的选项
check_port_80() {
local pid
pid=$(lsof -ti:80)
if [ -n "$pid" ]; then
echo -e "\033[0;31m端口 80 已被占用PID为: $pid\033[0m"
read -p "是否强制释放端口 80? (Y/n): " response
case "$response" in
[yY][eE][sS]|[yY])
echo "正在释放端口 80..."
kill -9 $pid
;;
*)
echo "未释放端口,脚本将退出。"
exit 1
;;
esac
fi
}
register_ca() {
local ca="$1"
local email="$2"
echo -e "\033[0;32m正在注册 CA 机构 $ca 使用电子邮件 $email...\033[0m"
~/.acme.sh/acme.sh --register-account -m "$email" --server "$ca"
}
generate_ssl_certificate() {
local domain_name="$1"
local ca="$2"
echo -e "\033[0;32m正在为 $domain_name 生成 SSL 证书...\033[0m"
~/.acme.sh/acme.sh --issue --force --standalone -d "$domain_name" --server "$ca"
if [ $? -ne 0 ]; then
echo -e "\033[0;31mSSL 证书生成失败\033[0m"
exit 1
fi
local cert_path="/root/.acme.sh/${domain_name}_ecc/fullchain.cer"
local key_path="/root/.acme.sh/${domain_name}_ecc/${domain_name}.key"
~/.acme.sh/acme.sh --install-cert -d "$domain_name" \
--key-file "$key_path" \
--fullchain-file "$cert_path"
# 打印 fullchain.cer 和 .key 文件的绝对路径
echo -e "\033[0;32m证书路径: $cert_path\033[0m"
echo -e "\033[0;32m密钥路径: $key_path\033[0m"
}
# 主流程
install_requirements
echo -e "\033[0;32m请输入您的域名确保已经解析到本机IP:\033[0m"
read -p "" domain_name
# 检查端口 80
check_port_80
# 检查证书和密钥是否已经存在
cert_path="/root/.acme.sh/${domain_name}_ecc/fullchain.cer"
key_path="/root/.acme.sh/${domain_name}_ecc/${domain_name}.key"
if [ -f "$cert_path" ] && [ -f "$key_path" ]; then
echo -e "\033[0;32m证书已存在:\033[0m"
echo -e "\033[0;32m证书全链路径: $cert_path\033[0m"
echo -e "\033[0;32m密钥文件路径: $key_path\033[0m"
exit 0
fi
# 生成随机邮箱
user_email=$(generate_random_email)
echo -e "\033[0;32m生成的邮箱: $user_email\033[0m"
# 检查 acme.sh 安装
check_acme_installation
# CA 机构选择
echo -e "\033[0;32m请选择 CA 机构:\033[0m"
echo -e "\033[0;32m1) Let's Encrypt\033[0m"
echo -e "\033[0;32m2) Buypass\033[0m"
echo -e "\033[0;32m3) ZeroSSL\033[0m"
echo -e "\033[0;32m选择 CA 机构 (回车默认选1):\033[0m"
read -p "" ca_choice
case $ca_choice in
2)
CA="buypass"
;;
3)
CA="zerossl"
;;
*)
CA="letsencrypt"
;;
esac
register_ca "$CA" "$user_email"
generate_ssl_certificate "$domain_name" "$CA"

242
dev/ansible.md Normal file
View File

@@ -0,0 +1,242 @@
## 🔵脚本加密-编译为可执行文件
- 下载环境
```
sudo apt update
sudo apt install shc gcc -yq
```
- 用法
| 命令 | 描述 | 示例 |
|-------------------------------|-------------------------------------------------------------------|---------------------------------------------------------------|
| `shc -f <script>` | 编译指定的 Shell 脚本文件。 | `shc -f script.sh` |
| `shc -o <output>` | 指定输出的可执行文件名。 | `shc -f script.sh -o myscript` |
| `shc -e <YYYY-MM-DD>` | 设置脚本的过期日期,格式为 `YYYY-MM-DD`。 | `shc -f script.sh -e 2024-12-31` |
| `shc -m "<message>"` | 设置当脚本过期时显示的消息。 | `shc -f script.sh -e 2024-12-31 -m "脚本已过期"` |
| `shc -r` | 允许在编译后的脚本中保留运行时的环境变量。 | `shc -r -f script.sh` |
| `shc -T` | 不生成中间的 C 源代码文件。 | `shc -f script.sh -T` |
| `shc -v` | 显示详细信息,帮助调试。 | `shc -v -f script.sh` |
| `shc -x` | 对脚本中的字符串进行 XOR 加密以增加安全性。 | `shc -x -f script.sh` |
| `shc -l <lib>` | 添加特定的库文件链接到编译的二进制文件中。 | `shc -f script.sh -l /usr/lib/somelibrary.so` |
- 远程执行加密脚本
```
curl -fsSL http://公网IP/my.sh -o my.sh && chmod +x my.sh && ./my.sh
```
需要系统一致
---
---
## 🔵ansible批量管理主机运维工具
- ### ✨一键安装脚本
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/dev/ansible.sh)
```
### 1安装并创建配置文件
```
sudo apt update
sudo apt install ansible -y
```
```
mkdir -p /etc/ansible && cd /etc/ansible && touch ansible.cfg hosts renwu.yml
```
> `ansible.cfg` 配置Ansible的全局设置。
> `hosts` 定义要管理的主机和主机组。
> `renwu.yml或playbook` 描述要在主机上执行的任务和操作步骤。
### 2禁用被控主机密钥检查
`ansible.cfg`中添加以下配置
```
[defaults]
host_key_checking = False
ansible_ssh_common_args = '-o StrictHostKeyChecking=no'
```
### 3配置被控主机清单
`hosts`中添加被控主机示例
```
[myservers]
1 ansible_host=192.168.1.1 ansible_user=root ansible_port=22 ansible_ssh_pass=password1
2 ansible_host=192.168.1.2 ansible_user=root ansible_port=22 ansible_ssh_pass=password2
3 ansible_host=192.168.1.3 ansible_user=root ansible_port=22 ansible_ssh_pass=password3
4 ansible_host=192.168.1.4 ansible_user=root ansible_port=22 ansible_ssh_pass=password4
5 ansible_host=192.168.1.5 ansible_user=root ansible_port=22 ansible_ssh_pass=password5
```
### 4使用ping模块测试所有被控主机连通性
> (可选)查看所有被控机的信息 `ansible-inventory --list -i /etc/ansible/hosts`
```
ansible -m ping all
```
### 5创建被控主机任务配置文件
`renwu.yml`中添加任务示例
```
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes
tasks:
- name: 更新包列表
apt:
update_cache: yes
- name: 安装所需的软件包
apt:
name:
- curl
- wget
- zip
- tar
state: present
- name: 将脚本复制到远程主机
copy:
# 本地脚本路径
src: /etc/ansible/ss.sh
# 远程主机上的目标路径
dest: /tmp/ss.sh
# 设置脚本权限为可执行
mode: '0755'
- name: 在远程主机上执行脚本
shell: /tmp/ss.sh # 在远程主机上执行脚本
```
或者直接执行远程脚本示例
```
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes # 以管理员权限运行命令
tasks:
- name: 更新包列表
apt:
update_cache: yes
- name: 安装所需的软件包
apt:
name:
- curl
- wget
- zip
- tar
state: present
- name: 在被控主机上执行远程脚本
shell: bash <(wget -qO- https://github.com/sky22333/shell/raw/main/vmess-ws.sh)
args:
executable: /bin/bash # 确保使用bash执行命令
```
### 6用法示例
- 对所有被控机器运行`renwu.yml`中的任务
```
ansible-playbook renwu.yml
```
- 临时对所有主机执行普通命令
```
ansible all -a "pwd"
```
- 临时对所有主机运行远程脚本
```
ansible all -m shell -a "bash <(wget -qO- https://github.com/sky22333/shell/raw/main/vmess-ws.sh)"
```
- 临时将本地脚本复制给所有被控主机并执行
```
ansible all -m copy -a "src=/etc/ansible/script.sh dest=/tmp/script.sh mode=0755"
ansible all -m shell -a "/tmp/script.sh"
```
- 临时对13号主机执行shell命令
```
ansible 1,3 -m shell -a "你的命令"
```
- 临时对13号主机执行普通命令
```
ansible 1,3 -a "pwd"
```
> 命令结尾后面追加`-v`选项会显示被控机器详细的执行信息
---
#### 命令解释
> `-m` 用于指定 Ansible 模块
> `-a` 用于指定传递给模块的参数或命令
| 模块 | 指令 | 中文解释 | 用法示例 |
|-------------------|---------|----------------------------------------------|---------------------------------------------------|
| `shell` | `-a` | 执行 shell 命令。支持管道、重定向等 shell 特性。 | `ansible all -m shell -a "pwd"` |
| `command` | `-a` | 执行命令,不通过 shell。默认模块 | `ansible all -m command -a "ls -l"` |
| `copy` | `-a` | 复制文件或目录到目标主机。 | `ansible all -m copy -a "src=/local/file dest=/remote/file mode=0644"` |
| `file` | `-a` | 管理文件和目录的属性(如权限、所有权等)。 | `ansible all -m file -a "path=/remote/file state=absent"` |
| `yum` | `-a` | 使用 Yum 包管理器安装、更新或删除软件包(适用于 RHEL/CentOS。 | `ansible all -m yum -a "name=nginx state=present"` |
| `apt` | `-a` | 使用 APT 包管理器安装、更新或删除软件包(适用于 Debian/Ubuntu。 | `ansible all -m apt -a "name=nginx state=latest"` |
| `service` | `-a` | 管理服务(如启动、停止、重启服务)。 | `ansible all -m service -a "name=nginx state=started"` |
| `systemd` | `-a` | 管理 systemd 服务(如启动、停止、重启服务)。| `ansible all -m systemd -a "name=nginx state=started"` |
| `user` | `-a` | 管理用户账户(如创建、删除用户)。 | `ansible all -m user -a "name=alice state=present"` |
| `group` | `-a` | 管理用户组(如创建、删除组)。 | `ansible all -m group -a "name=admin state=present"` |
| `git` | `-a` | 管理 Git 仓库(如克隆、拉取、提交等)。 | `ansible all -m git -a "repo=https://github.com/user/repo.git dest=/path/to/repo"` |
| `template` | `-a` | 使用 Jinja2 模板引擎渲染模板文件。 | `ansible all -m template -a "src=template.j2 dest=/etc/config"` |
| `cron` | `-a` | 管理 cron 任务。 | `ansible all -m cron -a "name='Backup' minute='0' hour='2' job='/usr/bin/backup.sh'"` |
| `wait_for` | `-a` | 等待某个条件满足(如端口开放、文件存在等)。 | `ansible all -m wait_for -a "port=80 delay=10 timeout=300"` |
| `docker_container`| `-a` | 管理 Docker 容器(如启动、停止、删除容器)。 | `ansible all -m docker_container -a "name=my_container state=started"` |
| `docker_image` | `-a` | 管理 Docker 镜像(如拉取、删除镜像)。 | `ansible all -m docker_image -a "name=nginx tag=latest state=present"` |
| `lineinfile` | `-a` | 在文件中插入、删除或修改行。 | `ansible all -m lineinfile -a "path=/etc/hosts line='127.0.0.1 localhost' state=present"` |
| `ini_file` | `-a` | 修改 INI 配置文件。 | `ansible all -m ini_file -a "path=/etc/myconfig.ini section=database option=host value=localhost"` |
| `debug` | `-a` | 打印调试信息。 | `ansible all -m debug -a "msg='This is a debug message'"` |
---
---
#### 执行结果解释
- **ok**: 表示在该主机上成功完成的任务数。
- **changed**: 表示在该主机上有多少任务进行了更改(如文件被复制、脚本被执行)。
- **unreachable**: 表示无法连接的主机数量。
- **failed**: 表示任务失败的数量。
- **skipped**: 表示被跳过的任务数量。
- **rescued**: 表示在任务失败后被恢复的数量。
- **ignored**: 表示被忽略的任务数量。
- 绿色:任务顺利完成
- 橙色:任务执行后有变化,比如文件被修改或某些服务被重启。
- 红色:任务执行失败,一般会终止剩余的所有任务。
#### 如果所有被控机端口和密码都一样
`/etc/ansible/hosts`配置可以这样写
```
[all:vars]
ansible_user=root
ansible_ssh_pass=your_password
ansible_port=22
[myservers]
1 ansible_host=192.168.1.101
2 ansible_host=192.168.1.102
3 ansible_host=192.168.1.103
```

48
dev/ansible.sh Normal file
View File

@@ -0,0 +1,48 @@
#!/bin/bash
# 安装 Ansible
sudo apt update && apt install ansible -yq
# 创建 Ansible 配置文件和目录
mkdir -p /etc/ansible
cd /etc/ansible || exit
# 创建 ansible.cfg 文件并添加配置
cat <<EOL > ansible.cfg
[defaults]
host_key_checking = False
ansible_ssh_common_args = '-o StrictHostKeyChecking=no'
EOL
# 创建 hosts 文件并添加被控主机
cat <<EOL > hosts
[myservers]
1 ansible_host=192.168.1.1 ansible_user=root ansible_port=22 ansible_ssh_pass=password1
2 ansible_host=192.168.1.2 ansible_user=root ansible_port=22 ansible_ssh_pass=password2
3 ansible_host=192.168.1.3 ansible_user=root ansible_port=22 ansible_ssh_pass=password3
EOL
# 创建 renwu.yml 文件并添加任务
cat <<EOL > renwu.yml
---
# 定义要执行任务的主机组
- hosts: myservers
become: yes
gather_facts: no # 禁用事实收集以避免依赖 Python
tasks:
- name: 将脚本复制到远程主机
copy:
# 本地脚本路径
src: ./proxy.sh
# 远程主机上的目标路径
dest: /tmp/ss.sh
# 设置脚本权限为可执行
mode: '0755'
- name: 在远程主机上执行脚本
raw: /tmp/ss.sh # 在远程主机上执行脚本
EOL
# 输出成功信息
echo "Ansible 配置文件和任务文件已成功创建并配置完成。"

366
dev/bbr.sh Normal file
View File

@@ -0,0 +1,366 @@
#!/usr/bin/env bash
#
# 自动安装 TCP BBR 的最新内核
#
# 系统要求CentOS 6+、Debian8+、Ubuntu16+
#
cur_dir=$(pwd)
_red() {
printf '\033[1;31;31m%b\033[0m' "$1"
}
_green() {
printf '\033[1;31;32m%b\033[0m' "$1"
}
_yellow() {
printf '\033[1;31;33m%b\033[0m' "$1"
}
_info() {
_green "[Info] "
printf -- "%s" "$1"
printf "\n"
}
_warn() {
_yellow "[Warning] "
printf -- "%s" "$1"
printf "\n"
}
_error() {
_red "[Error] "
printf -- "%s" "$1"
printf "\n"
exit 1
}
_exists() {
local cmd="$1"
if eval type type > /dev/null 2>&1; then
eval type "$cmd" > /dev/null 2>&1
elif command > /dev/null 2>&1; then
command -v "$cmd" > /dev/null 2>&1
else
which "$cmd" > /dev/null 2>&1
fi
local rt=$?
return ${rt}
}
_os() {
local os=""
[ -f "/etc/debian_version" ] && source /etc/os-release && os="${ID}" && printf -- "%s" "${os}" && return
[ -f "/etc/redhat-release" ] && os="centos" && printf -- "%s" "${os}" && return
}
_os_full() {
[ -f /etc/redhat-release ] && awk '{print ($1,$3~/^[0-9]/?$3:$4)}' /etc/redhat-release && return
[ -f /etc/os-release ] && awk -F'[= "]' '/PRETTY_NAME/{print $3,$4,$5}' /etc/os-release && return
[ -f /etc/lsb-release ] && awk -F'[="]+' '/DESCRIPTION/{print $2}' /etc/lsb-release && return
}
_os_ver() {
local main_ver="$( echo $(_os_full) | grep -oE "[0-9.]+")"
printf -- "%s" "${main_ver%%.*}"
}
_error_detect() {
local cmd="$1"
_info "${cmd}"
eval ${cmd}
if [ $? -ne 0 ]; then
_error "Execution command (${cmd}) failed, please check it and try again."
fi
}
_is_digit(){
local input=${1}
if [[ "$input" =~ ^[0-9]+$ ]]; then
return 0
else
return 1
fi
}
_is_64bit(){
if [ $(getconf WORD_BIT) = '32' ] && [ $(getconf LONG_BIT) = '64' ]; then
return 0
else
return 1
fi
}
_version_ge(){
test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"
}
get_valid_valname(){
local val=${1}
local new_val=$(eval echo $val | sed 's/[-.]/_/g')
echo ${new_val}
}
get_hint(){
local val=${1}
local new_val=$(get_valid_valname $val)
eval echo "\$hint_${new_val}"
}
#Display Memu
display_menu(){
local soft=${1}
local default=${2}
eval local arr=(\${${soft}_arr[@]})
local default_prompt
if [[ "$default" != "" ]]; then
if [[ "$default" == "last" ]]; then
default=${#arr[@]}
fi
default_prompt="(default ${arr[$default-1]})"
fi
local pick
local hint
local vname
local prompt="which ${soft} you'd select ${default_prompt}: "
while :
do
echo -e "\n------------ ${soft} setting ------------\n"
for ((i=1;i<=${#arr[@]};i++ )); do
vname="$(get_valid_valname ${arr[$i-1]})"
hint="$(get_hint $vname)"
[[ "$hint" == "" ]] && hint="${arr[$i-1]}"
echo -e "${green}${i}${plain}) $hint"
done
echo
read -p "${prompt}" pick
if [[ "$pick" == "" && "$default" != "" ]]; then
pick=${default}
break
fi
if ! _is_digit "$pick"; then
prompt="Input error, please input a number"
continue
fi
if [[ "$pick" -lt 1 || "$pick" -gt ${#arr[@]} ]]; then
prompt="Input error, please input a number between 1 and ${#arr[@]}: "
continue
fi
break
done
eval ${soft}=${arr[$pick-1]}
vname="$(get_valid_valname ${arr[$pick-1]})"
hint="$(get_hint $vname)"
[[ "$hint" == "" ]] && hint="${arr[$pick-1]}"
echo -e "\nyour selection: $hint\n"
}
get_latest_version() {
latest_version=($(wget -qO- https://kernel.ubuntu.com/~kernel-ppa/mainline/ | awk -F'\"v' '/v[4-9]./{print $2}' | cut -d/ -f1 | grep -v - | sort -V))
[ ${#latest_version[@]} -eq 0 ] && _error "Get latest kernel version failed."
kernel_arr=()
for i in ${latest_version[@]}; do
if _version_ge $i 5.15; then
kernel_arr+=($i);
fi
done
display_menu kernel last
if _is_64bit; then
deb_name=$(wget -qO- https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/ | grep "linux-image" | grep "generic" | awk -F'\">' '/amd64.deb/{print $2}' | cut -d'<' -f1 | head -1)
deb_kernel_url="https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/${deb_name}"
deb_kernel_name="linux-image-${kernel}-amd64.deb"
modules_deb_name=$(wget -qO- https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/ | grep "linux-modules" | grep "generic" | awk -F'\">' '/amd64.deb/{print $2}' | cut -d'<' -f1 | head -1)
deb_kernel_modules_url="https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/${modules_deb_name}"
deb_kernel_modules_name="linux-modules-${kernel}-amd64.deb"
else
deb_name=$(wget -qO- https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/ | grep "linux-image" | grep "generic" | awk -F'\">' '/i386.deb/{print $2}' | cut -d'<' -f1 | head -1)
deb_kernel_url="https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/${deb_name}"
deb_kernel_name="linux-image-${kernel}-i386.deb"
modules_deb_name=$(wget -qO- https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/ | grep "linux-modules" | grep "generic" | awk -F'\">' '/i386.deb/{print $2}' | cut -d'<' -f1 | head -1)
deb_kernel_modules_url="https://kernel.ubuntu.com/~kernel-ppa/mainline/v${kernel}/${modules_deb_name}"
deb_kernel_modules_name="linux-modules-${kernel}-i386.deb"
fi
[ -z "${deb_name}" ] && _error "Getting Linux kernel binary package name failed, maybe kernel build failed. Please choose other one and try again."
}
get_char() {
SAVEDSTTY=`stty -g`
stty -echo
stty cbreak
dd if=/dev/tty bs=1 count=1 2> /dev/null
stty -raw
stty echo
stty $SAVEDSTTY
}
check_bbr_status() {
local param=$(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}')
if [[ x"${param}" == x"bbr" ]]; then
return 0
else
return 1
fi
}
check_kernel_version() {
local kernel_version=$(uname -r | cut -d- -f1)
if _version_ge ${kernel_version} 4.9; then
return 0
else
return 1
fi
}
# Check OS version
check_os() {
if _exists "virt-what"; then
virt="$(virt-what)"
elif _exists "systemd-detect-virt"; then
virt="$(systemd-detect-virt)"
fi
if [ -n "${virt}" -a "${virt}" = "lxc" ]; then
_error "Virtualization method is LXC, which is 不支持."
fi
if [ -n "${virt}" -a "${virt}" = "openvz" ] || [ -d "/proc/vz" ]; then
_error "Virtualization method is OpenVZ, 不支持."
fi
[ -z "$(_os)" ] && _error "系统不支持"
case "$(_os)" in
ubuntu)
[ -n "$(_os_ver)" -a "$(_os_ver)" -lt 16 ] && _error "Not supported OS, please change to Ubuntu 16+ and try again."
;;
debian)
[ -n "$(_os_ver)" -a "$(_os_ver)" -lt 8 ] && _error "Not supported OS, please change to Debian 8+ and try again."
;;
centos)
[ -n "$(_os_ver)" -a "$(_os_ver)" -lt 6 ] && _error "Not supported OS, please change to CentOS 6+ and try again."
;;
*)
_error "系统不支持"
;;
esac
}
sysctl_config() {
sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf
sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf
echo "net.core.default_qdisc = fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control = bbr" >> /etc/sysctl.conf
sysctl -p >/dev/null 2>&1
}
install_kernel() {
case "$(_os)" in
centos)
if [ -n "$(_os_ver)" ]; then
if ! _exists "perl"; then
_error_detect "yum install -y perl"
fi
if [ "$(_os_ver)" -eq 6 ]; then
_error_detect "rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org"
rpm_kernel_url="https://dl.lamp.sh/files/"
if _is_64bit; then
rpm_kernel_name="kernel-ml-4.18.20-1.el6.elrepo.x86_64.rpm"
rpm_kernel_devel_name="kernel-ml-devel-4.18.20-1.el6.elrepo.x86_64.rpm"
else
rpm_kernel_name="kernel-ml-4.18.20-1.el6.elrepo.i686.rpm"
rpm_kernel_devel_name="kernel-ml-devel-4.18.20-1.el6.elrepo.i686.rpm"
fi
_error_detect "wget -c -t3 -T60 -O ${rpm_kernel_name} ${rpm_kernel_url}${rpm_kernel_name}"
_error_detect "wget -c -t3 -T60 -O ${rpm_kernel_devel_name} ${rpm_kernel_url}${rpm_kernel_devel_name}"
[ -s "${rpm_kernel_name}" ] && _error_detect "rpm -ivh ${rpm_kernel_name}" || _error "Download ${rpm_kernel_name} failed, please check it."
[ -s "${rpm_kernel_devel_name}" ] && _error_detect "rpm -ivh ${rpm_kernel_devel_name}" || _error "Download ${rpm_kernel_devel_name} failed, please check it."
rm -f ${rpm_kernel_name} ${rpm_kernel_devel_name}
[ ! -f "/boot/grub/grub.conf" ] && _error "/boot/grub/grub.conf not found, please check it."
sed -i 's/^default=.*/default=0/g' /boot/grub/grub.conf
elif [ "$(_os_ver)" -eq 7 ]; then
rpm_kernel_url="https://dl.lamp.sh/kernel/el7/"
if _is_64bit; then
rpm_kernel_name="kernel-ml-5.15.60-1.el7.x86_64.rpm"
rpm_kernel_devel_name="kernel-ml-devel-5.15.60-1.el7.x86_64.rpm"
else
_error "Not supported architecture, please change to 64-bit architecture."
fi
_error_detect "wget -c -t3 -T60 -O ${rpm_kernel_name} ${rpm_kernel_url}${rpm_kernel_name}"
_error_detect "wget -c -t3 -T60 -O ${rpm_kernel_devel_name} ${rpm_kernel_url}${rpm_kernel_devel_name}"
[ -s "${rpm_kernel_name}" ] && _error_detect "rpm -ivh ${rpm_kernel_name}" || _error "Download ${rpm_kernel_name} failed, please check it."
[ -s "${rpm_kernel_devel_name}" ] && _error_detect "rpm -ivh ${rpm_kernel_devel_name}" || _error "Download ${rpm_kernel_devel_name} failed, please check it."
rm -f ${rpm_kernel_name} ${rpm_kernel_devel_name}
/usr/sbin/grub2-set-default 0
fi
fi
;;
ubuntu|debian)
_info "Getting latest kernel version..."
get_latest_version
if [ -n "${modules_deb_name}" ]; then
_error_detect "wget -c -t3 -T60 -O ${deb_kernel_modules_name} ${deb_kernel_modules_url}"
fi
_error_detect "wget -c -t3 -T60 -O ${deb_kernel_name} ${deb_kernel_url}"
_error_detect "dpkg -i ${deb_kernel_modules_name} ${deb_kernel_name}"
rm -f ${deb_kernel_modules_name} ${deb_kernel_name}
_error_detect "/usr/sbin/update-grub"
;;
*)
;; # do nothing
esac
}
reboot_os() {
echo
_info "系统需要重启才能生效"
read -p "是否立即重启系统? [y/n]" is_reboot
if [[ ${is_reboot} == "y" || ${is_reboot} == "Y" ]]; then
reboot
else
_info "重启已取消..."
exit 0
fi
}
install_bbr() {
if check_bbr_status; then
echo
_info "TCP BBR 已启用。无需执行..."
exit 0
fi
if check_kernel_version; then
echo
_info "内核版本大于4.9直接设置TCP BBR……"
sysctl_config
_info "设置 TCP BBR 成功..."
exit 0
fi
check_os
install_kernel
sysctl_config
reboot_os
}
[[ $EUID -ne 0 ]] && _error "此脚本必须以root用户运行"
opsy=$( _os_full )
arch=$( uname -m )
lbit=$( getconf LONG_BIT )
kern=$( uname -r )
clear
echo "---------- 系统信息 ----------"
echo " 系统: $opsy"
echo " 架构: $arch ($lbit Bit)"
echo " 内核: $kern"
echo "-------------------------------------"
echo " 一键开启 TCP BBR 脚本"
echo "-------------------------------------"
echo
echo "按任意键启用BBR...或者 Ctrl+C 取消"
char=$(get_char)
install_bbr 2>&1 | tee ${cur_dir}/install_bbr.log

278
dev/cf-setup.ps1 Normal file
View File

@@ -0,0 +1,278 @@
# PowerShell: "iwr -useb https://raw.githubusercontent.com/sky22333/shell/main/dev/cf-setup.ps1 | iex"
# Path: "C:\ProgramData\cloudflared\"
$ProgressPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
function Write-ColorMessage {
param (
[Parameter(Mandatory=$true)]
[string]$Message,
[ValidateSet('Black','DarkBlue','DarkGreen','DarkCyan','DarkRed','DarkMagenta','DarkYellow','Gray','DarkGray','Blue','Green','Cyan','Red','Magenta','Yellow','White')]
[string]$Color = 'White'
)
try {
$originalColor = $null
if ($Host.UI -and $Host.UI.RawUI -and $Host.UI.RawUI.ForegroundColor) {
$originalColor = $Host.UI.RawUI.ForegroundColor
$Host.UI.RawUI.ForegroundColor = $Color
}
Write-Host $Message
if ($originalColor -ne $null) {
$Host.UI.RawUI.ForegroundColor = $originalColor
}
} catch {
try {
Write-Host $Message -ForegroundColor $Color
} catch {
Write-Host $Message
}
}
}
function Download-File {
param (
[string]$Url,
[string]$OutputPath
)
try {
if ($PSVersionTable.PSVersion.Major -ge 3) {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
$webClient.DownloadFile($Url, $OutputPath)
$webClient.Dispose()
} else {
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile($Url, $OutputPath)
$webClient.Dispose()
}
return $true
} catch {
Write-ColorMessage "Download failed: $($_.Exception.Message)" Red
return $false
}
}
function Test-AdminRights {
try {
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
} catch {
return $false
}
}
Write-Host "====== CloudFlared Tunnel Setup Tool ======" -ForegroundColor Cyan
Write-Host "Initializing..." -ForegroundColor Yellow
$cloudflaredUrl = "https://github.com/cloudflare/cloudflared/releases/download/2025.6.1/cloudflared-windows-amd64.exe"
$installDir = "$env:ProgramData\cloudflared"
$cloudflaredBin = Join-Path $installDir "cloudflared.exe"
$logPath = Join-Path $installDir "cloudflared.log"
$serviceName = "CloudflaredTunnel"
$psVersion = $PSVersionTable.PSVersion.Major
Write-Host "Detected PowerShell version: $psVersion" -ForegroundColor Green
try {
if (-not (Test-Path $installDir)) {
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
Write-ColorMessage "Created installation directory: $installDir" Green
}
} catch {
Write-ColorMessage "Cannot create installation directory, may need administrator privileges" Red
Write-ColorMessage "Error: $($_.Exception.Message)" Red
exit 1
}
Write-ColorMessage "`nChecking cloudflared..." Yellow
if (Test-Path $cloudflaredBin) {
Write-ColorMessage "cloudflared.exe already exists: $cloudflaredBin" Green
try {
$fileInfo = Get-Item $cloudflaredBin
$fileSize = [math]::Round($fileInfo.Length / 1MB, 2)
Write-ColorMessage "File size: ${fileSize} MB" Cyan
} catch {
}
} else {
Write-ColorMessage "Starting download of cloudflared..." Cyan
Write-ColorMessage "Download URL: $cloudflaredUrl" Gray
Write-ColorMessage "Save location: $cloudflaredBin" Gray
$downloadSuccess = Download-File -Url $cloudflaredUrl -OutputPath $cloudflaredBin
if ($downloadSuccess) {
Write-ColorMessage "Download complete!" Green
try {
$fileInfo = Get-Item $cloudflaredBin
$fileSize = [math]::Round($fileInfo.Length / 1MB, 2)
Write-ColorMessage "File size: ${fileSize} MB" Cyan
} catch {
}
} else {
Write-ColorMessage "Download failed, please check your network connection or download manually" Red
Write-ColorMessage "Manual download URL: $cloudflaredUrl" Yellow
exit 1
}
}
Write-ColorMessage "`nChecking existing services..." Yellow
try {
$serviceExists = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($serviceExists) {
Write-ColorMessage "Detected existing cloudflared service: $serviceName" Yellow
Write-ColorMessage "Service status: $($serviceExists.Status)" Cyan
do {
$uninstall = Read-Host "Do you want to uninstall the old service? (y/n)"
} while ($uninstall -notin @('y','Y','n','N','yes','no'))
if ($uninstall -in @('y','Y','yes')) {
Write-ColorMessage "Uninstalling old service..." Cyan
try {
Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
$scResult = & "$env:SystemRoot\System32\sc.exe" delete $serviceName
if (Test-Path $logPath) {
Remove-Item -Path $logPath -Force -ErrorAction SilentlyContinue
}
Write-ColorMessage "Service uninstallation complete" Green
} catch {
Write-ColorMessage "Error uninstalling service: $($_.Exception.Message)" Red
}
} else {
Write-ColorMessage "Keeping existing service, only updating run address" Yellow
}
}
} catch {
Write-ColorMessage "Error checking service: $($_.Exception.Message)" Red
}
Write-ColorMessage "`nPlease select run mode:" Yellow
Write-Host "1) Temporary run (foreground with trycloudflare domain display)"
Write-Host "2) Background run (register as system service)"
do {
$mode = Read-Host "Please enter 1 or 2 ?"
} while ($mode -notin @('1','2'))
do {
$localAddr = Read-Host "Please enter local service address (e.g.: 127.0.0.1:8080)"
} while ([string]::IsNullOrWhiteSpace($localAddr))
if ($mode -eq "1") {
Write-ColorMessage "`nRunning cloudflared in temporary mode..." Cyan
Write-ColorMessage "Starting cloudflared process..." Yellow
Write-ColorMessage "Local service address: $localAddr" Green
try {
Write-ColorMessage "Running cloudflared directly with output to console..." Yellow
Write-ColorMessage "Press Ctrl+C to stop the tunnel" Yellow
& $cloudflaredBin tunnel --url $localAddr
} catch {
Write-ColorMessage "Error starting process: $($_.Exception.Message)" Red
}
} elseif ($mode -eq "2") {
Write-ColorMessage "`nRegistering as system service and running in background..." Cyan
if (-not (Test-AdminRights)) {
Write-ColorMessage "Warning: Administrator privileges may be required to create system services" Yellow
Write-ColorMessage "If this fails, please run this script as administrator" Yellow
}
try {
$serviceCommand = "`"$cloudflaredBin`" tunnel --url $localAddr --logfile `"$logPath`""
$scResult = & "$env:SystemRoot\System32\sc.exe" create $serviceName binPath= $serviceCommand start= auto
if ($LASTEXITCODE -eq 0) {
Write-ColorMessage "Service created successfully" Green
} else {
Write-ColorMessage "Service creation may have failed, exit code: $LASTEXITCODE" Yellow
}
Start-Sleep -Seconds 2
Write-ColorMessage "Starting service..." Yellow
Start-Service -Name $serviceName -ErrorAction Stop
Write-ColorMessage "Service started successfully, waiting for log output..." Green
$domain = $null
for ($i = 0; $i -lt 30; $i++) {
Start-Sleep -Seconds 1
if (Test-Path $logPath) {
try {
$logContent = Get-Content $logPath -Raw -ErrorAction SilentlyContinue
if ($logContent -and $logContent -match 'https://[a-zA-Z0-9-]+\.trycloudflare\.com') {
$domain = $matches[0]
Write-ColorMessage "`n=== Service Running Successfully ===" Green
Write-ColorMessage "Public access URL: $domain" Green
Write-ColorMessage "Local service address: $localAddr" Cyan
Write-ColorMessage "Log file location: $logPath" Gray
break
}
} catch {
}
}
if ($i % 3 -eq 0) {
Write-Host "." -NoNewline
}
}
Write-Host ""
if (-not $domain) {
Write-ColorMessage "No access domain detected, please check the log manually: $logPath" Yellow
Write-ColorMessage "The service may need more time to establish connection" Cyan
try {
$serviceStatus = Get-Service -Name $serviceName
Write-ColorMessage "Service status: $($serviceStatus.Status)" Cyan
} catch {
Write-ColorMessage "Unable to get service status" Red
}
}
Write-ColorMessage "`nService management commands:" Yellow
Write-ColorMessage "Stop service: Stop-Service -Name $serviceName" Gray
Write-ColorMessage "Start service: Start-Service -Name $serviceName" Gray
Write-ColorMessage "Delete service: sc.exe delete $serviceName" Gray
} catch {
Write-ColorMessage "Failed to create or start service" Red
Write-ColorMessage "Error: $($_.Exception.Message)" Red
Write-ColorMessage "Please make sure you have administrator privileges" Yellow
try {
& "$env:SystemRoot\System32\sc.exe" delete $serviceName 2>$null
} catch {
}
}
} else {
Write-ColorMessage "Invalid option, please enter 1 or 2" Red
exit 1
}
Write-ColorMessage "`nScript execution complete" Green

133
dev/cf-tunnel.sh Normal file
View File

@@ -0,0 +1,133 @@
#!/bin/bash
# https://github.com/sky22333/shell
set -e
# === 颜色定义 ===
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
NC='\033[0m' # 清除颜色
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/download/2025.6.1/cloudflared-linux-amd64"
CLOUDFLARED_BIN="/usr/local/bin/cloudflared"
SERVICE_PATH="/etc/systemd/system/cloudflared-tunnel.service"
LOG_PATH="/var/log/cloudflared.log"
# 检查 cloudflared 是否已存在
if [[ -f "$CLOUDFLARED_BIN" ]]; then
echo -e "${GREEN}已存在文件,跳过下载。${NC}"
else
echo -e "${BLUE}正在下载 cloudflared...${NC}"
if ! curl -L "$CLOUDFLARED_URL" -o "$CLOUDFLARED_BIN"; then
echo -e "${RED}下载失败,请检查网络连接或 URL。${NC}"
exit 1
fi
chmod +x "$CLOUDFLARED_BIN"
fi
# 检查服务是否存在
SERVICE_EXISTS=false
if sudo systemctl list-units --full --all | grep -q 'cloudflared-tunnel.service'; then
SERVICE_EXISTS=true
echo -e "${YELLOW}已检测到 cloudflared-tunnel systemd 服务${NC}"
read -p "是否要卸载旧服务?(y/n): " UNINSTALL
if [[ "$UNINSTALL" == "y" || "$UNINSTALL" == "Y" ]]; then
echo -e "${BLUE}正在卸载旧服务...${NC}"
sudo systemctl stop cloudflared-tunnel || true
sudo systemctl disable cloudflared-tunnel || true
sudo rm -f "$SERVICE_PATH"
sudo rm -f "$LOG_PATH"
sudo systemctl daemon-reload
SERVICE_EXISTS=false
echo -e "${GREEN}服务卸载完成${NC}"
else
echo -e "${YELLOW}将保留旧服务配置,仅修改穿透地址${NC}"
fi
fi
# 用户选择运行模式
echo ""
echo -e "${YELLOW}请选择运行模式:${NC}"
echo "1) 临时运行(前台运行并显示临时访问域名)"
echo "2) 后台运行(自动配置后台服务并显示访问域名)"
read -p "请输入 1 或 2: " MODE
# 输入内网地址
read -p "请输入要穿透的本地地址(例如 127.0.0.1:8080: " LOCAL_ADDR
if [[ "$MODE" == "1" ]]; then
echo -e "${BLUE}正在前台运行 cloudflared...${NC}"
LOGFILE=$(mktemp)
stdbuf -oL "$CLOUDFLARED_BIN" tunnel --url "$LOCAL_ADDR" 2>&1 | tee "$LOGFILE" &
PID=$!
echo -e "${YELLOW}等待 cloudflared 输出访问域名...${NC}"
for i in {1..30}; do
DOMAIN=$(grep -oP 'https://[a-zA-Z0-9-]+\.trycloudflare\.com' "$LOGFILE" | head -n1)
if [[ -n "$DOMAIN" ]]; then
echo ""
echo -e "${GREEN}成功获取公网临时访问域名:$DOMAIN${NC}"
echo ""
wait $PID
exit 0
fi
sleep 1
done
echo -e "${RED}超时未能获取临时域名,日志保存在:$LOGFILE${NC}"
kill $PID 2>/dev/null || true
exit 1
elif [[ "$MODE" == "2" ]]; then
echo -e "${BLUE}正在配置 systemd 服务...${NC}"
if [[ "$SERVICE_EXISTS" == false ]]; then
sudo bash -c "cat > $SERVICE_PATH" <<EOF
[Unit]
Description=Cloudflared Tunnel Service
After=network.target
[Service]
ExecStart=$CLOUDFLARED_BIN tunnel --url $LOCAL_ADDR
Restart=always
StandardOutput=append:$LOG_PATH
StandardError=append:$LOG_PATH
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now cloudflared-tunnel
else
echo -e "${YELLOW}更新 systemd 服务配置中的穿透地址...${NC}"
sudo truncate -s 0 "$LOG_PATH" || sudo bash -c "> $LOG_PATH"
sudo sed -i "s|ExecStart=.*|ExecStart=$CLOUDFLARED_BIN tunnel --url $LOCAL_ADDR|" "$SERVICE_PATH"
sudo systemctl daemon-reload
sudo systemctl restart cloudflared-tunnel
fi
echo -e "${GREEN}服务已启动,日志保存在 $LOG_PATH${NC}"
echo -e "${YELLOW}等待 cloudflared 输出访问域名...${NC}"
for i in {1..30}; do
DOMAIN=$(grep -oP 'https://[a-zA-Z0-9-]+\.trycloudflare\.com' "$LOG_PATH" | head -n1)
if [[ -n "$DOMAIN" ]]; then
echo ""
echo -e "${GREEN}成功获取公网访问域名:$DOMAIN${NC}"
echo ""
exit 0
fi
sleep 1
done
echo -e "${RED}超时未能获取公网访问域名,请稍后手动查看:$LOG_PATH${NC}"
exit 1
else
echo -e "${RED}无效输入,请输入 1 或 2${NC}"
exit 1
fi

29
dev/check_ntpdate.sh Normal file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
# 封装脚本过期函数
check_ntpdate() {
# 设置过期时间
local expire_date="2025-04-10 12:00:00"
# date -d "$(curl -sI https://www.bing.com | grep -i '^date:' | cut -d' ' -f2-)" +'%Y-%m-%d %H:%M:%S UTC+8'
# 获取时间戳(从 https://www.cloudflare.com/cdn-cgi/trace 获取)
timestamp=$(curl -s https://www.cloudflare.com/cdn-cgi/trace | grep -oP 'ts=\K\d+')
# 如果获取时间戳失败,则停止运行脚本
if [[ -z "$timestamp" ]]; then
echo "网络错误,无法获取当前时间戳。"
exit 1
fi
# 转换时间戳为 YYYY-MM-DD HH:MM:SS 格式(北京时间)
current_time=$(TZ="Asia/Shanghai" date -d @$timestamp "+%Y-%m-%d %H:%M:%S")
# 判断当前时间是否超过过期日期
if [[ "$current_time" > "$expire_date" ]]; then
echo "当前脚本已过期,请联系开发者。"
exit 1
fi
}
# 调用函数执行检查
check_ntpdate

1161
dev/docker.sh Normal file

File diff suppressed because it is too large Load Diff

121
dev/go.sh Normal file
View File

@@ -0,0 +1,121 @@
#!/bin/bash
# Go 自动安装配置脚本 (适用于Debian/Ubuntu)
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
if ! grep -qiE 'debian|ubuntu' /etc/os-release; then
echo -e "${RED}错误本脚本仅适用于Debian/Ubuntu系统${NC}"
exit 1
fi
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}请使用 sudo 或以 root 用户运行此脚本${NC}"
exit 1
fi
DEFAULT_VERSION="1.24.0"
read -p "请输入要安装的 Go 版本 [默认: ${DEFAULT_VERSION}]: " GO_VERSION
GO_VERSION=${GO_VERSION:-$DEFAULT_VERSION}
if ! [[ "$GO_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e "${RED}错误:版本号格式不正确,版本号可在 https://golang.org/dl 查看${NC}"
exit 1
fi
GO_TAR="go${GO_VERSION}.linux-amd64.tar.gz"
GO_URL="https://dl.google.com/go/${GO_TAR}"
if command -v go &>/dev/null; then
echo -e "${YELLOW}检测到已安装Go当前版本: $(go version)${NC}"
read -p "是否要卸载当前版本? (y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}卸载旧版Go...${NC}"
rm -rf /usr/local/go
sed -i '/# GoLang/d' /etc/profile
sed -i '/export GOROOT/d' /etc/profile
sed -i '/export GOPATH/d' /etc/profile
sed -i '/export PATH=\$GOROOT/d' /etc/profile
else
echo -e "${YELLOW}保留现有安装,退出脚本${NC}"
exit 0
fi
fi
echo -e "${YELLOW}检查是否存在旧的安装包...${NC}"
cd /tmp
if [ -f "${GO_TAR}" ]; then
echo -e "${YELLOW}删除旧的安装包:${GO_TAR}${NC}"
rm -f "${GO_TAR}"
fi
echo -e "${GREEN}开始下载 Go ${GO_VERSION} 安装包...${NC}"
wget --progress=bar:force "${GO_URL}"
if [ $? -ne 0 ]; then
echo -e "${RED}下载失败,请检查网络连接和版本号是否正确${NC}"
echo "可用的Go版本可在 https://golang.org/dl/ 查看"
exit 1
fi
echo -e "${GREEN}安装 Go 到 /usr/local...${NC}"
rm -rf /usr/local/go
tar -C /usr/local -xzf "${GO_TAR}"
echo -e "${GREEN}配置环境变量...${NC}"
cat >> /etc/profile <<EOF
# GoLang Environment
export GOROOT=/usr/local/go
export GOPATH=\$HOME/go
export PATH=\$GOROOT/bin:\$GOPATH/bin:\$PATH
EOF
for USER_HOME in /home/* /root; do
USER=$(basename "${USER_HOME}")
if [ -d "${USER_HOME}" ]; then
cat >> "${USER_HOME}/.profile" <<EOF
# GoLang Environment
export GOROOT=/usr/local/go
export GOPATH=\$HOME/go
export PATH=\$GOROOT/bin:\$GOPATH/bin:\$PATH
EOF
chown "${USER}:${USER}" "${USER_HOME}/.profile"
fi
done
echo -e "${GREEN}创建 GOPATH 目录...${NC}"
for USER_HOME in /home/* /root; do
if [ -d "${USER_HOME}" ]; then
mkdir -p "${USER_HOME}/go"{,/bin,/pkg,/src}
chown -R "$(basename "${USER_HOME}"):$(basename "${USER_HOME}")" "${USER_HOME}/go"
fi
done
source /etc/profile
echo -e "${GREEN}验证安装...${NC}"
if ! command -v go &>/dev/null; then
echo -e "${RED}Go 安装失败,请检查错误信息${NC}"
exit 1
fi
echo -e "${GREEN}Go 安装成功!版本信息:${NC}"
go version
echo -e "
${GREEN}安装完成Go ${GO_VERSION} 已成功安装并配置。${NC}
${YELLOW}提示:
1. 新终端会话会自动加载 Go 环境变量
2. 当前会话可执行 ${NC}${GREEN}source ~/.profile${NC}${YELLOW} 立即生效
3. Go 工作目录 (GOPATH) 已创建在 ${NC}${GREEN}~/go${NC}
如需卸载,请删除 ${YELLOW}/usr/local/go${NC} 目录。
"

127
dev/image.sh Normal file
View File

@@ -0,0 +1,127 @@
#!/bin/bash
# Debian/Ubuntu 内核切换脚本
# 功能:从 Cloud 内核切换到标准内核
# 适用Debian 11+/Ubuntu 18.04+
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
check_root() {
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}错误:必须使用 root 权限运行此脚本${NC}"
exit 1
fi
}
check_cloud_kernel() {
if ! uname -r | grep -q 'cloud'; then
echo -e "${GREEN}提示:系统已在标准内核运行 ($(uname -r))${NC}"
exit 0
fi
}
purge_cloud_kernel() {
echo -e "${YELLOW}步骤1/4彻底移除 Cloud 内核...${NC}"
# 找出所有 Cloud 内核包
local cloud_pkgs=$(dpkg -l | awk '/linux-(image|headers)-[0-9].*cloud/ {print $2}')
if [ -n "$cloud_pkgs" ]; then
echo -e "正在卸载: ${cloud_pkgs}"
apt purge -y $cloud_pkgs
apt autoremove -y --purge
else
echo -e "${GREEN}提示:未找到 Cloud 内核包${NC}"
fi
}
lock_cloud_kernel() {
echo -e "${YELLOW}步骤2/4锁定 Cloud 内核...${NC}"
# 检查是否还有额外的 Cloud 内核包,如果有则标记为 hold
cloud_kernels=$(apt list --installed 2>/dev/null | grep -i 'linux-image' | grep -i 'cloud' | cut -d'/' -f1)
if [ -n "$cloud_kernels" ]; then
echo "找到以下 Cloud 内核包,正在锁定:$cloud_kernels"
apt-mark hold $cloud_kernels
else
echo -e "${GREEN}提示:未找到任何 Cloud 内核包,跳过锁定步骤。${NC}"
fi
}
force_install_standard() {
echo -e "${YELLOW}步骤3/4安装标准内核...${NC}"
# 根据系统类型选择包名
local image_pkg="linux-image-amd64"
local headers_pkg="linux-headers-amd64"
if grep -q 'ID=ubuntu' /etc/os-release; then
image_pkg="linux-image-generic"
headers_pkg="linux-headers-generic"
fi
# 强制安装并跳过配置提问
DEBIAN_FRONTEND=noninteractive apt install -y --reinstall --allow-downgrades \
"$image_pkg" "$headers_pkg"
# 确保 initramfs 更新
local std_kernel=$(ls /boot/vmlinuz-* | grep -v cloud | sort -V | tail -1 | sed 's|/boot/vmlinuz-||')
update-initramfs -u -k "$std_kernel"
}
nuclear_grub_update() {
echo -e "${YELLOW}步骤4/4重建 GRUB...${NC}"
# 备份原配置
mkdir -p /root/grub_backup
cp -a /boot/grub /root/grub_backup/grub.bak.$(date +%s)
# 生成干净的 GRUB 配置
cat > /etc/default/grub <<'EOF'
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=lsb_release -i -s 2> /dev/null || echo Debian
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX=""
GRUB_DISABLE_OS_PROBER=true
GRUB_DISABLE_RECOVERY=true
EOF
# 完全重建配置
grub-mkconfig -o /boot/grub/grub.cfg
# 确保使用第一个菜单项
grub-set-default 0
update-grub
# 特殊处理 UEFI 系统
if [ -d /sys/firmware/efi ]; then
echo -e "检测到 UEFI 系统,更新引导加载程序..."
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck
fi
}
main() {
echo -e "\n${GREEN}=== Debian/Ubuntu 内核切换脚本 ===${NC}"
check_root
check_cloud_kernel
# 执行核心修复步骤
purge_cloud_kernel
lock_cloud_kernel
force_install_standard
nuclear_grub_update
# 最终验证
echo -e "\n${GREEN}=== 操作完成 ===${NC}"
echo -e "请手动重启系统:"
echo -e "1. 重启系统: ${YELLOW}reboot${NC}"
echo -e "2. 检查内核: ${YELLOW}uname -r${NC}"
touch /root/.kernel_switch_success
}
main "$@"

82
dev/mbit.sh Normal file
View File

@@ -0,0 +1,82 @@
#!/bin/bash
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 检查是否以root权限运行
if [ "$(id -u)" != "0" ]; then
echo -e "${RED}此脚本需要root权限运行请输入 sudo -i 后再执行此脚本${NC}" 1>&2
exit 1
fi
# 检查是否安装了iproute2
if ! command -v tc &> /dev/null
then
echo -e "${BLUE}iproute2未安装正在安装...${NC}"
if ! apt update -q && apt install -yq iproute2; then
echo -e "${RED}安装iproute2失败。请检查您的网络连接和系统状态。${NC}"
exit 1
fi
else
echo -e "${BLUE}iproute2已安装。${NC}"
fi
# 获取默认网络接口
INTERFACE=$(ip route | grep default | awk '{print $5}' | head -n1)
if [ -z "$INTERFACE" ]; then
echo -e "${RED}错误:无法检测到默认网络接口。${NC}"
exit 1
fi
echo -e "${BLUE}检测到默认网络接口: $INTERFACE${NC}"
# 检查是否存在限速规则
if tc qdisc show dev $INTERFACE | grep -q "htb"; then
echo -e "${YELLOW}当前存在限速规则:${NC}"
tc -s qdisc ls dev $INTERFACE
echo -e "${GREEN}如果要更改配置请先清除限速规则,请运行以下命令,然后重新执行脚本。${NC}"
echo -e "${YELLOW}sudo tc qdisc del dev $INTERFACE root${NC}"
exit 0
fi
printf "${GREEN}请输入要限制带宽的端口号(多个端口用逗号分隔): ${NC}"
read PORTS
printf "${GREEN}请输入限速值单位为M: ${NC}"
read LIMIT
if [ -z "$PORTS" ] || [ -z "$LIMIT" ]; then
echo -e "${RED}错误:端口号和限速值不能为空。${NC}"
exit 1
fi
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
echo -e "${RED}错误:限速值必须是一个数字。${NC}"
exit 1
fi
IFS=',' read -ra PORT_ARRAY <<< "$PORTS"
# 创建根qdisc
tc qdisc add dev "$INTERFACE" root handle 1: htb default 12
tc class add dev "$INTERFACE" parent 1: classid 1:1 htb rate "${LIMIT}"mbit
tc class add dev "$INTERFACE" parent 1:1 classid 1:12 htb rate "${LIMIT}"mbit
for PORT in "${PORT_ARRAY[@]}"
do
if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
echo -e "${RED}错误:无效的端口号 $PORT。端口号必须在1-65535之间。${NC}"
continue
fi
tc filter add dev "$INTERFACE" protocol ip parent 1:0 prio 1 u32 match ip dport "$PORT" 0xffff flowid 1:12
echo -e "${GREEN}网络接口 $INTERFACE 的端口 $PORT 带宽限制已设置为 ${LIMIT}Mbit。${NC}"
done
echo -e "${YELLOW}如果要更改配置请先清除限速规则,请运行以下命令,然后重新执行脚本。${NC}"
echo -e "${YELLOW}sudo tc qdisc del dev $INTERFACE root${NC}"

5702
dev/mirrors.sh Normal file

File diff suppressed because it is too large Load Diff

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

584
docs/index.html Normal file
View File

@@ -0,0 +1,584 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IP地址命令生成器</title>
<link rel="icon" href="./favicon.ico">
<style>
:root {
--primary: #4f46e5;
--primary-hover: #4338ca;
--secondary: #10b981;
--secondary-hover: #059669;
--danger: #ef4444;
--danger-hover: #dc2626;
--text: #1f2937;
--text-light: #6b7280;
--bg: #f9fafb;
--card-bg: #ffffff;
--border: #e5e7eb;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 20px;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
font-size: 16px;
}
.container {
background: var(--card-bg);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05), 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 800px;
padding: 20px;
margin: 10px 0;
}
.header {
text-align: center;
margin-bottom: 16px;
}
.header h1 {
font-size: 1.8rem;
font-weight: 600;
margin-bottom: 8px;
color: var(--text);
}
.header p {
color: var(--text-light);
font-size: 1rem;
}
.input-section {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
}
.input-row {
display: flex;
flex-direction: column;
gap: 8px;
}
.flex-row {
display: flex;
gap: 12px;
}
.flex-col {
flex: 1;
}
label {
font-weight: 500;
font-size: 1rem;
color: var(--text);
}
input, textarea, select {
padding: 10px 12px;
font-size: 1rem;
border-radius: 6px;
border: 1px solid var(--border);
transition: border-color 0.2s, box-shadow 0.2s;
width: 100%;
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}
#interface {
max-width: 120px;
}
#ipInput {
min-height: 120px;
resize: vertical;
font-family: monospace;
font-size: 0.95rem;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
button {
padding: 4px 12px;
font-size: 1rem;
font-weight: 500;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
flex: 1;
min-width: 100px;
display: flex;
align-items: center;
justify-content: center;
height: 36px;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-secondary {
background-color: var(--secondary);
color: white;
}
.btn-secondary:hover {
background-color: var(--secondary-hover);
}
.btn-danger {
background-color: var(--danger);
color: white;
}
.btn-danger:hover {
background-color: var(--danger-hover);
}
.tab-container {
margin-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.tab-buttons {
display: flex;
}
.tab-button {
background: transparent;
border: none;
padding: 8px 16px;
cursor: pointer;
font-weight: 500;
color: var(--text-light);
border-bottom: 2px solid transparent;
}
.tab-button.active {
color: var(--primary);
border-bottom: 2px solid var(--primary);
}
.tab-content {
display: none;
padding-top: 16px;
}
.tab-content.active {
display: block;
}
.result-container {
margin-top: 16px;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.result-header h3 {
font-size: 1.1rem;
font-weight: 600;
}
.result-box {
background-color: #f8fafc;
border: 1px solid var(--border);
padding: 12px;
border-radius: 6px;
min-height: 150px;
max-height: 300px;
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Roboto Mono', monospace;
font-size: 0.95rem;
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
.result-box::-webkit-scrollbar {
display: none;
}
.doc-section {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--border);
font-size: 0.9rem;
}
.doc-section h4 {
margin-bottom: 8px;
font-size: 1rem;
}
.doc-section ul {
padding-left: 20px;
margin-bottom: 12px;
}
.doc-section ul li {
margin-bottom: 4px;
}
.doc-section code {
background-color: #f1f5f9;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Roboto Mono', monospace;
font-size: 0.85rem;
}
@media (max-width: 640px) {
.container {
padding: 16px;
}
.header h1 {
font-size: 1.5rem;
}
.button-group {
flex-direction: column;
}
button {
width: 100%;
min-width: auto;
}
#interface {
max-width: 100%;
}
.flex-row {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>公网IP地址添加命令生成器</h1>
<p>为Linux服务器生成添加公网IP的命令</p>
</div>
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" onclick="changeTab(event, 'ipv4Tab')">IPv4</button>
<button class="tab-button" onclick="changeTab(event, 'ipv6Tab')">IPv6</button>
</div>
</div>
<div id="ipv4Tab" class="tab-content active">
<div class="input-section">
<div class="input-row">
<label for="interface">网卡名称</label>
<input type="text" id="interface" value="eth0" placeholder="例如: eth0" />
</div>
<div class="input-row">
<label for="ipInput">公网IPv4地址列表 (每行一个)</label>
<textarea id="ipInput" placeholder="例如:
203.0.113.10
192.0.2.100/24"></textarea>
</div>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateCommand('ipv4')">生成命令</button>
<button class="btn-danger" onclick="generateCSectionCommand()">生成C段命令</button>
</div>
</div>
<div id="ipv6Tab" class="tab-content">
<div class="input-section">
<div class="input-row">
<label for="interfaceIpv6">网卡名称</label>
<input type="text" id="interfaceIpv6" value="eth0" placeholder="例如: eth0" />
</div>
<div class="input-row">
<label for="ipv6Prefix">IPv6地址或网段前缀</label>
<input type="text" id="ipv6Prefix" placeholder="例如: 2001:475:35:3f4::6/64" />
</div>
<div class="input-row">
<div class="flex-row">
<div class="flex-col">
<label for="ipv6Count">生成IPv6地址数量</label>
<input type="number" id="ipv6Count" value="10" min="1" max="100" />
</div>
<div class="flex-col">
<label for="ipv6Mask">子网掩码长度</label>
<input type="number" id="ipv6Mask" value="64" min="1" max="128" />
</div>
</div>
</div>
</div>
<div class="button-group">
<button class="btn-primary" onclick="generateIPv6Command()">生成IPv6命令</button>
</div>
</div>
<div class="result-container">
<div class="result-header">
<h3>生成的命令:</h3>
<button class="btn-secondary" onclick="copyResult()">复制命令</button>
</div>
<div class="result-box" id="resultBox">生成的命令将显示在这里...</div>
</div>
<div class="doc-section">
<h4>Linux服务器添加公网IP说明</h4>
<ul>
<li><strong>临时添加IP</strong>: 上述命令会临时添加IP系统重启后失效</li>
<li><strong>永久添加</strong>: 需要修改网络配置文件 <code>/etc/network/interfaces</code><code>/etc/sysconfig/network-scripts/</code> 下的配置</li>
<li><strong>验证添加</strong>: 使用 <code>ip addr show</code> 命令验证IP是否添加成功</li>
<li><strong>注意事项</strong>: 添加公网IP前需确认IP已分配给您的服务器否则可能导致IP冲突</li>
<li><strong>IPv6注意</strong>: 添加IPv6地址前确保服务器已开启IPv6支持</li>
</ul>
<p><small>使用<code>sysctl -w net.ipv6.conf.all.forwarding=1</code>开启IPv6转发<code>sysctl -p</code>使配置生效</small></p>
</div>
</div>
<script>
function changeTab(evt, tabId) {
const tabContents = document.getElementsByClassName("tab-content");
for (let i = 0; i < tabContents.length; i++) {
tabContents[i].classList.remove("active");
}
const tabButtons = document.getElementsByClassName("tab-button");
for (let i = 0; i < tabButtons.length; i++) {
tabButtons[i].classList.remove("active");
}
document.getElementById(tabId).classList.add("active");
evt.currentTarget.classList.add("active");
}
function generateCommand(type) {
const interfaceName = document.getElementById(type === 'ipv6' ? 'interfaceIpv6' : 'interface').value.trim();
const ipInput = document.getElementById('ipInput').value.trim().split('\n');
let commands = '';
if (interfaceName && ipInput.length > 0) {
ipInput.forEach(ip => {
const trimmedIp = ip.trim();
if (trimmedIp) {
const ipWithMask = trimmedIp.includes('/') ? trimmedIp : `${trimmedIp}/32`;
commands += `sudo ip addr add ${ipWithMask} dev ${interfaceName}\n`;
}
});
document.getElementById('resultBox').textContent = commands || "没有有效的IP地址输入";
} else {
document.getElementById('resultBox').textContent = "请填写网卡名称和IP地址";
}
}
function generateCSectionCommand() {
const interfaceName = document.getElementById('interface').value.trim();
if (!interfaceName) {
document.getElementById('resultBox').textContent = "请先填写网卡名称";
return;
}
// 获取第一个IP作为C段基准
let firstIp = document.getElementById('ipInput').value.trim().split('\n')[0] || '';
firstIp = firstIp.split('/')[0];
if (!firstIp) {
firstIp = '198.51.100.1'; // 使用RFC 5737定义的测试网段作为默认值
}
const ipParts = firstIp.split('.');
if (ipParts.length !== 4 || ipParts.some(part => isNaN(parseInt(part)) || parseInt(part) > 255)) {
document.getElementById('resultBox').textContent = "请输入有效的IP地址作为C段基准";
return;
}
let commands = '';
const baseIp = `${ipParts[0]}.${ipParts[1]}.${ipParts[2]}`;
for (let i = 1; i <= 254; i++) {
commands += `sudo ip addr add ${baseIp}.${i}/24 dev ${interfaceName}\n`;
}
document.getElementById('resultBox').textContent = commands;
}
function generateIPv6Command() {
const interfaceName = document.getElementById('interfaceIpv6').value.trim();
const ipv6Input = document.getElementById('ipv6Prefix').value.trim();
const ipv6Count = parseInt(document.getElementById('ipv6Count').value);
let ipv6Mask = parseInt(document.getElementById('ipv6Mask').value);
if (!interfaceName || !ipv6Input) {
document.getElementById('resultBox').textContent = "请填写网卡名称和IPv6前缀";
return;
}
if (isNaN(ipv6Count) || ipv6Count < 1 || ipv6Count > 1000) {
document.getElementById('resultBox').textContent = "IPv6一次最多生成1000个";
return;
}
if (isNaN(ipv6Mask) || ipv6Mask < 1 || ipv6Mask > 128) {
document.getElementById('resultBox').textContent = "IPv6子网掩码长度必须在1-128之间";
return;
}
let prefix = ipv6Input;
let inputMask = null;
if (ipv6Input.includes('/')) {
const parts = ipv6Input.split('/');
prefix = parts[0];
inputMask = parseInt(parts[1]);
if (!isNaN(inputMask) && inputMask >= 1 && inputMask <= 128) {
ipv6Mask = inputMask;
document.getElementById('ipv6Mask').value = ipv6Mask;
}
}
const prefixSegments = Math.ceil(ipv6Mask / 16);
let networkPrefix = '';
if (prefix.includes('::')) {
const expandedAddress = expandIPv6Address(prefix);
const segments = expandedAddress.split(':');
networkPrefix = segments.slice(0, prefixSegments).join(':');
if (prefixSegments < 8) {
networkPrefix += ':';
}
} else {
const segments = prefix.split(':');
networkPrefix = segments.slice(0, prefixSegments).join(':');
if (prefixSegments < 8) {
networkPrefix += ':';
}
}
let commands = '';
for (let i = 0; i < ipv6Count; i++) {
const randomAddress = generateRandomIPv6InterfaceID(networkPrefix, ipv6Mask);
commands += `sudo ip addr add ${randomAddress}/${ipv6Mask} dev ${interfaceName}\n`;
}
document.getElementById('resultBox').textContent = commands;
}
function expandIPv6Address(address) {
if (address.includes('/')) {
address = address.split('/')[0];
}
if (!address.includes('::')) {
return address;
}
const parts = address.split('::');
const beforeDoubleColon = parts[0] ? parts[0].split(':') : [];
const afterDoubleColon = parts[1] ? parts[1].split(':') : [];
const missingGroups = 8 - (beforeDoubleColon.length + afterDoubleColon.length);
let expandedAddress = '';
if (beforeDoubleColon.length > 0) {
expandedAddress += beforeDoubleColon.join(':') + ':';
}
for (let i = 0; i < missingGroups; i++) {
expandedAddress += '0:';
}
if (afterDoubleColon.length > 0) {
expandedAddress += afterDoubleColon.join(':');
} else {
expandedAddress = expandedAddress.slice(0, -1);
}
return expandedAddress;
}
function generateRandomIPv6InterfaceID(networkPrefix, prefixLength) {
const segmentsToKeep = Math.ceil(prefixLength / 16);
const segmentsToGenerate = 8 - segmentsToKeep;
if (segmentsToGenerate <= 0) {
return networkPrefix;
}
const cleanPrefix = networkPrefix.endsWith(':') ?
networkPrefix.slice(0, -1) : networkPrefix;
const existingSegments = cleanPrefix.split(':');
let randomSegments = [];
for (let i = 0; i < segmentsToGenerate; i++) {
randomSegments.push(generateRandomHex(4));
}
return [...existingSegments, ...randomSegments].join(':');
}
function generateRandomHex(length) {
const hexChars = '0123456789abcdef';
let result = '';
for (let i = 0; i < length; i++) {
result += hexChars.charAt(Math.floor(Math.random() * hexChars.length));
}
return result;
}
function copyResult() {
const resultBox = document.getElementById('resultBox');
const textToCopy = resultBox.textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
const copyBtn = document.querySelector('.btn-secondary');
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制!';
setTimeout(() => {
copyBtn.textContent = originalText;
}, 2000);
}).catch(err => {
console.error('复制失败: ', err);
alert('复制失败,请手动选择文本复制');
});
}
</script>
</body>
</html>

691
docs/md/index.html Normal file
View File

@@ -0,0 +1,691 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markdown 实时编辑器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
/* 默认主题(白天模式) */
--bg-color: #f5f5f5;
--text-color: #333;
--header-bg: #fff;
--header-text: #333;
--button-bg: #c4c4c4;
--button-hover: #939393;
--editor-bg: #fff;
--border-color: #ddd;
--code-bg: #f6f8fa;
--blockquote-color: #6a737d;
--blockquote-border: #dfe2e5;
--dropdown-bg: #fff;
--dropdown-shadow: 0 8px 16px rgba(0,0,0,0.1);
--dropdown-hover-bg: #f1f1f1;
}
/* 黑夜模式 */
body.theme-dark {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--header-bg: #121212;
--header-text: #f0f0f0;
--button-bg: #2c3e50;
--button-hover: #34495e;
--editor-bg: #2d2d2d;
--border-color: #444;
--code-bg: #383838;
--blockquote-color: #aaa;
--blockquote-border: #666;
--dropdown-bg: #3e3e3e;
--dropdown-shadow: 0 8px 16px rgba(0,0,0,0.3);
--dropdown-hover-bg: #555;
}
/* 蓝色模式 */
body.theme-blue {
--bg-color: #e8f4f8;
--text-color: #2c3e50;
--header-bg: #1e88e5;
--header-text: white;
--button-bg: #0d47a1;
--button-hover: #1565c0;
--editor-bg: #f1f8fe;
--border-color: #bbdefb;
--code-bg: #e3f2fd;
--blockquote-color: #546e7a;
--blockquote-border: #64b5f6;
--dropdown-bg: #ffffff;
--dropdown-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
--dropdown-hover-bg: #e3f2fd;
}
/* 赛博朋克模式 */
body.theme-cyberpunk {
--bg-color: #0a0a16;
--text-color: #f0f2f5;
--header-bg: #120458;
--header-text: #00ff9f;
--button-bg: #9900ff;
--button-hover: #b14aff;
--editor-bg: #1a1a2e;
--border-color: #ff00ff;
--code-bg: #2d1b54;
--blockquote-color: #00fff9;
--blockquote-border: #ff00ff;
--dropdown-bg: #1a1a2e;
--dropdown-shadow: 0 5px 15px rgba(255, 0, 255, 0.3);
--dropdown-hover-bg: #2d1b54;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
transition: background-color 0.3s, color 0.3s;
}
.container {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
margin: 0 auto;
}
header {
background-color: var(--header-bg);
color: var(--header-text);
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 {
font-size: 1.1rem;
margin: 0;
}
.toolbar {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
button {
background-color: var(--button-bg);
color: var(--header-text);
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
button:hover {
background-color: var(--button-hover);
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
/* --- Dropdown Menu Styles --- */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-menu {
display: none;
position: absolute;
background-color: var(--dropdown-bg);
min-width: 160px;
box-shadow: var(--dropdown-shadow);
z-index: 1;
border-radius: 4px;
padding: 5px 0;
margin-top: 5px;
}
.dropdown-menu button {
color: var(--text-color);
background-color: transparent;
padding: 10px 15px;
text-decoration: none;
display: block;
width: 100%;
text-align: left;
border-radius: 0;
}
.dropdown-menu button:hover {
background-color: var(--dropdown-hover-bg);
}
.dropdown-menu.show {
display: block;
}
/* --- End Dropdown Styles --- */
.theme-selector {
margin-left: 10px;
}
#themeSelector {
padding: 6px 10px;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--editor-bg);
color: var(--text-color);
cursor: pointer;
font-size: 14px;
outline: none;
}
main {
display: flex;
flex: 1;
overflow: hidden;
transition: all 0.3s ease-in-out;
}
.editor-container,
.preview-container {
flex: 1;
padding: 1rem;
overflow-y: auto;
height: 100%;
transition: flex 0.3s ease-in-out, opacity 0.3s ease-in-out;
min-width: 0;
}
.editor-container {
background-color: var(--editor-bg);
border-right: 1px solid var(--border-color);
/* 保持与.preview-container一致无多余样式 */
border-radius: 0;
padding: 1rem;
}
.preview-container {
background-color: var(--editor-bg);
padding: 1rem;
}
#editor {
width: 100%;
height: 100%;
border: none;
resize: none;
font-family: 'Consolas', monospace;
font-size: 16px;
line-height: 1.6;
padding: 4px 10px;
outline: none;
background-color: transparent;
color: var(--text-color);
}
#editor::-webkit-scrollbar {
height: 6px;
width: 0 !important;
background: transparent;
}
#editor {
scrollbar-width: thin; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.markdown-body {
padding: 10px;
line-height: 1.6;
}
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 1.5rem; margin-bottom: 1rem; font-weight: 600; line-height: 1.25; }
.markdown-body h1 { font-size: 2em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
.markdown-body h2 { font-size: 1.5em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
.markdown-body p { margin-top: 0; margin-bottom: 1rem; }
.markdown-body blockquote { padding: 0 1em; color: var(--blockquote-color); border-left: 0.25em solid var(--blockquote-border); margin: 0 0 1rem 0; }
.markdown-body pre { background-color: var(--code-bg); border-radius: 3px; padding: 16px; overflow: auto; margin-bottom: 1rem; }
.markdown-body pre code { padding: 0; background-color: transparent; }
.markdown-body code { font-family: 'Consolas', monospace; background-color: var(--code-bg); padding: 0.2em 0.4em; border-radius: 3px; }
.markdown-body img { max-width: 100%; }
.markdown-body ul, .markdown-body ol { padding-left: 2em; margin-bottom: 1rem; }
.markdown-body table { border-collapse: collapse; margin-bottom: 1rem; display: block; overflow: auto; width: 100%; }
.markdown-body th, .markdown-body td { padding: 6px 13px; border: 1px solid var(--border-color); }
.editor-container::-webkit-scrollbar,
.preview-container::-webkit-scrollbar {
width: 0 !important;
background: transparent;
}
.editor-container,
.preview-container {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
@media (max-width: 768px) {
header { flex-direction: column; gap: 1rem; }
main { flex-direction: column; }
.editor-container, .preview-container { flex: none; height: 50%; width: 100%; }
.editor-container { border-right: none; border-bottom: 1px solid var(--border-color); }
main.preview-hidden .editor-container,
main.editor-hidden .preview-container { height: 100%; border-bottom: none; }
}
.sync-scroll-toggle {
display: flex;
align-items: center;
margin-left: 18px;
font-size: 13px;
color: var(--header-text);
user-select: none;
}
.sync-scroll-toggle input[type="checkbox"] {
accent-color: var(--button-bg);
margin-right: 4px;
width: 16px;
height: 16px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Markdown 实时编辑器</h1>
<div class="toolbar">
<!-- 文件操作 -->
<button id="saveBtn">保存</button>
<button id="importBtn">导入 MD</button>
<div class="dropdown">
<button class="dropdown-toggle">导出</button>
<div id="export-menu" class="dropdown-menu">
<button id="exportBtn">导出 MD</button>
<button id="exportHtmlBtn">导出 HTML</button>
<button id="exportPdfBtn">导出 PDF</button>
</div>
</div>
<!-- 视图选项 -->
<div class="dropdown">
<button class="dropdown-toggle">视图</button>
<div id="view-menu" class="dropdown-menu">
<button id="toggleEditorBtn">切换编辑区</button>
<button id="togglePreviewBtn">切换预览区</button>
<button id="mdGuideBtn">Markdown 指南</button>
</div>
</div>
<!-- 主题选择 -->
<div class="theme-selector">
<select id="themeSelector">
<option value="default">白天模式</option>
<option value="dark">黑夜模式</option>
<option value="blue">蓝色模式</option>
<option value="cyberpunk">赛博朋克</option>
</select>
</div>
<!-- 同步滚动开关 -->
<label class="sync-scroll-toggle" title="编辑区滚动时预览区也跟随滚动">
<input type="checkbox" id="syncScrollToggle" checked>
同步滚动
</label>
</div>
</header>
<main>
<div class="editor-container">
<textarea id="editor" placeholder="在此输入 Markdown 内容..."></textarea>
</div>
<div class="preview-container">
<div id="preview" class="markdown-body"></div>
</div>
</main>
</div>
<!-- 依赖库 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<!-- 主逻辑 -->
<script>
window.addEventListener('load', function () {
// --- DOM 元素获取 ---
const editor = document.getElementById('editor');
const preview = document.getElementById('preview');
const mainContainer = document.querySelector('main');
const syncScrollToggle = document.getElementById('syncScrollToggle');
// 工具栏按钮
const saveBtn = document.getElementById('saveBtn');
const importBtn = document.getElementById('importBtn');
const exportBtn = document.getElementById('exportBtn');
const exportHtmlBtn = document.getElementById('exportHtmlBtn');
const exportPdfBtn = document.getElementById('exportPdfBtn');
const toggleEditorBtn = document.getElementById('toggleEditorBtn');
const togglePreviewBtn = document.getElementById('togglePreviewBtn');
const mdGuideBtn = document.getElementById('mdGuideBtn');
const themeSelector = document.getElementById('themeSelector');
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
// --- 状态变量 ---
let isShowingGuide = false;
let userContentBeforeGuide = '';
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.md,.markdown,text/markdown';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
marked.setOptions({ breaks: true, gfm: true, headerIds: true, sanitize: false });
// --- 核心功能 ---
function renderMarkdown(shouldSave = true) {
const markdownText = editor.value;
const htmlContent = marked.parse(markdownText);
preview.innerHTML = htmlContent;
if (shouldSave && !isShowingGuide) {
localStorage.setItem('markdown-content', markdownText);
}
}
function getFilename() {
const firstLine = editor.value.trim().split('\n')[0];
const sanitized = firstLine.replace(/[^a-zA-Z0-9\u4e00-\u9fa5\s]/g, '').trim();
return sanitized && sanitized.length > 0 ? sanitized : 'markdown-export';
}
// --- 初始化 ---
const savedContent = localStorage.getItem('markdown-content');
if (savedContent) editor.value = savedContent;
renderMarkdown(false);
const initialTheme = localStorage.getItem('markdown-theme') || 'default';
applyTheme(initialTheme);
themeSelector.value = initialTheme;
updateViewButtonsText();
// --- 事件监听器 ---
editor.addEventListener('input', () => {
if (isShowingGuide) {
isShowingGuide = false;
mdGuideBtn.textContent = 'Markdown 指南';
}
renderMarkdown(true);
});
saveBtn.addEventListener('click', () => {
localStorage.setItem('markdown-content', editor.value);
alert('内容已手动保存到本地存储!');
});
importBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
editor.value = e.target.result;
if (isShowingGuide) {
isShowingGuide = false;
mdGuideBtn.textContent = '返回编辑';
}
renderMarkdown(true);
};
reader.readAsText(file);
fileInput.value = '';
});
exportBtn.addEventListener('click', () => {
const blob = new Blob([editor.value], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${getFilename()}.md`;
a.click();
URL.revokeObjectURL(url);
});
exportHtmlBtn.addEventListener('click', () => {
const filename = getFilename() + '.html';
// 构造完整HTML文档
const htmlContent = `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<title>${filename}</title>\n<style>${document.querySelector('style').innerHTML}</style>\n</head>\n<body>\n<div class=\"markdown-body\">${preview.innerHTML}</div>\n</body>\n</html>`;
const blob = new Blob([htmlContent], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
});
exportPdfBtn.addEventListener('click', async () => {
const btn = exportPdfBtn;
btn.disabled = true;
btn.textContent = '正在生成...';
try {
const element = document.getElementById('preview');
const opt = {
margin: 15,
filename: `${getFilename()}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
await html2pdf().from(element).set(opt).save();
} catch (error) {
console.error("PDF导出失败:", error);
alert("导出 PDF 时出错,请查看控制台获取更多信息。");
} finally {
btn.disabled = false;
btn.textContent = '导出 PDF';
}
});
// --- 视图切换功能 ---
function updateViewButtonsText() {
toggleEditorBtn.textContent = mainContainer.classList.contains('editor-hidden') ? '显示编辑区' : '隐藏编辑区';
togglePreviewBtn.textContent = mainContainer.classList.contains('preview-hidden') ? '显示预览区' : '隐藏预览区';
mdGuideBtn.textContent = isShowingGuide ? '返回编辑' : 'Markdown 指南';
}
toggleEditorBtn.addEventListener('click', function () {
mainContainer.classList.remove('preview-hidden');
mainContainer.classList.toggle('editor-hidden');
updateViewButtonsText();
});
togglePreviewBtn.addEventListener('click', function () {
mainContainer.classList.remove('editor-hidden');
mainContainer.classList.toggle('preview-hidden');
updateViewButtonsText();
});
const markdownGuideContent = `...`; // 指南内容太长,为保持代码清爽此处省略,实际代码会包含完整内容。
mdGuideBtn.addEventListener('click', () => {
isShowingGuide = !isShowingGuide;
if (isShowingGuide) {
userContentBeforeGuide = editor.value;
editor.value = markdownGuideContent.replace('...', `
# Markdown 语法指南
这是一个 Markdown 格式的快速参考指南,您可以随时查看这个页面来学习 Markdown 的使用方法。
## 基本语法
### 标题
\`\`\`
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
\`\`\`
### 强调
\`\`\`
*斜体文本* 或 _斜体文本_
**粗体文本** 或 __粗体文本__
***粗斜体文本*** 或 ___粗斜体文本___
\`\`\`
### 列表
无序列表:
\`\`\`
- 项目1
- 项目2
- 子项目A
- 子项目B
\`\`\`
有序列表:
\`\`\`
1. 第一项
2. 第二项
3. 第三项
\`\`\`
### 链接
\`\`\`
[链接文本](https://www.example.com)
\`\`\`
### 图片
\`\`\`
![替代文本](图片URL)
\`\`\`
## 高级语法
### 表格
\`\`\`
| 表头1 | 表头2 | 表头3 |
| :--- | :---: | ---: |
| 左对齐 | 居中对齐 | 右对齐 |
| 单元格4 | 单元格5 | 单元格6 |
\`\`\`
### 代码块
行内代码使用反引号 \`code\` 包裹。
代码块使用三个反引号包裹:
\`\`\`javascript
function greet(name) {
console.log("Hello, " + name + "!");
}
greet('World');
\`\`\`
### 引用
\`\`\`
> 这是一段引用的文字。
>
> > 引用可以嵌套。
\`\`\`
### 分隔线
使用三个或更多的星号、破折号或下划线来创建分隔线。
\`\`\`
***
---
___
\`\`\`
### 删除线
\`\`\`
~~这段文字将被划掉。~~
\`\`\`
`);
renderMarkdown(false);
} else {
editor.value = userContentBeforeGuide;
renderMarkdown(true);
}
updateViewButtonsText();
});
// --- 主题切换 ---
themeSelector.addEventListener('change', (e) => {
applyTheme(e.target.value);
localStorage.setItem('markdown-theme', e.target.value);
});
function applyTheme(theme) {
document.body.className = '';
document.body.classList.add(`theme-${theme}`);
}
// --- 下拉菜单交互逻辑 ---
function closeAllDropdowns() {
document.querySelectorAll('.dropdown-menu').forEach(menu => {
menu.classList.remove('show');
});
}
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', function(event) {
event.stopPropagation();
const currentMenu = this.nextElementSibling;
const isShown = currentMenu.classList.contains('show');
closeAllDropdowns(); // Close others first
if (!isShown) {
currentMenu.classList.add('show');
}
});
});
// Click outside to close dropdowns
window.addEventListener('click', function(event) {
if (!event.target.matches('.dropdown-toggle')) {
closeAllDropdowns();
}
});
// --- 同步滚动功能 ---
let isSyncScroll = true;
let isPreviewScrolling = false;
syncScrollToggle.addEventListener('change', function() {
isSyncScroll = this.checked;
});
editor.addEventListener('scroll', function() {
if (!isSyncScroll) return;
if (isPreviewScrolling) return;
const editorScroll = editor.scrollTop;
const editorHeight = editor.scrollHeight - editor.clientHeight;
const percent = editorHeight > 0 ? editorScroll / editorHeight : 0;
const previewContainer = preview.parentElement;
const previewHeight = previewContainer.scrollHeight - previewContainer.clientHeight;
isPreviewScrolling = true;
previewContainer.scrollTop = percent * previewHeight;
setTimeout(() => { isPreviewScrolling = false; }, 10);
});
});
</script>
</body>
</html>

1090
docs/nezha/index.html Normal file

File diff suppressed because it is too large Load Diff

15
gost/README.md Normal file
View File

@@ -0,0 +1,15 @@
### gost中转脚本
```
bash <(curl -sSL https://raw.githubusercontent.com/sky22333/shell/main/gost/gost.sh)
```
---
### 国内加速
```
bash <(curl -sSL https://gh-proxy.com/https://raw.githubusercontent.com/sky22333/shell/main/gost/gost.sh)
```
---
基于 [Multi-EasyGost](https://github.com/KANIKIG/Multi-EasyGost) 修改

7
gost/config.json Normal file
View File

@@ -0,0 +1,7 @@
{
"Debug": true,
"Retries": 0,
"ServeNodes": [
"udp://127.0.0.1:65532"
]
}

15
gost/gost.service Normal file
View File

@@ -0,0 +1,15 @@
[Unit]
Description=gost
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service
[Service]
Type=simple
User=root
Restart=always
RestartSec=5
DynamicUser=true
ExecStart=/usr/bin/gost -C /etc/gost/config.json
[Install]
WantedBy=multi-user.target

942
gost/gost.sh Normal file
View File

@@ -0,0 +1,942 @@
#! /bin/bash
# 鸣谢https://github.com/KANIKIG/Multi-EasyGost
Green_font_prefix="\033[32m" && Red_font_prefix="\033[31m" && Green_background_prefix="\033[42;37m" && Font_color_suffix="\033[0m"
Info="${Green_font_prefix}[信息]${Font_color_suffix}"
Error="${Red_font_prefix}[错误]${Font_color_suffix}"
ct_new_ver="2.11.2" # 使用固定的稳定版本
gost_conf_path="/etc/gost/config.json"
raw_conf_path="/etc/gost/rawconf"
function check_sys() {
if [[ -f /etc/redhat-release ]]; then
release="centos"
elif cat /etc/issue | grep -q -E -i "debian"; then
release="debian"
elif cat /etc/issue | grep -q -E -i "ubuntu"; then
release="ubuntu"
elif cat /etc/issue | grep -q -E -i "centos|red hat|redhat"; then
release="centos"
elif cat /proc/version | grep -q -E -i "debian"; then
release="debian"
elif cat /proc/version | grep -q -E -i "ubuntu"; then
release="ubuntu"
elif cat /proc/version | grep -q -E -i "centos|red hat|redhat"; then
release="centos"
fi
bit=$(uname -m)
if test "$bit" != "x86_64"; then
echo "请输入你的芯片架构,/386/armv5/armv6/armv7/armv8"
read bit
else
bit="amd64"
fi
}
function Installation_dependency() {
gzip_ver=$(gzip -V)
if [[ -z ${gzip_ver} ]]; then
if [[ ${release} == "centos" ]]; then
yum update
yum install -y gzip wget
else
apt update
apt install -y gzip wget
fi
fi
}
function check_root() {
[[ $EUID != 0 ]] && echo -e "${Error} 当前非root用户无法继续操作请使用root权限运行此脚本。" && exit 1
}
function check_file() {
if test ! -d "/usr/lib/systemd/system/"; then
mkdir /usr/lib/systemd/system
chmod -R 755 /usr/lib/systemd/system
fi
}
function check_nor_file() {
rm -rf "$(pwd)"/gost
rm -rf "$(pwd)"/gost.service
rm -rf "$(pwd)"/config.json
rm -rf /etc/gost
rm -rf /usr/lib/systemd/system/gost.service
rm -rf /usr/bin/gost
}
function Install_ct() {
check_root
check_nor_file
Installation_dependency
check_file
check_sys
loc=$(curl -s https://www.cloudflare.com/cdn-cgi/trace | grep 'loc=' | cut -d= -f2)
gh_proxy_prefix=""
if [[ "$loc" == "CN" ]]; then
echo -e "检测到服务器位于中国大陆GitHub 下载可能较慢。"
read -e -p "是否使用 GitHub 加速代理? [y/n]:" use_proxy
[[ -z $use_proxy ]] && use_proxy="y"
if [[ $use_proxy == [Yy] ]]; then
read -e -p "请输入加速域名回车使用内置加速域名https://gh-proxy.com:" input_proxy
[[ -z $input_proxy ]] && input_proxy="https://gh-proxy.com"
gh_proxy_prefix="${input_proxy}/"
fi
fi
# 下载和安装 gost
rm -rf gost-linux-"$bit"-"$ct_new_ver".gz
curl --progress-bar -L -o gost-linux-"${bit}"-"${ct_new_ver}".gz "${gh_proxy_prefix}https://github.com/ginuerzh/gost/releases/download/v${ct_new_ver}/gost-linux-${bit}-${ct_new_ver}.gz"
gunzip gost-linux-"$bit"-"$ct_new_ver".gz
mv gost-linux-"$bit"-"$ct_new_ver" gost
mv gost /usr/bin/gost
chmod -R 755 /usr/bin/gost
# 下载 systemd 启动服务文件
wget -q --no-check-certificate "${gh_proxy_prefix}https://raw.githubusercontent.com/sky22333/shell/main/gost/gost.service"
chmod -R 755 gost.service
mv gost.service /usr/lib/systemd/system
# 下载默认配置文件
mkdir -p /etc/gost
wget -q --no-check-certificate "${gh_proxy_prefix}https://raw.githubusercontent.com/sky22333/shell/main/gost/config.json"
mv config.json /etc/gost
chmod -R 755 /etc/gost
# 启动服务
systemctl enable gost && systemctl restart gost
echo "------------------------------"
if [[ -f /usr/bin/gost && -f /usr/lib/systemd/system/gost.service && -f /etc/gost/config.json ]]; then
echo "gost安装成功"
rm -rf "$(pwd)/gost" "$(pwd)/gost.service" "$(pwd)/config.json"
else
echo "gost安装失败"
rm -rf "$(pwd)/gost" "$(pwd)/gost.service" "$(pwd)/config.json" "$(pwd)/gost.sh"
fi
}
function Uninstall_ct() {
systemctl stop gost.service 2>/dev/null
systemctl disable gost.service 2>/dev/null
rm -rf /usr/bin/gost
rm -rf /usr/lib/systemd/system/gost.service
rm -rf /etc/gost
systemctl daemon-reload
echo "gost已经成功删除"
}
function Start_ct() {
systemctl start gost
echo "已启动"
}
function Stop_ct() {
systemctl stop gost
echo "已停止"
}
function Restart_ct() {
rm -rf /etc/gost/config.json
confstart
writeconf
conflast
systemctl restart gost
echo "已重读配置并重启"
}
function read_protocol() {
echo -e "请问您要设置哪种功能: "
echo -e "-----------------------------------"
echo -e "[1] tcp+udp流量转发, 不加密"
echo -e "说明: 一般设置在国内中转机上"
echo -e "-----------------------------------"
echo -e "[2] 加密隧道流量转发"
echo -e "说明: 用于转发原本加密等级较低的流量, 一般设置在国内中转机上"
echo -e " 选择此协议意味着你还有一台机器用于接收此加密流量, 之后须在那台机器上配置协议[3]进行对接"
echo -e "-----------------------------------"
echo -e "[3] 解密由gost传输而来的流量并转发"
echo -e "说明: 对于经由gost加密中转的流量, 通过此选项进行解密并转发给本机的代理服务端口或转发给其他远程机器"
echo -e " 一般设置在用于接收中转流量的国外机器上"
echo -e "-----------------------------------"
echo -e "[4] 一键安装ss/socks5/http代理"
echo -e "说明: 使用gost内置的代理协议轻量且易于管理"
echo -e "-----------------------------------"
echo -e "[5] 进阶:多落地均衡负载"
echo -e "说明: 支持各种加密方式的简单均衡负载"
echo -e "-----------------------------------"
echo -e "[6] 进阶转发CDN自选节点"
echo -e "说明: 只需在中转机设置"
echo -e "-----------------------------------"
read -p "请选择: " numprotocol
if [ "$numprotocol" == "1" ]; then
flag_a="nonencrypt"
elif [ "$numprotocol" == "2" ]; then
encrypt
elif [ "$numprotocol" == "3" ]; then
decrypt
elif [ "$numprotocol" == "4" ]; then
proxy
elif [ "$numprotocol" == "5" ]; then
enpeer
elif [ "$numprotocol" == "6" ]; then
cdn
else
echo "type error, please try again"
exit
fi
}
function read_s_port() {
if [ "$flag_a" == "ss" ]; then
echo -e "-----------------------------------"
read -p "请输入ss密码: " flag_b
elif [ "$flag_a" == "socks" ]; then
echo -e "-----------------------------------"
read -p "请输入socks密码: " flag_b
elif [ "$flag_a" == "http" ]; then
echo -e "-----------------------------------"
read -p "请输入http密码: " flag_b
else
echo -e "------------------------------------------------------------------"
echo -e "请问你要将本机哪个端口接收到的流量进行转发?"
read -p "请输入: " flag_b
fi
}
function read_d_ip() {
if [ "$flag_a" == "ss" ]; then
echo -e "------------------------------------------------------------------"
echo -e "请问您要设置的ss加密(仅提供常用的几种): "
echo -e "-----------------------------------"
echo -e "[1] aes-256-gcm"
echo -e "[2] aes-256-cfb"
echo -e "[3] chacha20-ietf-poly1305"
echo -e "[4] chacha20"
echo -e "[5] rc4-md5"
echo -e "[6] AEAD_CHACHA20_POLY1305"
echo -e "-----------------------------------"
read -p "请选择ss加密方式: " ssencrypt
if [ "$ssencrypt" == "1" ]; then
flag_c="aes-256-gcm"
elif [ "$ssencrypt" == "2" ]; then
flag_c="aes-256-cfb"
elif [ "$ssencrypt" == "3" ]; then
flag_c="chacha20-ietf-poly1305"
elif [ "$ssencrypt" == "4" ]; then
flag_c="chacha20"
elif [ "$ssencrypt" == "5" ]; then
flag_c="rc4-md5"
elif [ "$ssencrypt" == "6" ]; then
flag_c="AEAD_CHACHA20_POLY1305"
else
echo "type error, please try again"
exit
fi
elif [ "$flag_a" == "socks" ]; then
echo -e "-----------------------------------"
read -p "请输入socks用户名: " flag_c
elif [ "$flag_a" == "http" ]; then
echo -e "-----------------------------------"
read -p "请输入http用户名: " flag_c
elif [[ "$flag_a" == "peer"* ]]; then
echo -e "------------------------------------------------------------------"
echo -e "请输入落地列表文件名"
read -e -p "自定义但不同配置应不重复不用输入后缀例如ips1、iplist2: " flag_c
touch $flag_c.txt
echo -e "------------------------------------------------------------------"
echo -e "请依次输入你要均衡负载的落地ip与端口"
while true; do
echo -e "请问你要将本机从${flag_b}接收到的流量转发向的IP或域名?"
read -p "请输入: " peer_ip
echo -e "请问你要将本机从${flag_b}接收到的流量转发向${peer_ip}的哪个端口?"
read -p "请输入: " peer_port
echo -e "$peer_ip:$peer_port" >>$flag_c.txt
read -e -p "是否继续添加落地?[Y/n]:" addyn
[[ -z ${addyn} ]] && addyn="y"
if [[ ${addyn} == [Nn] ]]; then
echo -e "------------------------------------------------------------------"
echo -e "已在root目录创建$flag_c.txt您可以随时编辑该文件修改落地信息重启gost即可生效"
echo -e "------------------------------------------------------------------"
break
else
echo -e "------------------------------------------------------------------"
echo -e "继续添加均衡负载落地配置"
fi
done
elif [[ "$flag_a" == "cdn"* ]]; then
echo -e "------------------------------------------------------------------"
echo -e "将本机从${flag_b}接收到的流量转发向的自选ip:"
read -p "请输入: " flag_c
echo -e "请问你要将本机从${flag_b}接收到的流量转发向${flag_c}的哪个端口?"
echo -e "[1] 80"
echo -e "[2] 443"
echo -e "[3] 自定义端口如8080等"
read -p "请选择端口: " cdnport
if [ "$cdnport" == "1" ]; then
flag_c="$flag_c:80"
elif [ "$cdnport" == "2" ]; then
flag_c="$flag_c:443"
elif [ "$cdnport" == "3" ]; then
read -p "请输入自定义端口: " customport
flag_c="$flag_c:$customport"
else
echo "type error, please try again"
exit
fi
else
echo -e "------------------------------------------------------------------"
echo -e "请问你要将本机从${flag_b}接收到的流量转发向哪个IP或域名?"
echo -e "注: IP既可以是[远程机器/当前机器]的公网IP, 也可是以本机本地回环IP(即127.0.0.1)"
if [[ ${is_cert} == [Yy] ]]; then
echo -e "注意: 落地机开启自定义tls证书务必填写${Red_font_prefix}域名${Font_color_suffix}"
fi
read -p "请输入: " flag_c
fi
}
function read_d_port() {
if [ "$flag_a" == "ss" ]; then
echo -e "------------------------------------------------------------------"
echo -e "请问你要设置ss代理服务的端口?"
read -p "请输入: " flag_d
elif [ "$flag_a" == "socks" ]; then
echo -e "------------------------------------------------------------------"
echo -e "请问你要设置socks代理服务的端口?"
read -p "请输入: " flag_d
elif [ "$flag_a" == "http" ]; then
echo -e "------------------------------------------------------------------"
echo -e "请问你要设置http代理服务的端口?"
read -p "请输入: " flag_d
elif [[ "$flag_a" == "peer"* ]]; then
echo -e "------------------------------------------------------------------"
echo -e "您要设置的均衡负载策略: "
echo -e "-----------------------------------"
echo -e "[1] round - 轮询"
echo -e "[2] random - 随机"
echo -e "[3] fifo - 自上而下"
echo -e "-----------------------------------"
read -p "请选择均衡负载类型: " numstra
if [ "$numstra" == "1" ]; then
flag_d="round"
elif [ "$numstra" == "2" ]; then
flag_d="random"
elif [ "$numstra" == "3" ]; then
flag_d="fifo"
else
echo "type error, please try again"
exit
fi
elif [[ "$flag_a" == "cdn"* ]]; then
echo -e "------------------------------------------------------------------"
read -p "请输入host:" flag_d
else
echo -e "------------------------------------------------------------------"
echo -e "请问你要将本机从${flag_b}接收到的流量转发向${flag_c}的哪个端口?"
read -p "请输入: " flag_d
if [[ ${is_cert} == [Yy] ]]; then
flag_d="$flag_d?secure=true"
fi
fi
}
function writerawconf() {
echo $flag_a"/""$flag_b""#""$flag_c""#""$flag_d" >>$raw_conf_path
}
function rawconf() {
read_protocol
read_s_port
read_d_ip
read_d_port
writerawconf
}
function eachconf_retrieve() {
d_server=${trans_conf#*#}
d_port=${d_server#*#}
d_ip=${d_server%#*}
flag_s_port=${trans_conf%%#*}
s_port=${flag_s_port#*/}
is_encrypt=${flag_s_port%/*}
}
function confstart() {
echo "{
\"Debug\": true,
\"Retries\": 0,
\"ServeNodes\": [" >>$gost_conf_path
}
function multiconfstart() {
echo " {
\"Retries\": 0,
\"ServeNodes\": [" >>$gost_conf_path
}
function conflast() {
echo " ]
}" >>$gost_conf_path
}
function multiconflast() {
if [ $i -eq $count_line ]; then
echo " ]
}" >>$gost_conf_path
else
echo " ]
}," >>$gost_conf_path
fi
}
function encrypt() {
echo -e "请问您要设置的转发传输类型: "
echo -e "-----------------------------------"
echo -e "[1] tls隧道"
echo -e "[2] ws隧道"
echo -e "[3] wss隧道"
echo -e "注意: 同一则转发中转与落地传输类型必须对应本脚本默认开启tcp+udp"
echo -e "-----------------------------------"
read -p "请选择转发传输类型: " numencrypt
if [ "$numencrypt" == "1" ]; then
flag_a="encrypttls"
echo -e "注意: 选择 是 将针对落地的自定义证书开启证书校验保证安全性,稍后落地机务必填写${Red_font_prefix}域名${Font_color_suffix}"
read -e -p "落地机是否开启了自定义tls证书[y/n]:" is_cert
elif [ "$numencrypt" == "2" ]; then
flag_a="encryptws"
elif [ "$numencrypt" == "3" ]; then
flag_a="encryptwss"
echo -e "注意: 选择 是 将针对落地的自定义证书开启证书校验保证安全性,稍后落地机务必填写${Red_font_prefix}域名${Font_color_suffix}"
read -e -p "落地机是否开启了自定义tls证书[y/n]:" is_cert
else
echo "type error, please try again"
exit
fi
}
function enpeer() {
echo -e "请问您要设置的均衡负载传输类型: "
echo -e "-----------------------------------"
echo -e "[1] 不加密转发"
echo -e "[2] tls隧道"
echo -e "[3] ws隧道"
echo -e "[4] wss隧道"
echo -e "注意: 同一则转发,中转与落地传输类型必须对应!本脚本默认同一配置的传输类型相同"
echo -e "此脚本仅支持简单型均衡负载,具体可参考官方文档"
echo -e "-----------------------------------"
read -p "请选择转发传输类型: " numpeer
if [ "$numpeer" == "1" ]; then
flag_a="peerno"
elif [ "$numpeer" == "2" ]; then
flag_a="peertls"
elif [ "$numpeer" == "3" ]; then
flag_a="peerws"
elif [ "$numpeer" == "4" ]; then
flag_a="peerwss"
else
echo "type error, please try again"
exit
fi
}
function cdn() {
echo -e "请问您要设置的CDN传输类型: "
echo -e "-----------------------------------"
echo -e "[1] 不加密转发"
echo -e "[2] ws隧道"
echo -e "[3] wss隧道"
echo -e "注意: 同一则转发,中转与落地传输类型必须对应!"
echo -e "此功能只需在中转机设置"
echo -e "-----------------------------------"
read -p "请选择CDN转发传输类型: " numcdn
if [ "$numcdn" == "1" ]; then
flag_a="cdnno"
elif [ "$numcdn" == "2" ]; then
flag_a="cdnws"
elif [ "$numcdn" == "3" ]; then
flag_a="cdnwss"
else
echo "type error, please try again"
exit
fi
}
function status() {
systemctl status gost
}
function cert() {
echo -e "-----------------------------------"
echo -e "[1] ACME一键申请证书"
echo -e "[2] 手动上传证书"
echo -e "-----------------------------------"
echo -e "说明: 仅用于落地机配置默认使用的gost内置的证书可能带来安全问题使用自定义证书提高安全性"
echo -e " 配置后对本机所有tls/wss解密生效无需再次设置"
read -p "请选择证书生成方式: " numcert
if [ "$numcert" == "1" ]; then
check_sys
if [[ ${release} == "centos" ]]; then
yum install -y socat
else
apt-get install -y socat
fi
read -p "请随意输入一个邮箱用于申请证书:" zeromail
read -p "请输入解析到本机的域名:" domain
curl https://get.acme.sh | sh
"$HOME"/.acme.sh/acme.sh --set-default-ca --server zerossl
"$HOME"/.acme.sh/acme.sh --register-account -m "${zeromail}" --server zerossl
echo -e "ACME证书申请程序安装成功"
echo -e "-----------------------------------"
echo -e "[1] HTTP申请需要80端口未占用"
echo -e "[2] Cloudflare DNS API 申请需要输入APIKEY"
echo -e "-----------------------------------"
read -p "请选择证书申请方式: " certmethod
if [ "$certmethod" == "1" ]; then
echo -e "请确认本机${Red_font_prefix}80${Font_color_suffix}端口未被占用, 否则会申请失败"
if "$HOME"/.acme.sh/acme.sh --issue -d "${domain}" --standalone -k ec-256 --force; then
echo -e "SSL 证书生成成功默认申请高安全性的ECC证书"
if [ ! -d "$HOME/gost_cert" ]; then
mkdir $HOME/gost_cert
fi
if "$HOME"/.acme.sh/acme.sh --installcert -d "${domain}" --fullchainpath $HOME/gost_cert/cert.pem --keypath $HOME/gost_cert/key.pem --ecc --force; then
echo -e "SSL 证书配置成功,且会自动续签,证书及秘钥位于用户目录下的 ${Red_font_prefix}gost_cert${Font_color_suffix} 目录"
echo -e "证书目录名与证书文件名请勿更改; 删除 gost_cert 目录后用脚本重启会自动启用gost内置证书"
echo -e "-----------------------------------"
fi
else
echo -e "SSL 证书生成失败"
exit 1
fi
else
read -p "请输入Cloudflare账户邮箱" cfmail
read -p "请输入Cloudflare Global API Key" cfkey
export CF_Key="${cfkey}"
export CF_Email="${cfmail}"
if "$HOME"/.acme.sh/acme.sh --issue --dns dns_cf -d "${domain}" --standalone -k ec-256 --force; then
echo -e "SSL 证书生成成功默认申请高安全性的ECC证书"
if [ ! -d "$HOME/gost_cert" ]; then
mkdir $HOME/gost_cert
fi
if "$HOME"/.acme.sh/acme.sh --installcert -d "${domain}" --fullchainpath $HOME/gost_cert/cert.pem --keypath $HOME/gost_cert/key.pem --ecc --force; then
echo -e "SSL 证书配置成功,且会自动续签,证书及秘钥位于用户目录下的 ${Red_font_prefix}gost_cert${Font_color_suffix} 目录"
echo -e "证书目录名与证书文件名请勿更改; 删除 gost_cert 目录后使用脚本重启, 会重新启用gost内置证书"
echo -e "-----------------------------------"
fi
else
echo -e "SSL 证书生成失败"
exit 1
fi
fi
elif [ "$numcert" == "2" ]; then
if [ ! -d "$HOME/gost_cert" ]; then
mkdir $HOME/gost_cert
fi
echo -e "-----------------------------------"
echo -e "已在用户目录建立 ${Red_font_prefix}gost_cert${Font_color_suffix} 目录,请将证书文件 cert.pem 与秘钥文件 key.pem 上传到该目录"
echo -e "证书与秘钥文件名必须与上述一致,目录名也请勿更改"
echo -e "上传成功后用脚本重启gost会自动启用无需再设置; 删除 gost_cert 目录后用脚本重启会重新启用gost内置证书"
echo -e "-----------------------------------"
else
echo "type error, please try again"
exit
fi
}
function decrypt() {
echo -e "请问您要设置的解密传输类型: "
echo -e "-----------------------------------"
echo -e "[1] tls"
echo -e "[2] ws"
echo -e "[3] wss"
echo -e "注意: 同一则转发中转与落地传输类型必须对应本脚本默认开启tcp+udp"
echo -e "-----------------------------------"
read -p "请选择解密传输类型: " numdecrypt
if [ "$numdecrypt" == "1" ]; then
flag_a="decrypttls"
elif [ "$numdecrypt" == "2" ]; then
flag_a="decryptws"
elif [ "$numdecrypt" == "3" ]; then
flag_a="decryptwss"
else
echo "type error, please try again"
exit
fi
}
function proxy() {
echo -e "------------------------------------------------------------------"
echo -e "请问您要设置的代理类型: "
echo -e "-----------------------------------"
echo -e "[1] shadowsocks"
echo -e "[2] socks5(强烈建议加隧道用于Telegram代理)"
echo -e "[3] http"
echo -e "-----------------------------------"
read -p "请选择代理类型: " numproxy
if [ "$numproxy" == "1" ]; then
flag_a="ss"
elif [ "$numproxy" == "2" ]; then
flag_a="socks"
elif [ "$numproxy" == "3" ]; then
flag_a="http"
else
echo "type error, please try again"
exit
fi
}
function method() {
if [ $i -eq 1 ]; then
if [ "$is_encrypt" == "nonencrypt" ]; then
echo " \"tcp://:$s_port/$d_ip:$d_port\",
\"udp://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnno" ]; then
echo " \"tcp://:$s_port/$d_ip?host=$d_port\",
\"udp://:$s_port/$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerno" ]; then
echo " \"tcp://:$s_port?ip=/root/$d_ip.txt&strategy=$d_port\",
\"udp://:$s_port?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encrypttls" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+tls://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encryptws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encryptwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peertls" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+tls://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "decrypttls" ]; then
if [ -d "$HOME/gost_cert" ]; then
echo " \"relay+tls://:$s_port/$d_ip:$d_port?cert=/root/gost_cert/cert.pem&key=/root/gost_cert/key.pem\"" >>$gost_conf_path
else
echo " \"relay+tls://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
fi
elif [ "$is_encrypt" == "decryptws" ]; then
echo " \"relay+ws://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "decryptwss" ]; then
if [ -d "$HOME/gost_cert" ]; then
echo " \"relay+wss://:$s_port/$d_ip:$d_port?cert=/root/gost_cert/cert.pem&key=/root/gost_cert/key.pem\"" >>$gost_conf_path
else
echo " \"relay+wss://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
fi
elif [ "$is_encrypt" == "ss" ]; then
echo " \"ss://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "socks" ]; then
echo " \"socks5://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "http" ]; then
echo " \"http://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
else
echo "config error"
fi
elif [ $i -gt 1 ]; then
if [ "$is_encrypt" == "nonencrypt" ]; then
echo " \"tcp://:$s_port/$d_ip:$d_port\",
\"udp://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerno" ]; then
echo " \"tcp://:$s_port?ip=/root/$d_ip.txt&strategy=$d_port\",
\"udp://:$s_port?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnno" ]; then
echo " \"tcp://:$s_port/$d_ip?host=$d_port\",
\"udp://:$s_port/$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encrypttls" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+tls://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encryptws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "encryptwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peertls" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+tls://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "peerwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://:?ip=/root/$d_ip.txt&strategy=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnws" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+ws://$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "cdnwss" ]; then
echo " \"tcp://:$s_port\",
\"udp://:$s_port\"
],
\"ChainNodes\": [
\"relay+wss://$d_ip?host=$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "decrypttls" ]; then
if [ -d "$HOME/gost_cert" ]; then
echo " \"relay+tls://:$s_port/$d_ip:$d_port?cert=/root/gost_cert/cert.pem&key=/root/gost_cert/key.pem\"" >>$gost_conf_path
else
echo " \"relay+tls://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
fi
elif [ "$is_encrypt" == "decryptws" ]; then
echo " \"relay+ws://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "decryptwss" ]; then
if [ -d "$HOME/gost_cert" ]; then
echo " \"relay+wss://:$s_port/$d_ip:$d_port?cert=/root/gost_cert/cert.pem&key=/root/gost_cert/key.pem\"" >>$gost_conf_path
else
echo " \"relay+wss://:$s_port/$d_ip:$d_port\"" >>$gost_conf_path
fi
elif [ "$is_encrypt" == "ss" ]; then
echo " \"ss://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "socks" ]; then
echo " \"socks5://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
elif [ "$is_encrypt" == "http" ]; then
echo " \"http://$d_ip:$s_port@:$d_port\"" >>$gost_conf_path
else
echo "config error"
fi
else
echo "config error"
exit
fi
}
function writeconf() {
count_line=$(awk 'END{print NR}' $raw_conf_path)
for ((i = 1; i <= $count_line; i++)); do
if [ $i -eq 1 ]; then
trans_conf=$(sed -n "${i}p" $raw_conf_path)
eachconf_retrieve
method
elif [ $i -gt 1 ]; then
if [ $i -eq 2 ]; then
echo " ],
\"Routes\": [" >>$gost_conf_path
trans_conf=$(sed -n "${i}p" $raw_conf_path)
eachconf_retrieve
multiconfstart
method
multiconflast
else
trans_conf=$(sed -n "${i}p" $raw_conf_path)
eachconf_retrieve
multiconfstart
method
multiconflast
fi
fi
done
}
function show_all_conf() {
echo -e " GOST 配置 "
echo -e "--------------------------------------------------------"
echo -e "序号|方法\t |本地端口\t|目的地地址:目的地端口"
echo -e "--------------------------------------------------------"
count_line=$(awk 'END{print NR}' $raw_conf_path)
for ((i = 1; i <= $count_line; i++)); do
trans_conf=$(sed -n "${i}p" $raw_conf_path)
eachconf_retrieve
if [ "$is_encrypt" == "nonencrypt" ]; then
str="不加密中转"
elif [ "$is_encrypt" == "encrypttls" ]; then
str=" tls隧道 "
elif [ "$is_encrypt" == "encryptws" ]; then
str=" ws隧道 "
elif [ "$is_encrypt" == "encryptwss" ]; then
str=" wss隧道 "
elif [ "$is_encrypt" == "peerno" ]; then
str=" 不加密均衡负载 "
elif [ "$is_encrypt" == "peertls" ]; then
str=" tls隧道均衡负载 "
elif [ "$is_encrypt" == "peerws" ]; then
str=" ws隧道均衡负载 "
elif [ "$is_encrypt" == "peerwss" ]; then
str=" wss隧道均衡负载 "
elif [ "$is_encrypt" == "decrypttls" ]; then
str=" tls解密 "
elif [ "$is_encrypt" == "decryptws" ]; then
str=" ws解密 "
elif [ "$is_encrypt" == "decryptwss" ]; then
str=" wss解密 "
elif [ "$is_encrypt" == "ss" ]; then
str=" ss "
elif [ "$is_encrypt" == "socks" ]; then
str=" socks5 "
elif [ "$is_encrypt" == "http" ]; then
str=" http "
elif [ "$is_encrypt" == "cdnno" ]; then
str="不加密转发CDN"
elif [ "$is_encrypt" == "cdnws" ]; then
str="ws隧道转发CDN"
elif [ "$is_encrypt" == "cdnwss" ]; then
str="wss隧道转发CDN"
else
str=""
fi
echo -e " $i |$str |$s_port\t|$d_ip:$d_port"
echo -e "--------------------------------------------------------"
done
}
cron_restart() {
echo -e "------------------------------------------------------"
echo -e "gost定时重启任务: "
echo -e "-----------------------------------"
echo -e "[1] 配置gost定时重启任务"
echo -e "[2] 删除gost定时重启任务"
echo -e "-----------------------------------"
read -p "请选择: " numcron
if [ "$numcron" == "1" ]; then
echo -e "-----------------------------------------------------"
echo -e "gost定时重启任务类型: "
echo -e "-----------------------------------"
echo -e "[1] 每?小时重启"
echo -e "[2] 每日?点重启"
echo -e "-----------------------------------"
read -p "请选择: " numcrontype
if [ "$numcrontype" == "1" ]; then
echo -e "-----------------------------------"
read -p "每?小时重启: " cronhr
echo "0 0 */$cronhr * * ? * systemctl restart gost" >>/etc/crontab
echo -e "定时重启设置成功!"
elif [ "$numcrontype" == "2" ]; then
echo -e "-----------------------------------"
read -p "每日?点重启: " cronhr
echo "0 0 $cronhr * * ? systemctl restart gost" >>/etc/crontab
echo -e "定时重启设置成功!"
else
echo "type error, please try again"
exit
fi
elif [ "$numcron" == "2" ]; then
sed -i "/gost/d" /etc/crontab
echo -e "定时重启任务删除完成!"
else
echo "type error, please try again"
exit
fi
}
echo && echo -e " gost 一键安装配置脚本" "
-------------------------------------------------------------
特性: (1)本脚本采用systemd及gost配置文件对gost进行管理
(2)机器重启后转发也不会失效
功能: (1)tcp+udp不加密转发, (2)中转机加密转发, (3)落地机解密对接转发
${Green_font_prefix}1.${Font_color_suffix} 安装 gost
${Green_font_prefix}2.${Font_color_suffix} 卸载 gost
————————————
${Green_font_prefix}3.${Font_color_suffix} 启动 gost
${Green_font_prefix}4.${Font_color_suffix} 停止 gost
${Green_font_prefix}5.${Font_color_suffix} 重启 gost
————————————
${Green_font_prefix}6.${Font_color_suffix} 新增gost转发配置
${Green_font_prefix}7.${Font_color_suffix} 查看现有gost配置
${Green_font_prefix}8.${Font_color_suffix} 删除一则gost配置
————————————
${Green_font_prefix}9.${Font_color_suffix} gost定时重启配置
${Green_font_prefix}10.${Font_color_suffix} 自定义TLS证书配置
${Green_font_prefix}11.${Font_color_suffix} 查看gost运行状态
————————————" && echo
read -e -p " 请输入数字 [1-11]:" num
case "$num" in
1)
Install_ct
;;
2)
Uninstall_ct
;;
3)
Start_ct
;;
4)
Stop_ct
;;
5)
Restart_ct
;;
6)
rawconf
rm -rf /etc/gost/config.json
confstart
writeconf
conflast
systemctl restart gost
echo -e "配置已生效,当前配置如下"
echo -e "--------------------------------------------------------"
show_all_conf
;;
7)
show_all_conf
;;
8)
show_all_conf
read -p "请输入你要删除的配置编号:" numdelete
if echo $numdelete | grep -q '[0-9]'; then
sed -i "${numdelete}d" $raw_conf_path
rm -rf /etc/gost/config.json
confstart
writeconf
conflast
systemctl restart gost
echo -e "配置已删除,服务已重启"
else
echo "请输入正确数字"
fi
;;
9)
cron_restart
;;
10)
cert
;;
11)
status
;;
*)
echo "请输入正确数字 [1-9]"
;;
esac

10
nezha/README.md Normal file
View File

@@ -0,0 +1,10 @@
### nezha固定版本`v0.20.5`的安装`agent`脚本
将面板中复制所得的指令中的sh的raw文件url替换成
```
https://cdn.jsdelivr.net/gh/sky22333/shell@main/nezha/install.sh
```
然后接着去vps安装即可
> 旧版备份`https://cdn.jsdelivr.net/gh/sky22333/shell@main/nezha/agent.sh`

411
nezha/agent.sh Normal file
View File

@@ -0,0 +1,411 @@
#!/bin/sh
# Generated by nezhahq/scriptgen. DO NOT EDIT
NZ_BASE_PATH="/opt/nezha"
NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
GITHUB_URL="https://github.com"
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
plain='\033[0m'
export PATH="$PATH:/usr/local/bin"
os_arch=""
[ -e /etc/os-release ] && grep -i "PRETTY_NAME" /etc/os-release | grep -qi "alpine" && os_alpine='1'
sudo() {
myEUID=$(id -ru)
if [ "$myEUID" -ne 0 ]; then
if command -v sudo > /dev/null 2>&1; then
command sudo "$@"
else
err "错误: 您的系统未安装 sudo因此无法进行该项操作。"
exit 1
fi
else
"$@"
fi
}
check_systemd() {
if [ "$os_alpine" != 1 ] && ! command -v systemctl >/dev/null 2>&1; then
echo "不支持此系统:未找到 systemctl 命令"
exit 1
fi
}
err() {
printf "${red}%s${plain}\n" "$*" >&2
}
success() {
printf "${green}%s${plain}\n" "$*"
}
info() {
printf "${yellow}%s${plain}\n" "$*"
}
geo_check() {
api_list="https://blog.cloudflare.com/cdn-cgi/trace https://dash.cloudflare.com/cdn-cgi/trace https://developers.cloudflare.com/cdn-cgi/trace"
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
set -- "$api_list"
for url in $api_list; do
text="$(curl -A "$ua" -m 10 -s "$url")"
endpoint="$(echo "$text" | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
if echo "$text" | grep -qw 'CN'; then
isCN=true
break
elif echo "$url" | grep -q "$endpoint"; then
break
fi
done
}
pre_check() {
umask 077
## os_arch
if uname -m | grep -q 'x86_64'; then
os_arch="amd64"
elif uname -m | grep -q 'i386\|i686'; then
os_arch="386"
elif uname -m | grep -q 'aarch64\|armv8b\|armv8l'; then
os_arch="arm64"
elif uname -m | grep -q 'arm'; then
os_arch="arm"
elif uname -m | grep -q 's390x'; then
os_arch="s390x"
elif uname -m | grep -q 'riscv64'; then
os_arch="riscv64"
fi
## China_IP
if [ -z "$CN" ]; then
geo_check
if [ -n "$isCN" ]; then
echo "根据geoip api提供的信息当前IP可能在中国"
printf "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3)"
read -r input
case $input in
[yY][eE][sS] | [yY])
echo "使用中国镜像"
CN=true
;;
[nN][oO] | [nN])
echo "不使用中国镜像"
;;
[3])
echo "使用自定义镜像"
printf "请输入自定义镜像 (例如:dn-dao-github-mirror.daocloud.io),留空为不使用:"
read -r input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*)
echo "不使用中国镜像"
;;
esac
fi
fi
}
select_version() {
if [ -z "$IS_DOCKER_NEZHA" ]; then
info "请自行选择您的Agent安装方式输入哪个都是一样的"
info "1. 安装Agent"
info "2. 安装Agent"
while true; do
printf "请输入选择 [1-2]"
read -r option
case "${option}" in
1)
IS_DOCKER_NEZHA=1
break
;;
2)
IS_DOCKER_NEZHA=0
break
;;
*)
err "请输入正确的选择 [1-2]"
;;
esac
done
fi
}
before_show_menu() {
echo && info "* 按回车返回主菜单 *" && read temp
show_menu
}
install_base() {
(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) ||
(install_soft curl wget unzip)
}
install_arch() {
info "提示Arch安装libselinux需添加nezha-agent用户安装完会自动删除建议手动检查一次"
read -r -p "是否安装libselinux? [Y/n] " input
case $input in
[yY][eE][sS] | [yY])
useradd -m nezha-agent
sed -i "$ a\nezha-agent ALL=(ALL ) NOPASSWD:ALL" /etc/sudoers
sudo -iu nezha-agent bash -c 'gpg --keyserver keys.gnupg.net --recv-keys 4695881C254508D1;
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel -r nezha-agent
info "提示: 已删除用户nezha-agent请务必手动核查一遍"
;;
[nN][oO] | [nN])
echo "不安装libselinux"
;;
*)
echo "不安装libselinux"
exit 0
;;
esac
}
install_soft() {
(command -v yum >/dev/null 2>&1 && sudo yum makecache && sudo yum install "$@" selinux-policy -y) ||
(command -v apt >/dev/null 2>&1 && sudo apt update && sudo apt install "$@" selinux-utils -y) ||
(command -v pacman >/dev/null 2>&1 && sudo pacman -Syu "$@" base-devel --noconfirm && install_arch) ||
(command -v apt-get >/dev/null 2>&1 && sudo apt-get update && sudo apt-get install "$@" selinux-utils -y) ||
(command -v apk >/dev/null 2>&1 && sudo apk update && sudo apk add "$@" -f)
}
selinux() {
#Check SELinux
if command -v getenforce >/dev/null 2>&1; then
if getenforce | grep '[Ee]nfor'; then
echo "SELinux是开启状态正在关闭"
sudo setenforce 0 >/dev/null 2>&1
find_key="SELINUX="
sudo sed -ri "/^$find_key/c${find_key}disabled" /etc/selinux/config
fi
fi
}
install_agent() {
install_base
selinux
echo "> 安装监控Agent"
_version=v0.20.5
# Nezha Monitoring Folder
sudo mkdir -p $NZ_AGENT_PATH
echo "正在下载监控端"
if [ -z "$CN" ]; then
NZ_AGENT_URL="${GITHUB_URL}/sky22333/shell/releases/download/${_version}/nezha-agent_linux_${os_arch}.zip"
else
NZ_AGENT_URL="${GITHUB_URL}/sky22333/shell/releases/download/${_version}/nezha-agent_linux_${os_arch}.zip"
fi
_cmd="wget -t 2 -T 60 -O nezha-agent_linux_${os_arch}.zip $NZ_AGENT_URL >/dev/null 2>&1"
if ! eval "$_cmd"; then
err "Release 下载失败,请检查本机能否连接 ${GITHUB_URL}"
return 1
fi
sudo unzip -qo nezha-agent_linux_${os_arch}.zip &&
sudo mv nezha-agent $NZ_AGENT_PATH &&
sudo rm -rf nezha-agent_linux_${os_arch}.zip README.md
if [ $# -ge 3 ]; then
modify_agent_config "$@"
else
modify_agent_config 0
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
modify_agent_config() {
echo "> 修改 Agent 配置"
if [ $# -lt 3 ]; then
echo "请先在管理面板上添加Agent记录下密钥"
printf "请输入一个解析到面板所在IP的域名不可套CDN: "
read -r nz_grpc_host
printf "请输入面板RPC端口 (默认值 5555): "
read -r nz_grpc_port
printf "请输入Agent 密钥: "
read -r nz_client_secret
printf "是否启用针对 gRPC 端口的 SSL/TLS加密 (--tls),需要请按 [y],默认是不需要,不理解用户可回车跳过: "
read -r nz_grpc_proxy
echo "${nz_grpc_proxy}" | grep -qiw 'Y' && args='--tls'
if [ -z "$nz_grpc_host" ] || [ -z "$nz_client_secret" ]; then
err "所有选项都不能为空"
before_show_menu
return 1
fi
if [ -z "$nz_grpc_port" ]; then
nz_grpc_port=5555
fi
else
nz_grpc_host=$1
nz_grpc_port=$2
nz_client_secret=$3
shift 3
if [ $# -gt 0 ]; then
args="$*"
fi
fi
_cmd="sudo ${NZ_AGENT_PATH}/nezha-agent service install -s $nz_grpc_host:$nz_grpc_port -p $nz_client_secret $args >/dev/null 2>&1"
if ! eval "$_cmd"; then
sudo "${NZ_AGENT_PATH}"/nezha-agent service uninstall >/dev/null 2>&1
sudo "${NZ_AGENT_PATH}"/nezha-agent service install -s "$nz_grpc_host:$nz_grpc_port" -p "$nz_client_secret" "$args" >/dev/null 2>&1
fi
success "Agent 配置 修改成功,请稍等 Agent 重启生效"
#if [[ $# == 0 ]]; then
# before_show_menu
#fi
}
show_agent_log() {
echo "> 获取 Agent 日志"
if [ "$os_alpine" != 1 ]; then
sudo journalctl -xf -u nezha-agent.service
else
sudo tail -n 10 /var/log/nezha-agent.err
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
uninstall_agent() {
echo "> 卸载 Agent"
sudo ${NZ_AGENT_PATH}/nezha-agent service uninstall
sudo rm -rf $NZ_AGENT_PATH
clean_all
if [ $# = 0 ]; then
before_show_menu
fi
}
restart_agent() {
echo "> 重启 Agent"
sudo ${NZ_AGENT_PATH}/nezha-agent service restart
if [ $# = 0 ]; then
before_show_menu
fi
}
clean_all() {
if [ -z "$(ls -A ${NZ_BASE_PATH})" ]; then
sudo rm -rf ${NZ_BASE_PATH}
fi
}
show_usage() {
echo "哪吒监控v0.20.5管理脚本使用方法: "
echo "--------------------------------------------------------"
echo "./nezha.sh - 显示管理菜单"
echo "--------------------------------------------------------"
echo "./nezha.sh install_agent - 安装监控Agent"
echo "./nezha.sh modify_agent_config - 修改Agent配置"
echo "./nezha.sh show_agent_log - 查看Agent日志"
echo "./nezha.sh uninstall_agent - 卸载Agent"
echo "./nezha.sh restart_agent - 重启Agent"
echo "--------------------------------------------------------"
}
show_menu() {
printf "
${green}哪吒v0.20.5监控Agent管理脚本${plain}
————————————————-
${green}8.${plain} 安装监控Agent
${green}9.${plain} 修改Agent配置
${green}10.${plain} 查看Agent日志
${green}11.${plain} 卸载Agent
${green}12.${plain} 重启Agent
————————————————-
${green}0.${plain} 退出脚本
"
echo && printf "请输入选择 [0-13]: " && read -r num
case "${num}" in
0)
exit 0
;;
8)
install_agent
;;
9)
modify_agent_config
;;
10)
show_agent_log
;;
11)
uninstall_agent
;;
12)
restart_agent
;;
*)
err "请输入正确的数字 [0-13]"
;;
esac
}
pre_check
if [ $# -gt 0 ]; then
case $1 in
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
*) show_usage ;;
esac
else
select_version
show_menu
fi

901
nezha/install.sh Normal file
View File

@@ -0,0 +1,901 @@
#!/bin/sh
# Generated by nezhahq/scriptgen. DO NOT EDIT
NZ_BASE_PATH="/opt/nezha"
NZ_DASHBOARD_PATH="${NZ_BASE_PATH}/dashboard"
NZ_AGENT_PATH="${NZ_BASE_PATH}/agent"
NZ_DASHBOARD_SERVICE="/etc/systemd/system/nezha-dashboard.service"
NZ_DASHBOARD_SERVICERC="/etc/init.d/nezha-dashboard"
red='\033[0;31m'
green='\033[0;32m'
yellow='\033[0;33m'
plain='\033[0m'
export PATH="$PATH:/usr/local/bin"
os_arch=""
[ -e /etc/os-release ] && grep -i "PRETTY_NAME" /etc/os-release | grep -qi "alpine" && os_alpine='1'
sudo() {
myEUID=$(id -ru)
if [ "$myEUID" -ne 0 ]; then
if command -v sudo > /dev/null 2>&1; then
command sudo "$@"
else
err "错误: 您的系统未安装 sudo因此无法进行该项操作。"
exit 1
fi
else
"$@"
fi
}
check_systemd() {
if [ "$os_alpine" != 1 ] && ! command -v systemctl >/dev/null 2>&1; then
echo "不支持此系统:未找到 systemctl 命令"
exit 1
fi
}
err() {
printf "${red}%s${plain}\n" "$*" >&2
}
success() {
printf "${green}%s${plain}\n" "$*"
}
info() {
printf "${yellow}%s${plain}\n" "$*"
}
geo_check() {
api_list="https://blog.cloudflare.com/cdn-cgi/trace https://dash.cloudflare.com/cdn-cgi/trace https://developers.cloudflare.com/cdn-cgi/trace"
ua="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
set -- "$api_list"
for url in $api_list; do
text="$(curl -A "$ua" -m 10 -s "$url")"
endpoint="$(echo "$text" | sed -n 's/.*h=\([^ ]*\).*/\1/p')"
if echo "$text" | grep -qw 'CN'; then
isCN=true
break
elif echo "$url" | grep -q "$endpoint"; then
break
fi
done
}
pre_check() {
umask 077
## os_arch
if uname -m | grep -q 'x86_64'; then
os_arch="amd64"
elif uname -m | grep -q 'i386\|i686'; then
os_arch="386"
elif uname -m | grep -q 'aarch64\|armv8b\|armv8l'; then
os_arch="arm64"
elif uname -m | grep -q 'arm'; then
os_arch="arm"
elif uname -m | grep -q 's390x'; then
os_arch="s390x"
elif uname -m | grep -q 'riscv64'; then
os_arch="riscv64"
fi
## China_IP
if [ -z "$CN" ]; then
geo_check
if [ -n "$isCN" ]; then
echo "根据geoip api提供的信息当前IP可能在中国"
printf "是否选用中国镜像完成安装? [Y/n] (自定义镜像输入 3)"
read -r input
case $input in
[yY][eE][sS] | [yY])
echo "使用中国镜像"
CN=true
;;
[nN][oO] | [nN])
echo "不使用中国镜像"
;;
[3])
echo "使用自定义镜像"
printf "请输入自定义镜像 (例如:dn-dao-github-mirror.daocloud.io),留空为不使用:"
read -r input
case $input in
*)
CUSTOM_MIRROR=$input
;;
esac
;;
*)
echo "不使用中国镜像"
;;
esac
fi
fi
if [ -n "$CUSTOM_MIRROR" ]; then
GITHUB_RAW_URL="gitee.com/naibahq/scripts/raw/main"
GITHUB_URL=$CUSTOM_MIRROR
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
else
if [ -z "$CN" ]; then
GITHUB_RAW_URL="raw.githubusercontent.com/nezhahq/scripts/main"
GITHUB_URL="github.com"
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" "
Docker_IMG="ghcr.io\/naiba\/nezha-dashboard"
else
GITHUB_RAW_URL="gitee.com/naibahq/scripts/raw/main"
GITHUB_URL="gitee.com"
Get_Docker_URL="get.docker.com"
Get_Docker_Argu=" -s docker --mirror Aliyun"
Docker_IMG="registry.cn-shanghai.aliyuncs.com\/naibahq\/nezha-dashboard"
fi
fi
}
installation_check() {
if docker compose version >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker compose"
if sudo $DOCKER_COMPOSE_COMMAND ls | grep -qw "$NZ_DASHBOARD_PATH/docker-compose.yaml" >/dev/null 2>&1; then
NEZHA_IMAGES=$(sudo docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
FRESH_INSTALL=0
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi
fi
elif command -v docker-compose >/dev/null 2>&1; then
DOCKER_COMPOSE_COMMAND="docker-compose"
if sudo $DOCKER_COMPOSE_COMMAND -f "$NZ_DASHBOARD_PATH/docker-compose.yaml" config >/dev/null 2>&1; then
NEZHA_IMAGES=$(sudo docker images --format "{{.Repository}}:{{.Tag}}" | grep -w "nezha-dashboard")
if [ -n "$NEZHA_IMAGES" ]; then
echo "存在带有 nezha-dashboard 仓库的 Docker 镜像:"
echo "$NEZHA_IMAGES"
IS_DOCKER_NEZHA=1
FRESH_INSTALL=0
return
else
echo "未找到带有 nezha-dashboard 仓库的 Docker 镜像。"
fi
fi
fi
if [ -f "$NZ_DASHBOARD_PATH/app" ]; then
IS_DOCKER_NEZHA=0
FRESH_INSTALL=0
fi
}
select_version() {
if [ -z "$IS_DOCKER_NEZHA" ]; then
info "请安装方式(此脚本是安装旧版,所以输入哪个都是一样的):"
info "1. 独立安装"
info "2. 独立安装"
while true; do
printf "请输入选择 [1-2]"
read -r option
case "${option}" in
1)
IS_DOCKER_NEZHA=0
break
;;
2)
IS_DOCKER_NEZHA=0
break
;;
*)
err "请输入正确的选择 [1-2]"
;;
esac
done
fi
}
update_script() {
echo "> 更新脚本"
curl -sL https://${GITHUB_RAW_URL}/install.sh -o /tmp/nezha.sh
mv -f /tmp/nezha.sh ./nezha.sh && chmod a+x ./nezha.sh
echo "3s后执行新脚本"
sleep 3s
clear
exec ./nezha.sh
exit 0
}
before_show_menu() {
echo && info "* 按回车返回主菜单 *" && read temp
show_menu
}
install_base() {
(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) ||
(install_soft curl wget unzip)
}
install_arch() {
info "提示Arch安装libselinux需添加nezha-agent用户安装完会自动删除建议手动检查一次"
read -r -p "是否安装libselinux? [Y/n] " input
case $input in
[yY][eE][sS] | [yY])
useradd -m nezha-agent
sed -i "$ a\nezha-agent ALL=(ALL ) NOPASSWD:ALL" /etc/sudoers
sudo -iu nezha-agent bash -c 'gpg --keyserver keys.gnupg.net --recv-keys 4695881C254508D1;
cd /tmp; git clone https://aur.archlinux.org/libsepol.git; cd libsepol; makepkg -si --noconfirm --asdeps; cd ..;
git clone https://aur.archlinux.org/libselinux.git; cd libselinux; makepkg -si --noconfirm; cd ..;
rm -rf libsepol libselinux'
sed -i '/nezha-agent/d' /etc/sudoers && sleep 30s && killall -u nezha-agent && userdel -r nezha-agent
info "提示: 已删除用户nezha-agent请务必手动核查一遍"
;;
[nN][oO] | [nN])
echo "不安装libselinux"
;;
*)
echo "不安装libselinux"
exit 0
;;
esac
}
install_soft() {
(command -v yum >/dev/null 2>&1 && sudo yum makecache && sudo yum install "$@" selinux-policy -y) ||
(command -v apt >/dev/null 2>&1 && sudo apt update && sudo apt install "$@" selinux-utils -y) ||
(command -v pacman >/dev/null 2>&1 && sudo pacman -Syu "$@" base-devel --noconfirm && install_arch) ||
(command -v apt-get >/dev/null 2>&1 && sudo apt-get update && sudo apt-get install "$@" selinux-utils -y) ||
(command -v apk >/dev/null 2>&1 && sudo apk update && sudo apk add "$@" -f)
}
install_dashboard() {
check_systemd
install_base
echo "> 安装面板"
# Nezha Monitoring Folder
if [ ! "$FRESH_INSTALL" = 0 ]; then
sudo mkdir -p $NZ_DASHBOARD_PATH
else
echo "您可能已经安装过面板端,重复安装会覆盖数据,请注意备份。"
printf "是否退出安装? [Y/n] "
read -r input
case $input in
[yY][eE][sS] | [yY])
echo "退出安装"
exit 0
;;
[nN][oO] | [nN])
echo "继续安装"
;;
*)
echo "退出安装"
exit 0
;;
esac
fi
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
install_dashboard_docker
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
install_dashboard_standalone
fi
modify_dashboard_config 0
if [ $# = 0 ]; then
before_show_menu
fi
}
install_dashboard_docker() {
if [ ! "$FRESH_INSTALL" = 0 ]; then
if ! command -v docker >/dev/null 2>&1; then
echo "正在安装 Docker"
if [ "$os_alpine" != 1 ]; then
if ! curl -sL https://${Get_Docker_URL} | sudo bash -s "${Get_Docker_Argu}"; then
err "脚本获取失败,请检查本机能否链接 ${Get_Docker_URL}"
return 0
fi
sudo systemctl enable docker.service
sudo systemctl start docker.service
else
sudo apk add docker docker-compose
sudo rc-update add docker
sudo rc-service docker start
fi
success "Docker 安装成功"
installation_check
fi
fi
}
install_dashboard_standalone() {
if [ ! -d "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" ] || [ ! -d "${NZ_DASHBOARD_PATH}/resource/static/custom" ]; then
sudo mkdir -p "${NZ_DASHBOARD_PATH}/resource/template/theme-custom" "${NZ_DASHBOARD_PATH}/resource/static/custom" >/dev/null 2>&1
fi
}
selinux() {
#Check SELinux
if command -v getenforce >/dev/null 2>&1; then
if getenforce | grep '[Ee]nfor'; then
echo "SELinux是开启状态正在关闭"
sudo setenforce 0 >/dev/null 2>&1
find_key="SELINUX="
sudo sed -ri "/^$find_key/c${find_key}disabled" /etc/selinux/config
fi
fi
}
install_agent() {
install_base
selinux
echo "> 安装监控Agent"
_version=v0.20.5
# Nezha Monitoring Folder
sudo mkdir -p $NZ_AGENT_PATH
echo "正在下载监控端"
if [ -z "$CN" ]; then
NZ_AGENT_URL="https://${GITHUB_URL}/nezhahq/agent/releases/download/${_version}/nezha-agent_linux_${os_arch}.zip"
else
NZ_AGENT_URL="https://${GITHUB_URL}/naibahq/agent/releases/download/${_version}/nezha-agent_linux_${os_arch}.zip"
fi
_cmd="wget -t 2 -T 60 -O nezha-agent_linux_${os_arch}.zip $NZ_AGENT_URL >/dev/null 2>&1"
if ! eval "$_cmd"; then
err "Release 下载失败,请检查本机能否连接 ${GITHUB_URL}"
return 1
fi
sudo unzip -qo nezha-agent_linux_${os_arch}.zip &&
sudo mv nezha-agent $NZ_AGENT_PATH &&
sudo rm -rf nezha-agent_linux_${os_arch}.zip README.md
if [ $# -ge 3 ]; then
modify_agent_config "$@"
else
modify_agent_config 0
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
modify_agent_config() {
echo "> 修改 Agent 配置"
if [ $# -lt 3 ]; then
echo "请先在管理面板上添加Agent记录下密钥"
printf "请输入一个解析到面板所在IP的域名不可套CDN: "
read -r nz_grpc_host
printf "请输入面板RPC端口 (默认值 5555): "
read -r nz_grpc_port
printf "请输入Agent 密钥: "
read -r nz_client_secret
printf "是否启用针对 gRPC 端口的 SSL/TLS加密 (--tls),需要请按 [y],默认是不需要,不理解用户可回车跳过: "
read -r nz_grpc_proxy
echo "${nz_grpc_proxy}" | grep -qiw 'Y' && args='--tls'
if [ -z "$nz_grpc_host" ] || [ -z "$nz_client_secret" ]; then
err "所有选项都不能为空"
before_show_menu
return 1
fi
if [ -z "$nz_grpc_port" ]; then
nz_grpc_port=5555
fi
else
nz_grpc_host=$1
nz_grpc_port=$2
nz_client_secret=$3
shift 3
if [ $# -gt 0 ]; then
args="$*"
fi
fi
_cmd="sudo ${NZ_AGENT_PATH}/nezha-agent service install -s $nz_grpc_host:$nz_grpc_port -p $nz_client_secret $args >/dev/null 2>&1"
if ! eval "$_cmd"; then
sudo "${NZ_AGENT_PATH}"/nezha-agent service uninstall >/dev/null 2>&1
sudo "${NZ_AGENT_PATH}"/nezha-agent service install -s "$nz_grpc_host:$nz_grpc_port" -p "$nz_client_secret" "$args" >/dev/null 2>&1
fi
success "Agent 配置 修改成功,请稍等 Agent 重启生效"
#if [[ $# == 0 ]]; then
# before_show_menu
#fi
}
modify_dashboard_config() {
echo "> 修改 Dashboard 配置"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
if [ -n "$DOCKER_COMPOSE_COMMAND" ]; then
echo "正在下载 Docker 脚本"
_cmd="wget -t 2 -T 60 -O /tmp/nezha-docker-compose.yaml https://${GITHUB_RAW_URL}/extras/docker-compose.yaml >/dev/null 2>&1"
if ! eval "$_cmd"; then
err "脚本获取失败,请检查本机能否链接 ${GITHUB_RAW_URL}"
return 0
fi
else
err "请手动安装 docker-compose。 https://docs.docker.com/compose/install/linux/"
before_show_menu
fi
fi
_cmd="wget -t 2 -T 60 -O /tmp/nezha-config.yaml https://${GITHUB_RAW_URL}/extras/config.yaml >/dev/null 2>&1"
if ! eval "$_cmd"; then
err "脚本获取失败,请检查本机能否链接 ${GITHUB_RAW_URL}"
return 0
fi
echo "关于 GitHub Oauth2 应用:在 https://github.com/settings/developers 创建无需审核Callback 填 http(s)://域名或IP/oauth2/callback"
echo "关于 Gitee Oauth2 应用:在 https://gitee.com/oauth/applications 创建无需审核Callback 填 http(s)://域名或IP/oauth2/callback"
printf "请输入 OAuth2 提供商(github/gitlab/jihulab/gitee默认 github): "
read -r nz_oauth2_type
printf "请输入 Oauth2 应用的 Client ID: "
read -r nz_github_oauth_client_id
printf "请输入 Oauth2 应用的 Client Secret: "
read -r nz_github_oauth_client_secret
printf "请输入 GitHub/Gitee 登录名作为管理员,多个以逗号隔开: "
read -r nz_admin_logins
printf "请输入站点标题: "
read -r nz_site_title
printf "请输入站点访问端口: (默认 8008)"
read -r nz_site_port
printf "请输入用于 Agent 接入的 RPC 端口: (默认 5555)"
read -r nz_grpc_port
if [ -z "$nz_admin_logins" ] || [ -z "$nz_github_oauth_client_id" ] || [ -z "$nz_github_oauth_client_secret" ] || [ -z "$nz_site_title" ]; then
err "所有选项都不能为空"
before_show_menu
return 1
fi
if [ -z "$nz_site_port" ]; then
nz_site_port=8008
fi
if [ -z "$nz_grpc_port" ]; then
nz_grpc_port=5555
fi
if [ -z "$nz_oauth2_type" ]; then
nz_oauth2_type=github
fi
sed -i "s/nz_oauth2_type/${nz_oauth2_type}/" /tmp/nezha-config.yaml
sed -i "s/nz_admin_logins/${nz_admin_logins}/" /tmp/nezha-config.yaml
sed -i "s/nz_grpc_port/${nz_grpc_port}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_id/${nz_github_oauth_client_id}/" /tmp/nezha-config.yaml
sed -i "s/nz_github_oauth_client_secret/${nz_github_oauth_client_secret}/" /tmp/nezha-config.yaml
sed -i "s/nz_language/zh-CN/" /tmp/nezha-config.yaml
sed -i "s/nz_site_title/${nz_site_title}/" /tmp/nezha-config.yaml
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
sed -i "s/nz_site_port/${nz_site_port}/" /tmp/nezha-docker-compose.yaml
sed -i "s/nz_grpc_port/${nz_grpc_port}/g" /tmp/nezha-docker-compose.yaml
sed -i "s/nz_image_url/${Docker_IMG}/" /tmp/nezha-docker-compose.yaml
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
sed -i "s/80/${nz_site_port}/" /tmp/nezha-config.yaml
fi
sudo mkdir -p $NZ_DASHBOARD_PATH/data
sudo mv -f /tmp/nezha-config.yaml ${NZ_DASHBOARD_PATH}/data/config.yaml
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
sudo mv -f /tmp/nezha-docker-compose.yaml ${NZ_DASHBOARD_PATH}/docker-compose.yaml
fi
if [ "$IS_DOCKER_NEZHA" = 0 ]; then
echo "正在下载服务文件"
if [ "$os_alpine" != 1 ]; then
_download="sudo wget -t 2 -T 60 -O $NZ_DASHBOARD_SERVICE https://${GITHUB_RAW_URL}/services/nezha-dashboard.service >/dev/null 2>&1"
if ! eval "$_download"; then
err "文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}"
return 0
fi
else
_download="sudo wget -t 2 -T 60 -O $NZ_DASHBOARD_SERVICERC https://${GITHUB_RAW_URL}/services/nezha-dashboard >/dev/null 2>&1"
if ! eval "$_download"; then
err "文件下载失败,请检查本机能否连接 ${GITHUB_RAW_URL}"
return 0
fi
sudo chmod +x $NZ_DASHBOARD_SERVICERC
fi
fi
success "Dashboard 配置 修改成功,请稍等 Dashboard 重启生效"
restart_and_update
if [ $# = 0 ]; then
before_show_menu
fi
}
restart_and_update() {
echo "> 重启并更新面板"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
_cmd="restart_and_update_docker"
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
_cmd="restart_and_update_standalone"
fi
if eval "$_cmd"; then
success "哪吒监控 重启成功"
info "默认管理面板地址:域名:站点访问端口"
else
err "重启失败,可能是因为启动时间超过了两秒,请稍后查看日志信息"
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
restart_and_update_docker() {
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml pull
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml down
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml up -d
}
restart_and_update_standalone() {
_version=v0.20.13
if [ "$os_alpine" != 1 ]; then
sudo systemctl daemon-reload
sudo systemctl stop nezha-dashboard
else
sudo rc-service nezha-dashboard stop
fi
if [ -z "$CN" ]; then
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naiba/nezha/releases/download/${_version}/dashboard-linux-${os_arch}.zip"
else
NZ_DASHBOARD_URL="https://${GITHUB_URL}/naibahq/nezha/releases/download/${_version}/dashboard-linux-${os_arch}.zip"
fi
sudo wget -qO $NZ_DASHBOARD_PATH/app.zip "$NZ_DASHBOARD_URL" >/dev/null 2>&1 && sudo unzip -qq -o $NZ_DASHBOARD_PATH/app.zip -d $NZ_DASHBOARD_PATH && sudo mv $NZ_DASHBOARD_PATH/dashboard-linux-$os_arch $NZ_DASHBOARD_PATH/app && sudo rm $NZ_DASHBOARD_PATH/app.zip
sudo chmod +x $NZ_DASHBOARD_PATH/app
if [ "$os_alpine" != 1 ]; then
sudo systemctl enable nezha-dashboard
sudo systemctl restart nezha-dashboard
else
sudo rc-update add nezha-dashboard
sudo rc-service nezha-dashboard restart
fi
}
start_dashboard() {
echo "> 启动面板"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
_cmd="start_dashboard_docker"
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
_cmd="start_dashboard_standalone"
fi
if eval "$_cmd"; then
success "哪吒监控 启动成功"
else
err "启动失败,请稍后查看日志信息"
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
start_dashboard_docker() {
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml up -d
}
start_dashboard_standalone() {
if [ "$os_alpine" != 1 ]; then
sudo systemctl start nezha-dashboard
else
sudo rc-service nezha-dashboard start
fi
}
stop_dashboard() {
echo "> 停止面板"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
_cmd="stop_dashboard_docker"
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
_cmd="stop_dashboard_standalone"
fi
if eval "$_cmd"; then
success "哪吒监控 停止成功"
else
err "停止失败,请稍后查看日志信息"
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
stop_dashboard_docker() {
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml down
}
stop_dashboard_standalone() {
if [ "$os_alpine" != 1 ]; then
sudo systemctl stop nezha-dashboard
else
sudo rc-service nezha-dashboard stop
fi
}
show_dashboard_log() {
echo "> 获取 Dashboard 日志"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
show_dashboard_log_docker
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
show_dashboard_log_standalone
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
show_dashboard_log_docker() {
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml logs -f
}
show_dashboard_log_standalone() {
if [ "$os_alpine" != 1 ]; then
sudo journalctl -xf -u nezha-dashboard.service
else
sudo tail -n 10 /var/log/nezha-dashboard.err
fi
}
uninstall_dashboard() {
echo "> 卸载 Dashboard"
if [ "$IS_DOCKER_NEZHA" = 1 ]; then
uninstall_dashboard_docker
elif [ "$IS_DOCKER_NEZHA" = 0 ]; then
uninstall_dashboard_standalone
fi
clean_all
if [ $# = 0 ]; then
before_show_menu
fi
}
uninstall_dashboard_docker() {
sudo $DOCKER_COMPOSE_COMMAND -f ${NZ_DASHBOARD_PATH}/docker-compose.yaml down
sudo rm -rf $NZ_DASHBOARD_PATH
sudo docker rmi -f ghcr.io/naiba/nezha-dashboard >/dev/null 2>&1
sudo docker rmi -f registry.cn-shanghai.aliyuncs.com/naibahq/nezha-dashboard >/dev/null 2>&1
}
uninstall_dashboard_standalone() {
sudo rm -rf $NZ_DASHBOARD_PATH
if [ "$os_alpine" != 1 ]; then
sudo systemctl disable nezha-dashboard
sudo systemctl stop nezha-dashboard
else
sudo rc-update del nezha-dashboard
sudo rc-service nezha-dashboard stop
fi
if [ "$os_alpine" != 1 ]; then
sudo rm $NZ_DASHBOARD_SERVICE
else
sudo rm $NZ_DASHBOARD_SERVICERC
fi
}
show_agent_log() {
echo "> 获取 Agent 日志"
if [ "$os_alpine" != 1 ]; then
sudo journalctl -xf -u nezha-agent.service
else
sudo tail -n 10 /var/log/nezha-agent.err
fi
if [ $# = 0 ]; then
before_show_menu
fi
}
uninstall_agent() {
echo "> 卸载 Agent"
sudo ${NZ_AGENT_PATH}/nezha-agent service uninstall
sudo rm -rf $NZ_AGENT_PATH
clean_all
if [ $# = 0 ]; then
before_show_menu
fi
}
restart_agent() {
echo "> 重启 Agent"
sudo ${NZ_AGENT_PATH}/nezha-agent service restart
if [ $# = 0 ]; then
before_show_menu
fi
}
clean_all() {
if [ -z "$(ls -A ${NZ_BASE_PATH})" ]; then
sudo rm -rf ${NZ_BASE_PATH}
fi
}
show_usage() {
echo "哪吒监控 管理脚本使用方法: "
echo "--------------------------------------------------------"
echo "./nezha.sh - 显示管理菜单"
echo "./nezha.sh install_dashboard - 安装面板端"
echo "./nezha.sh modify_dashboard_config - 修改面板配置"
echo "./nezha.sh start_dashboard - 启动面板"
echo "./nezha.sh stop_dashboard - 停止面板"
echo "./nezha.sh restart_and_update - 重启并更新面板"
echo "./nezha.sh show_dashboard_log - 查看面板日志"
echo "./nezha.sh uninstall_dashboard - 卸载管理面板"
echo "--------------------------------------------------------"
echo "./nezha.sh install_agent - 安装监控Agent"
echo "./nezha.sh modify_agent_config - 修改Agent配置"
echo "./nezha.sh show_agent_log - 查看Agent日志"
echo "./nezha.sh uninstall_agent - 卸载Agent"
echo "./nezha.sh restart_agent - 重启Agent"
echo "./nezha.sh update_script - 更新脚本"
echo "--------------------------------------------------------"
}
show_menu() {
printf "
${green}哪吒监控管理脚本${plain}
--- https://github.com/naiba/nezha ---
${green}1.${plain} 安装面板端
${green}2.${plain} 修改面板配置
${green}3.${plain} 启动面板
${green}4.${plain} 停止面板
${green}5.${plain} 重启并更新面板
${green}6.${plain} 查看面板日志
${green}7.${plain} 卸载管理面板
————————————————-
${green}8.${plain} 安装监控Agent
${green}9.${plain} 修改Agent配置
${green}10.${plain} 查看Agent日志
${green}11.${plain} 卸载Agent
${green}12.${plain} 重启Agent
————————————————-
${green}0.${plain} 退出脚本
"
echo && printf "请输入选择 [0-13]: " && read -r num
case "${num}" in
0)
exit 0
;;
1)
install_dashboard
;;
2)
modify_dashboard_config
;;
3)
start_dashboard
;;
4)
stop_dashboard
;;
5)
restart_and_update
;;
6)
show_dashboard_log
;;
7)
uninstall_dashboard
;;
8)
install_agent
;;
9)
modify_agent_config
;;
10)
show_agent_log
;;
11)
uninstall_agent
;;
12)
restart_agent
;;
*)
err "请输入正确的数字 [0-13]"
;;
esac
}
pre_check
installation_check
if [ $# -gt 0 ]; then
case $1 in
"install_dashboard")
install_dashboard 0
;;
"modify_dashboard_config")
modify_dashboard_config 0
;;
"start_dashboard")
start_dashboard 0
;;
"stop_dashboard")
stop_dashboard 0
;;
"restart_and_update")
restart_and_update 0
;;
"show_dashboard_log")
show_dashboard_log 0
;;
"uninstall_dashboard")
uninstall_dashboard 0
;;
"install_agent")
shift
if [ $# -ge 3 ]; then
install_agent "$@"
else
install_agent 0
fi
;;
"modify_agent_config")
modify_agent_config 0
;;
"show_agent_log")
show_agent_log 0
;;
"uninstall_agent")
uninstall_agent 0
;;
"restart_agent")
restart_agent 0
;;
"update_script")
update_script 0
;;
*) show_usage ;;
esac
else
select_version
show_menu
fi

31
proxy/README.md Normal file
View File

@@ -0,0 +1,31 @@
#### 卸载xray
```
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ remove --purge
```
- 一键安装L2TP
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/proxy/l2tp.sh)
```
- 站群多IP源进源出节点脚本支持sk5和vless+tcp协议
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/proxy/zhanqun.sh)
```
- 快速批量搭建二级代理脚本vmess入站sk5出站
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/proxy/vmess-sk5.sh)
```
- 站群多IP源进源出节点脚本sk5协议
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/proxy/duosocks.sh)
```
- 站群多IP源进源出节点脚本vmess+ws协议
```
bash <(curl -sSL https://github.com/sky22333/shell/raw/main/proxy/duovmess.sh)
```

556
proxy/duosk5-ss2022.sh Normal file
View File

@@ -0,0 +1,556 @@
#!/bin/bash
# 批量搭建ss2022入站到sk5出站代理
# 读取sk5文件实现批量导入出站
# 作者sky22333
red='\e[31m'
yellow='\e[33m'
green='\e[32m'
none='\e[0m'
config_file="/usr/local/etc/xray/config.json"
default_config='
{
"inbounds": [
{
"port": 9999,
"protocol": "shadowsocks",
"settings": {
"method": "2022-blake3-aes-256-gcm",
"password": "75ENbpfSCyzUdZnLRjVGexaQxVPdCLw5T4RXbTGRQ/Q=",
"network": "tcp,udp"
},
"tag": "inbound0"
}
],
"outbounds": [
{
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.2",
"port": 2222,
"users": [
{
"user": "admin123",
"pass": "admin333"
}
]
}
]
},
"tag": "outbound0"
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["inbound0"],
"outboundTag": "outbound0"
}
]
}
}
'
check_and_install_curl() {
if ! type curl &>/dev/null; then
echo -e "${yellow}正在安装curl...${none}"
apt update && apt install -yq curl
fi
}
check_and_install_jq() {
if ! type jq &>/dev/null; then
echo -e "${yellow}正在安装jq...${none}"
apt update && apt install -yq jq
fi
}
check_and_install_openssl() {
if ! type openssl &>/dev/null; then
echo -e "${yellow}正在安装 openssl...${none}"
apt update && apt install -yq openssl
fi
}
check_and_install_xray() {
if ! type xray &>/dev/null; then
echo -e "${yellow}正在安装 xray...${none}"
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.13
fi
}
check_existing_inbound_config() {
if grep -q '"tag":' "$config_file"; then
return 0
else
return 1
fi
}
create_default_config() {
if ! check_existing_inbound_config; then
echo "$default_config" > "$config_file"
echo -e "${green}已创建默认配置文件。${none}"
else
echo -e "${yellow}入站配置已存在,跳过创建默认配置文件。${none}"
fi
}
get_local_ip() {
local ip=$(curl -s http://ipinfo.io/ip)
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$ip"
else
echo "无法自动获取公网IP地址请手动输入。"
read -p "请输入您的公网IP地址: " manual_ip
if [[ $manual_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$manual_ip"
else
echo "输入的IP地址格式不正确请重新运行脚本并输入有效的公网IP地址。"
exit 1
fi
fi
}
get_ss_filename() {
local timestamp=$(date +"%Y%m%d-%H点%M分%S秒")
echo "/home/${timestamp}-ss.txt"
}
generate_ss_link() {
local server=$1
local port=$2
local method=$3
local password=$4
local ps=$5
local password_urlencoded=$(echo -n "$password" | xxd -p | tr -d '\n' | sed 's/\(..\)/%\1/g')
local base64_part=$(echo -n "${method}:${password}" | base64 -w 0)
echo "ss://${base64_part}@${server}:${port}#${ps}"
}
save_multiple_ss_links() {
local local_ip=$1
shift
local ss_file=$(get_ss_filename)
> "$ss_file"
while [[ $# -gt 0 ]]; do
local port=$1
local password=$2
local method=$3
local index=$4
shift 4
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP"
fi
local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip")
echo "$ss_link" >> "$ss_file"
done
echo -e "${green}已将操作的所有节点保存至 $ss_file${none}"
}
save_multiple_ss_links_with_ps() {
local local_ip=$1
shift
local ss_file=$(get_ss_filename)
> "$ss_file"
while [[ $# -gt 0 ]]; do
local port=$1
local password=$2
local method=$3
local index=$4
local sk5_ip=$5
shift 5
local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip")
echo "$ss_link" >> "$ss_file"
done
echo -e "${green}已将操作的所有节点保存至 $ss_file${none}"
}
save_all_ss_links() {
local local_ip=$(get_local_ip)
local ss_file=$(get_ss_filename)
> "$ss_file"
local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file")
local length=$(jq '. | length' <<< "$config")
for ((i = 0; i < length; i++)); do
local port=$(jq -r ".[$i].port" <<< "$config")
local method=$(jq -r ".[$i].settings.method" <<< "$config")
local password=$(jq -r ".[$i].settings.password" <<< "$config")
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP"
fi
# 生成SS链接
local ss_link=$(generate_ss_link "$local_ip" "$port" "$method" "$password" "$sk5_ip")
# 写入文件
echo "$ss_link" >> "$ss_file"
done
echo -e "${green}已将全部Shadowsocks节点保存至 $ss_file${none}"
}
show_inbound_configs() {
local local_ip=$(get_local_ip)
local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file")
local outbounds=$(jq '.outbounds' "$config_file")
echo -e "${green}入站节点配置:${none}"
local length=$(jq '. | length' <<< "$config")
for ((i = 0; i < length; i++)); do
local port=$(jq -r ".[$i].port" <<< "$config")
local method=$(jq -r ".[$i].settings.method" <<< "$config")
local password=$(jq -r ".[$i].settings.password" <<< "$config")
local node_address="$local_ip"
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP"
fi
local ss_link=$(generate_ss_link "$node_address" "$port" "$method" "$password" "$sk5_ip")
echo -e "${yellow}节点: $(($i + 1))${none} - 端口: ${port}, Shadowsocks 链接: ${ss_link}"
# 构造出站配置的标签
local outbound_tag="outbound$port"
# 根据构造的标签查找对应的出站配置
local outbound_config=$(jq --arg tag "$outbound_tag" '.[] | select(.tag == $tag) | .settings.servers[] | {address, port, user: .users[0].user, pass: .users[0].pass}' <<< "$outbounds")
if [[ ! -z $outbound_config ]]; then
echo -e "${green}出站配置:${none} 地址: $(jq -r '.address' <<< "$outbound_config"), 端口: $(jq -r '.port' <<< "$outbound_config"), 用户名: $(jq -r '.user' <<< "$outbound_config"), 密码: $(jq -r '.pass' <<< "$outbound_config")"
else
echo -e "${red}未找到对应的出站配置。${none}"
fi
done
save_all_ss_links
}
add_new_nodes() {
local sk5_file="/home/sk5.txt"
# 检查sk5.txt文件是否存在
if [ ! -f "$sk5_file" ]; then
echo -e "${red}错误!${none} $sk5_file 文件不存在。"
return
fi
# 读取sk5.txt文件中的代理信息
local sk5_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
sk5_proxies+=("$line")
done < "$sk5_file"
local proxy_count=${#sk5_proxies[@]}
if [ $proxy_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $sk5_file 中找到有效的代理配置。"
return
fi
echo -e "${green}$sk5_file 读取到 $proxy_count 个代理配置。${none}"
read -p "是否要导入全部配置?(y/n): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
read -p "请输入要导入的代理数量 (最大 $proxy_count): " num_to_import
if ! [[ $num_to_import =~ ^[0-9]+$ ]] || [ $num_to_import -le 0 ] || [ $num_to_import -gt $proxy_count ]; then
echo -e "${red}错误!${none} 输入数量无效。"
return
fi
else
num_to_import=$proxy_count
fi
local max_port=$(jq '[.inbounds[].port] | max // 10000' "$config_file")
local start_port=$((max_port+1))
local local_ip=$(get_local_ip)
local nodes_to_save=()
for ((i=0; i<num_to_import; i++)); do
local new_port=$((start_port+i))
local new_tag="inbound$new_port"
local new_outbound_tag="outbound$new_port"
# 为Shadowsocks 2022生成密钥
local new_password=$(openssl rand -base64 32)
local method="2022-blake3-aes-256-gcm" # 使用默认的Shadowsocks 2022加密方法
# 解析代理信息 (格式: IP:端口:用户名:密码)
IFS=':' read -r outbound_addr outbound_port outbound_user outbound_pass <<< "${sk5_proxies[$i]}"
if [[ -z "$outbound_addr" || -z "$outbound_port" || -z "$outbound_user" || -z "$outbound_pass" ]]; then
echo -e "${red}警告:${none} 代理 #$((i+1)) 格式无效,终止脚本运行: ${sk5_proxies[$i]}"
exit 1
fi
echo -e "${yellow}配置入站端口 $new_port 连接到代理 $outbound_addr:$outbound_port${none}"
# 添加Shadowsocks入站配置
jq --argjson port "$new_port" --arg password "$new_password" --arg method "$method" --arg tag "$new_tag" '
.inbounds += [{
listen: "0.0.0.0",
port: $port,
protocol: "shadowsocks",
settings: {
method: $method,
password: $password,
network: "tcp,udp"
},
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加出站配置
jq --arg tag "$new_outbound_tag" --arg addr "$outbound_addr" --argjson port "$outbound_port" --arg user "$outbound_user" --arg pass "$outbound_pass" '
.outbounds += [{
protocol: "socks",
settings: { servers: [{ address: $addr, port: $port | tonumber, users: [{ user: $user, pass: $pass }] }] },
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加路由规则
jq --arg inTag "$new_tag" --arg outTag "$new_outbound_tag" '
.routing.rules += [{ type: "field", inboundTag: [$inTag], outboundTag: $outTag }]
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 保存节点信息以便后续生成SS链接
nodes_to_save+=("$new_port" "$new_password" "$method" "$((i+1))")
done
# 保存所有新添加的节点到一个文件
save_multiple_ss_links "$local_ip" "${nodes_to_save[@]}"
echo -e "${green}已成功添加 $num_to_import 个节点。${none}"
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 根据xiugai.txt文件修改SOCKS5出站代理
modify_socks5_outbound() {
local modify_file="/home/xiugai.txt"
# 检查xiugai.txt文件是否存在
if [ ! -f "$modify_file" ]; then
echo -e "${red}错误!${none} $modify_file 文件不存在。"
return
fi
# 读取xiugai.txt文件中的代理信息
local modify_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
modify_proxies+=("$line")
done < "$modify_file"
# 检查是否读取到代理
local proxy_count=${#modify_proxies[@]}
if [ $proxy_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的代理配置。"
return
fi
echo -e "${green}$modify_file 读取到 $proxy_count 个代理配置。${none}"
local local_ip=$(get_local_ip)
local nodes_to_save=()
# 处理每个要修改的代理
for proxy in "${modify_proxies[@]}"; do
IFS=':' read -r old_ip new_port new_user new_pass <<< "$proxy"
if [[ -z "$old_ip" || -z "$new_port" || -z "$new_user" || -z "$new_pass" ]]; then
echo -e "${red}警告:${none} 代理格式无效,终止脚本运行: $proxy"
exit 1
fi
# 查找匹配的出站节点
local outbound_config=$(jq --arg ip "$old_ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, address: .settings.servers[0].address, port: .settings.servers[0].port, user: .settings.servers[0].users[0].user, pass: .settings.servers[0].users[0].pass}' "$config_file")
if [[ -z "$outbound_config" ]]; then
echo -e "${red}警告:${none} 未找到IP地址为 $old_ip 的SOCKS5出站节点终止脚本运行"
exit 1
fi
local tag=$(echo "$outbound_config" | jq -r '.tag')
local old_port=$(echo "$outbound_config" | jq -r '.port')
local old_user=$(echo "$outbound_config" | jq -r '.user')
local old_pass=$(echo "$outbound_config" | jq -r '.pass')
echo -e "${yellow}找到匹配的出站节点:${none} 标签=$tag, 旧IP=$old_ip, 旧端口=$old_port, 旧用户名=$old_user, 旧密码=$old_pass"
echo -e "${green}将更新为:${none} 新IP=$old_ip, 新端口=$new_port, 新用户名=$new_user, 新密码=$new_pass"
# 更新SOCKS5出站配置
local temp_file=$(mktemp)
jq --arg tag "$tag" \
--arg ip "$old_ip" \
--arg port "$new_port" \
--arg user "$new_user" \
--arg pass "$new_pass" \
'(.outbounds[] | select(.tag == $tag) | .settings.servers[0].address) = $ip |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].port) = ($port | tonumber) |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].user) = $user |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].pass) = $pass' \
"$config_file" > "$temp_file"
if [ $? -eq 0 ] && [ -s "$temp_file" ]; then
mv "$temp_file" "$config_file"
echo -e "${green}成功修改SOCKS5出站节点配置!${none}"
# 查找对应的入站配置并保存节点信息
local inbound_port=${tag//outbound/}
local inbound_config=$(jq --arg port "$inbound_port" '.inbounds[] | select(.port == ($port | tonumber))' "$config_file")
if [[ -n "$inbound_config" ]]; then
local method=$(echo "$inbound_config" | jq -r '.settings.method')
local password=$(echo "$inbound_config" | jq -r '.settings.password')
local index=$(jq --arg port "$inbound_port" '.inbounds | map(select(.port != 9999)) | map(.port == ($port | tonumber)) | index(true)' "$config_file")
# 包含实际的SOCKS5 IP地址作为PS字段
nodes_to_save+=("$inbound_port" "$password" "$method" "$((index+1))" "$old_ip")
fi
else
echo -e "${red}更新配置失败!${none}"
rm -f "$temp_file"
continue
fi
done
if [[ ${#nodes_to_save[@]} -gt 0 ]]; then
save_multiple_ss_links_with_ps "$local_ip" "${nodes_to_save[@]}"
fi
sudo chmod 755 /usr/local/etc/xray/config.json
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 根据xiugai.txt文件删除节点
delete_nodes_by_ip() {
local modify_file="/home/xiugai.txt"
# 检查xiugai.txt文件是否存在
if [ ! -f "$modify_file" ]; then
echo -e "${red}错误!${none} $modify_file 文件不存在。"
return
fi
# 读取xiugai.txt文件中的代理信息
local modify_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
# 只提取IP部分
IFS=':' read -r ip _ <<< "$line"
modify_proxies+=("$ip")
done < "$modify_file"
# 检查是否读取到IP
local ip_count=${#modify_proxies[@]}
if [ $ip_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的IP地址。"
return
fi
echo -e "${green}$modify_file 读取到 $ip_count 个IP地址。${none}"
# 处理每个要删除的IP
for ip in "${modify_proxies[@]}"; do
# 查找匹配的出站节点
local outbound_config=$(jq --arg ip "$ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, port: .settings.servers[0].port}' "$config_file")
if [[ -z "$outbound_config" ]]; then
echo -e "${red}警告:${none} 未找到IP地址为 $ip 的SOCKS5出站节点终止脚本运行"
exit 1
fi
local outbound_tag=$(echo "$outbound_config" | jq -r '.tag')
# 从outbound_tag中提取端口号假设格式为"outbound端口号"
local port=${outbound_tag#outbound}
echo -e "${yellow}找到匹配的节点:${none} 出站标签=$outbound_tag, IP=$ip, 端口=$port"
# 查找对应的入站配置
local inbound_config=$(jq --arg port "$port" '.inbounds[] | select(.port == ($port | tonumber))' "$config_file")
if [[ -z "$inbound_config" ]]; then
echo -e "${red}警告:${none} 未找到对应端口 $port 的入站配置,继续删除出站配置"
else
local inbound_tag=$(echo "$inbound_config" | jq -r '.tag')
echo -e "${yellow}找到对应的入站配置:${none} 标签=$inbound_tag"
# 删除入站配置
jq --arg port "$port" 'del(.inbounds[] | select(.port == ($port | tonumber)))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 删除路由规则使用实际的inbound_tag而不是构造的标签
jq --arg inTag "$inbound_tag" 'del(.routing.rules[] | select(.inboundTag[] == $inTag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
fi
# 删除出站配置
jq --arg tag "$outbound_tag" 'del(.outbounds[] | select(.tag == $tag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
echo -e "${green}已成功删除IP地址为 $ip 的节点。${none}"
done
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
main_menu() {
while true; do
echo -e "\n${green}快速批量搭建二级代理脚本-管理菜单:${none}"
echo "1. 查看所有节点"
echo "2. 新增Shadowsocks入站sk5出站(从/home/sk5.txt文件导入)"
echo "3. 删除节点(根据/home/xiugai.txt文件匹配)"
echo "4. 修改SOCKS5出站节点(根据/home/xiugai.txt文件匹配)"
echo "5. 退出"
read -p "请输入选项: " choice
case $choice in
1) show_inbound_configs ;;
2) add_new_nodes ;;
3) delete_nodes_by_ip ;;
4) modify_socks5_outbound ;;
5) break ;;
*) echo -e "${red}无效的选项,请重新选择。${none}" ;;
esac
done
}
check_and_install_curl
check_and_install_jq
check_and_install_openssl
check_and_install_xray
create_default_config
get_local_ip
main_menu

578
proxy/duosk5-vmess.sh Normal file
View File

@@ -0,0 +1,578 @@
#!/bin/bash
# 批量搭建vmess+ws入站到sk5出站代理
# 读取sk5文件实现批量导入出站
# 作者sky22333
red='\e[31m'
yellow='\e[33m'
green='\e[32m'
none='\e[0m'
config_file="/usr/local/etc/xray/config.json"
default_config='
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 9999,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "wss"
}
]
},
"streamSettings": {
"network": "ws",
"security": "none",
"wsSettings": {
"path": "/wss"
}
},
"tag": "inbound0"
}
],
"outbounds": [
{
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.2",
"port": 2222,
"users": [
{
"user": "admin123",
"pass": "admin333"
}
]
}
]
},
"tag": "outbound0"
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["inbound0"],
"outboundTag": "outbound0"
}
]
}
}
'
# 检查并安装curl
check_and_install_curl() {
if ! type curl &>/dev/null; then
echo -e "${yellow}正在安装curl...${none}"
apt update && apt install -yq curl
fi
}
# 检查并安装jq
check_and_install_jq() {
if ! type jq &>/dev/null; then
echo -e "${yellow}正在安装jq...${none}"
apt update && apt install -yq jq
fi
}
# 检查并安装uuid-runtime
check_and_install_uuid_runtime() {
if ! type uuidgen &>/dev/null; then
echo -e "${yellow}正在安装 uuid-runtime...${none}"
apt update && apt install -yq uuid-runtime
fi
}
# 检查并安装xray
check_and_install_xray() {
if ! type xray &>/dev/null; then
echo -e "${yellow}正在安装 xray...正在启用BBR...${none}"
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.4
fi
}
# 检查是否已存在入站配置
check_existing_inbound_config() {
if grep -q '"tag":' "$config_file"; then
return 0 # 已存在入站配置
else
return 1 # 不存在入站配置
fi
}
# 创建默认配置文件
create_default_config() {
if ! check_existing_inbound_config; then
echo "$default_config" > "$config_file"
echo -e "${green}已创建默认配置文件。${none}"
else
echo -e "${yellow}入站配置已存在,跳过创建默认配置文件。${none}"
fi
}
# 获取本机公网 IP
get_local_ip() {
local ip=$(curl -s http://ipinfo.io/ip)
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$ip"
else
echo "无法自动获取公网IP地址请手动输入。"
read -p "请输入您的公网IP地址: " manual_ip
if [[ $manual_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$manual_ip"
else
echo "输入的IP地址格式不正确请重新运行脚本并输入有效的公网IP地址。"
exit 1
fi
fi
}
# 生成带时间戳的vmess文件名(精确到秒)
get_vmess_filename() {
local timestamp=$(date +"%Y%m%d-%H点%M分%S秒")
echo "/home/${timestamp}-vmess.txt"
}
# 保存多个VMess节点到文件
save_multiple_vmess_links() {
local local_ip=$1
shift
local vmess_file=$(get_vmess_filename)
# 清空或创建文件
> "$vmess_file"
# 处理所有传入的节点信息
while [[ $# -gt 0 ]]; do
local port=$1
local id=$2
local path=$3
local index=$4
shift 4
# 获取对应的出站 SOCKS5 的 IP 地址
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP" # 如果未找到对应的IP设置为默认值
fi
# 生成VMess链接
local vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$sk5_ip\",\"add\":\"$local_ip\",\"port\":$port,\"id\":\"$id\",\"aid\":0,\"net\":\"ws\",\"path\":\"$path\",\"type\":\"none\"}" | base64 -w 0)"
# 写入文件
echo "$vmess_link" >> "$vmess_file"
done
echo -e "${green}已将操作的所有节点保存至 $vmess_file${none}"
}
# 保存多个VMess节点到文件带自定义PS字段
save_multiple_vmess_links_with_ps() {
local local_ip=$1
shift
local vmess_file=$(get_vmess_filename)
# 清空或创建文件
> "$vmess_file"
# 处理所有传入的节点信息
while [[ $# -gt 0 ]]; do
local port=$1
local id=$2
local path=$3
local index=$4
local sk5_ip=$5 # 额外参数用于PS字段
shift 5
# 生成VMess链接使用自定义PS字段
local vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$sk5_ip\",\"add\":\"$local_ip\",\"port\":$port,\"id\":\"$id\",\"aid\":0,\"net\":\"ws\",\"path\":\"$path\",\"type\":\"none\"}" | base64 -w 0)"
# 写入文件
echo "$vmess_link" >> "$vmess_file"
done
echo -e "${green}已将操作的所有节点保存至 $vmess_file${none}"
}
# 保存所有VMess节点到文件
save_all_vmess_links() {
local local_ip=$(get_local_ip) # 获取本机IP
local vmess_file=$(get_vmess_filename)
# 清空或创建文件
> "$vmess_file"
local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file")
local length=$(jq '. | length' <<< "$config")
for ((i = 0; i < length; i++)); do
local port=$(jq -r ".[$i].port" <<< "$config")
local id=$(jq -r ".[$i].settings.clients[0].id" <<< "$config")
local path=$(jq -r ".[$i].streamSettings.wsSettings.path" <<< "$config")
# 获取对应的出站 SOCKS5 的 IP 地址
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP" # 如果未找到对应的IP设置为默认值
fi
# 生成VMess链接
local vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$sk5_ip\",\"add\":\"$local_ip\",\"port\":$port,\"id\":\"$id\",\"aid\":0,\"net\":\"ws\",\"path\":\"$path\",\"type\":\"none\"}" | base64 -w 0)"
# 写入文件
echo "$vmess_link" >> "$vmess_file"
done
echo -e "${green}已将全部VMess节点保存至 $vmess_file${none}"
}
# 显示所有入站配置和 Vmess 链接以及对应的出站配置
show_inbound_configs() {
local local_ip=$(get_local_ip) # 获取本机IP
local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file")
local outbounds=$(jq '.outbounds' "$config_file")
echo -e "${green}入站节点配置:${none}"
local length=$(jq '. | length' <<< "$config")
for ((i = 0; i < length; i++)); do
local port=$(jq -r ".[$i].port" <<< "$config")
local id=$(jq -r ".[$i].settings.clients[0].id" <<< "$config")
local path=$(jq -r ".[$i].streamSettings.wsSettings.path" <<< "$config")
# 将节点地址设置为本机IP
local node_address="$local_ip"
# 获取sk5代理IP
local sk5_ip=$(jq -r ".outbounds | map(select(.tag == \"outbound${port}\")) | .[0].settings.servers[0].address" "$config_file")
if [[ -z "$sk5_ip" ]]; then
sk5_ip="未知IP" # 如果未找到对应的IP设置为默认值
fi
local vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$sk5_ip\",\"add\":\"$node_address\",\"port\":$port,\"id\":\"$id\",\"aid\":0,\"net\":\"ws\",\"path\":\"$path\",\"type\":\"none\"}" | base64 -w 0)"
echo -e "${yellow}节点: $(($i + 1))${none} - 端口: ${port}, Vmess 链接: ${vmess_link}"
# 构造出站配置的标签
local outbound_tag="outbound$port"
# 根据构造的标签查找对应的出站配置
local outbound_config=$(jq --arg tag "$outbound_tag" '.[] | select(.tag == $tag) | .settings.servers[] | {address, port, user: .users[0].user, pass: .users[0].pass}' <<< "$outbounds")
if [[ ! -z $outbound_config ]]; then
echo -e "${green}出站配置:${none} 地址: $(jq -r '.address' <<< "$outbound_config"), 端口: $(jq -r '.port' <<< "$outbound_config"), 用户名: $(jq -r '.user' <<< "$outbound_config"), 密码: $(jq -r '.pass' <<< "$outbound_config")"
else
echo -e "${red}未找到对应的出站配置。${none}"
fi
done
# 保存所有VMess链接到文件
save_all_vmess_links
}
# 添加新节点
add_new_nodes() {
local sk5_file="/home/sk5.txt"
# 检查sk5.txt文件是否存在
if [ ! -f "$sk5_file" ]; then
echo -e "${red}错误!${none} $sk5_file 文件不存在。"
return
fi
# 读取sk5.txt文件中的代理信息
local sk5_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
sk5_proxies+=("$line")
done < "$sk5_file"
# 检查是否读取到代理
local proxy_count=${#sk5_proxies[@]}
if [ $proxy_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $sk5_file 中找到有效的代理配置。"
return
fi
echo -e "${green}$sk5_file 读取到 $proxy_count 个代理配置。${none}"
read -p "是否要导入全部配置?(y/n): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
read -p "请输入要导入的代理数量 (最大 $proxy_count): " num_to_import
if ! [[ $num_to_import =~ ^[0-9]+$ ]] || [ $num_to_import -le 0 ] || [ $num_to_import -gt $proxy_count ]; then
echo -e "${red}错误!${none} 输入数量无效。"
return
fi
else
num_to_import=$proxy_count
fi
local max_port=$(jq '[.inbounds[].port] | max // 10000' "$config_file")
local start_port=$((max_port+1))
local local_ip=$(get_local_ip)
local nodes_to_save=()
for ((i=0; i<num_to_import; i++)); do
local new_port=$((start_port+i))
local new_tag="inbound$new_port"
local new_outbound_tag="outbound$new_port"
local new_id=$(uuidgen)
# 解析代理信息 (格式: IP:端口:用户名:密码)
IFS=':' read -r outbound_addr outbound_port outbound_user outbound_pass <<< "${sk5_proxies[$i]}"
if [[ -z "$outbound_addr" || -z "$outbound_port" || -z "$outbound_user" || -z "$outbound_pass" ]]; then
echo -e "${red}警告:${none} 代理 #$((i+1)) 格式无效,终止脚本运行: ${sk5_proxies[$i]}"
exit 1
fi
echo -e "${yellow}配置入站端口 $new_port 连接到代理 $outbound_addr:$outbound_port${none}"
# 添加入站配置(入站地址设置为 "0.0.0.0"
jq --argjson port "$new_port" --arg id "$new_id" --arg tag "$new_tag" '
.inbounds += [{
listen: "0.0.0.0",
port: $port,
protocol: "vmess",
settings: { clients: [{ id: $id }] },
streamSettings: { network: "ws", security: "none", wsSettings: { path: "/websocket" } },
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加出站配置
jq --arg tag "$new_outbound_tag" --arg addr "$outbound_addr" --argjson port "$outbound_port" --arg user "$outbound_user" --arg pass "$outbound_pass" '
.outbounds += [{
protocol: "socks",
settings: { servers: [{ address: $addr, port: $port | tonumber, users: [{ user: $user, pass: $pass }] }] },
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加路由规则
jq --arg inTag "$new_tag" --arg outTag "$new_outbound_tag" '
.routing.rules += [{ type: "field", inboundTag: [$inTag], outboundTag: $outTag }]
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 保存节点信息以便后续生成VMess链接
nodes_to_save+=("$new_port" "$new_id" "/websocket" "$((i+1))")
done
# 保存所有新添加的节点到一个文件
save_multiple_vmess_links "$local_ip" "${nodes_to_save[@]}"
echo -e "${green}已成功添加 $num_to_import 个节点。${none}"
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 根据xiugai.txt文件修改SOCKS5出站代理
modify_socks5_outbound() {
local modify_file="/home/xiugai.txt"
# 检查xiugai.txt文件是否存在
if [ ! -f "$modify_file" ]; then
echo -e "${red}错误!${none} $modify_file 文件不存在。"
return
fi
# 读取xiugai.txt文件中的代理信息
local modify_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
modify_proxies+=("$line")
done < "$modify_file"
# 检查是否读取到代理
local proxy_count=${#modify_proxies[@]}
if [ $proxy_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的代理配置。"
return
fi
echo -e "${green}$modify_file 读取到 $proxy_count 个代理配置。${none}"
local local_ip=$(get_local_ip)
local nodes_to_save=()
# 处理每个要修改的代理
for proxy in "${modify_proxies[@]}"; do
IFS=':' read -r old_ip new_port new_user new_pass <<< "$proxy"
if [[ -z "$old_ip" || -z "$new_port" || -z "$new_user" || -z "$new_pass" ]]; then
echo -e "${red}警告:${none} 代理格式无效,终止脚本运行: $proxy"
exit 1
fi
# 查找匹配的出站节点
local outbound_config=$(jq --arg ip "$old_ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, address: .settings.servers[0].address, port: .settings.servers[0].port, user: .settings.servers[0].users[0].user, pass: .settings.servers[0].users[0].pass}' "$config_file")
if [[ -z "$outbound_config" ]]; then
echo -e "${red}警告:${none} 未找到IP地址为 $old_ip 的SOCKS5出站节点终止脚本运行"
exit 1
fi
local tag=$(echo "$outbound_config" | jq -r '.tag')
local old_port=$(echo "$outbound_config" | jq -r '.port')
local old_user=$(echo "$outbound_config" | jq -r '.user')
local old_pass=$(echo "$outbound_config" | jq -r '.pass')
echo -e "${yellow}找到匹配的出站节点:${none} 标签=$tag, 旧IP=$old_ip, 旧端口=$old_port, 旧用户名=$old_user, 旧密码=$old_pass"
echo -e "${green}将更新为:${none} 新IP=$old_ip, 新端口=$new_port, 新用户名=$new_user, 新密码=$new_pass"
# 更新SOCKS5出站配置
local temp_file=$(mktemp)
jq --arg tag "$tag" \
--arg ip "$old_ip" \
--argjson port "$new_port" \
--arg user "$new_user" \
--arg pass "$new_pass" \
'(.outbounds[] | select(.tag == $tag) | .settings.servers[0].address) = $ip |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].port) = $port |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].user) = $user |
(.outbounds[] | select(.tag == $tag) | .settings.servers[0].users[0].pass) = $pass' \
"$config_file" > "$temp_file"
if [ $? -eq 0 ] && [ -s "$temp_file" ]; then
mv "$temp_file" "$config_file"
echo -e "${green}成功修改SOCKS5出站节点配置!${none}"
# 查找对应的入站配置并保存节点信息
local inbound_port=${tag//outbound/}
local inbound_config=$(jq --argjson port "$inbound_port" '.inbounds[] | select(.port == $port)' "$config_file")
if [[ -n "$inbound_config" ]]; then
local id=$(echo "$inbound_config" | jq -r '.settings.clients[0].id')
local path=$(echo "$inbound_config" | jq -r '.streamSettings.wsSettings.path')
local index=$(jq '.inbounds | map(select(.port != 9999)) | map(.port == '$inbound_port') | index(true)' "$config_file")
# 修复: 包含实际的SOCKS5 IP地址作为PS字段
nodes_to_save+=("$inbound_port" "$id" "$path" "$((index+1))" "$old_ip")
fi
else
echo -e "${red}更新配置失败!${none}"
rm -f "$temp_file"
continue
fi
done
# 保存所有修改的节点到一个文件使用新函数传递PS字段
if [[ ${#nodes_to_save[@]} -gt 0 ]]; then
save_multiple_vmess_links_with_ps "$local_ip" "${nodes_to_save[@]}"
fi
# 重启Xray服务使配置生效
sudo chmod 755 /usr/local/etc/xray/config.json
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 根据xiugai.txt文件删除节点
delete_nodes_by_ip() {
local modify_file="/home/xiugai.txt"
# 检查xiugai.txt文件是否存在
if [ ! -f "$modify_file" ]; then
echo -e "${red}错误!${none} $modify_file 文件不存在。"
return
fi
# 读取xiugai.txt文件中的代理信息
local modify_proxies=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 忽略空行
if [[ -z "$line" ]]; then
continue
fi
# 只提取IP部分
IFS=':' read -r ip _ <<< "$line"
modify_proxies+=("$ip")
done < "$modify_file"
# 检查是否读取到IP
local ip_count=${#modify_proxies[@]}
if [ $ip_count -eq 0 ]; then
echo -e "${red}错误!${none} 未在 $modify_file 中找到有效的IP地址。"
return
fi
echo -e "${green}$modify_file 读取到 $ip_count 个IP地址。${none}"
# 处理每个要删除的IP
for ip in "${modify_proxies[@]}"; do
# 查找匹配的出站节点
local outbound_config=$(jq --arg ip "$ip" '.outbounds[] | select(.protocol == "socks" and .settings.servers[0].address == $ip) | {tag: .tag, port: .settings.servers[0].port}' "$config_file")
if [[ -z "$outbound_config" ]]; then
echo -e "${red}警告:${none} 未找到IP地址为 $ip 的SOCKS5出站节点终止脚本运行"
exit 1
fi
local outbound_tag=$(echo "$outbound_config" | jq -r '.tag')
# 从outbound_tag中提取端口号假设格式为"outbound端口号"
local port=${outbound_tag#outbound}
echo -e "${yellow}找到匹配的节点:${none} 出站标签=$outbound_tag, IP=$ip, 端口=$port"
# 查找对应的入站配置
local inbound_config=$(jq --argjson port "$port" '.inbounds[] | select(.port == $port)' "$config_file")
if [[ -z "$inbound_config" ]]; then
echo -e "${red}警告:${none} 未找到对应端口 $port 的入站配置,继续删除出站配置"
else
local inbound_tag=$(echo "$inbound_config" | jq -r '.tag')
echo -e "${yellow}找到对应的入站配置:${none} 标签=$inbound_tag"
# 删除入站配置
jq --argjson port "$port" 'del(.inbounds[] | select(.port == $port))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 删除路由规则使用实际的inbound_tag而不是构造的标签
jq --arg inTag "$inbound_tag" 'del(.routing.rules[] | select(.inboundTag[] == $inTag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
fi
# 删除出站配置
jq --arg tag "$outbound_tag" 'del(.outbounds[] | select(.tag == $tag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
echo -e "${green}已成功删除IP地址为 $ip 的节点。${none}"
done
sudo systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 主菜单
main_menu() {
while true; do
echo -e "\n${green}快速批量搭建二级代理脚本-管理菜单:${none}"
echo "1. 查看所有节点"
echo "2. 新增vmess入站sk5出站(从/home/sk5.txt文件导入)"
echo "3. 删除节点(根据/home/xiugai.txt文件匹配)"
echo "4. 修改SOCKS5出站节点(根据/home/xiugai.txt文件匹配)"
echo "5. 退出"
read -p "请输入选项: " choice
case $choice in
1) show_inbound_configs ;;
2) add_new_nodes ;;
3) delete_nodes_by_ip ;;
4) modify_socks5_outbound ;;
5) break ;;
*) echo -e "${red}无效的选项,请重新选择。${none}" ;;
esac
done
}
# 调用主菜单函数
check_and_install_curl
check_and_install_jq
check_and_install_uuid_runtime
check_and_install_xray
create_default_config
get_local_ip
main_menu

469
proxy/duosk5.go Normal file
View File

@@ -0,0 +1,469 @@
package main
import (
"bufio"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
)
const (
// 颜色常量ANSI转义码
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorCyan = "\033[36m"
// 构建CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o sk5 main.go
// 脚本过期时间以及其他变量
EXPIRE_DATE = "2025-06-08 02:01:01"
CONFIG_FILE = "/usr/local/etc/xray/config.json"
SOCKS_FILE = "/home/socks.txt"
XRAY_INSTALL_URL = "https://github.com/XTLS/Xray-install/raw/main/install-release.sh"
XRAY_VERSION = "v1.8.4"
START_PORT = 10001
)
// 彩色打印函数
func colorPrint(colorCode, format string, a ...interface{}) {
fmt.Printf(colorCode+format+ColorReset+"\n", a...)
}
// XrayConfig represents the Xray configuration structure
type XrayConfig struct {
Inbounds []Inbound `json:"inbounds"`
Outbounds []Outbound `json:"outbounds"`
Routing Routing `json:"routing"`
}
type Inbound struct {
Port int `json:"port"`
Protocol string `json:"protocol"`
Settings InboundSettings `json:"settings"`
StreamSettings StreamSettings `json:"streamSettings"`
Tag string `json:"tag"`
}
type InboundSettings struct {
Auth string `json:"auth"`
Accounts []Account `json:"accounts"`
UDP bool `json:"udp"`
IP string `json:"ip"`
}
type Account struct {
User string `json:"user"`
Pass string `json:"pass"`
}
type StreamSettings struct {
Network string `json:"network"`
}
type Outbound struct {
Protocol string `json:"protocol"`
Settings interface{} `json:"settings"`
SendThrough string `json:"sendThrough"`
Tag string `json:"tag"`
}
type Routing struct {
Rules []Rule `json:"rules"`
}
type Rule struct {
Type string `json:"type"`
InboundTag []string `json:"inboundTag"`
OutboundTag string `json:"outboundTag"`
}
type NodeInfo struct {
IP string
Port int
Username string
Password string
}
// generateRandomString generates a random string of specified length
func generateRandomString(length int) string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
panic(err)
}
for i := range b {
b[i] = charset[b[i]%byte(len(charset))]
}
return string(b)
}
// checkExpiration checks if the script has expired
func checkExpiration() error {
colorPrint(ColorCyan, "开始运行...")
// Get timestamp from cloudflare
resp, err := http.Get("https://www.cloudflare.com/cdn-cgi/trace")
if err != nil {
return fmt.Errorf("网络错误")
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取响应失败")
}
// Extract timestamp
re := regexp.MustCompile(`ts=(\d+)`)
matches := re.FindStringSubmatch(string(body))
if len(matches) < 2 {
return fmt.Errorf("无法解析时间")
}
timestamp, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return fmt.Errorf("时间转换失败")
}
// Convert to Beijing time
currentTime := time.Unix(timestamp, 0).In(time.FixedZone("CST", 8*3600))
expireTime, _ := time.ParseInLocation("2006-01-02 15:04:05", EXPIRE_DATE, time.FixedZone("CST", 8*3600))
if currentTime.After(expireTime) {
return fmt.Errorf("当前脚本已过期,请联系作者")
}
return nil
}
// commandExists checks if a command exists in PATH
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
// installJQ installs jq if not present
func installJQ() error {
if commandExists("jq") {
colorPrint(ColorGreen, "jq 已安装")
return nil
}
colorPrint(ColorYellow, "jq 未安装,正在安装 jq...")
// Detect OS
if _, err := os.Stat("/etc/debian_version"); err == nil {
// Debian/Ubuntu
cmd := exec.Command("bash", "-c", "apt update && apt install -yq jq")
return cmd.Run()
} else if _, err := os.Stat("/etc/redhat-release"); err == nil {
// RHEL/CentOS
cmd := exec.Command("yum", "install", "-y", "epel-release", "jq")
return cmd.Run()
}
return fmt.Errorf("无法确定系统发行版,请手动安装 jq")
}
// installXray installs Xray if not present
func installXray() error {
if commandExists("xray") {
colorPrint(ColorGreen, "Xray 已安装")
return nil
}
colorPrint(ColorYellow, "Xray 未安装,正在安装 Xray...")
cmd := exec.Command("bash", "-c", fmt.Sprintf("curl -L %s | bash -s install --version %s", XRAY_INSTALL_URL, XRAY_VERSION))
if err := cmd.Run(); err != nil {
return fmt.Errorf("Xray 安装失败: %v", err)
}
colorPrint(ColorGreen, "Xray 安装完成")
return nil
}
// getPublicIPv4 gets all public IPv4 addresses
func getPublicIPv4() ([]string, error) {
var publicIPs []string
// Get all network interfaces
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
ip := ipNet.IP.String()
// Check if it's a public IP
if isPublicIP(ip) {
publicIPs = append(publicIPs, ip)
}
}
}
}
}
return publicIPs, nil
}
// isPublicIP checks if an IP is public
func isPublicIP(ip string) bool {
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return false
}
// Check for private IP ranges
privateRanges := []string{
"127.0.0.0/8", // loopback
"10.0.0.0/8", // private
"172.16.0.0/12", // private
"192.168.0.0/16", // private
"169.254.0.0/16", // link-local
}
for _, cidr := range privateRanges {
_, network, _ := net.ParseCIDR(cidr)
if network.Contains(parsedIP) {
return false
}
}
return true
}
// ensureSocksFileExists creates socks.txt if it doesn't exist
func ensureSocksFileExists() error {
if _, err := os.Stat(SOCKS_FILE); os.IsNotExist(err) {
colorPrint(ColorYellow, "socks.txt 文件不存在,正在创建...")
file, err := os.Create(SOCKS_FILE)
if err != nil {
return err
}
file.Close()
}
return nil
}
// saveNodeInfo saves node information to file and prints it
func saveNodeInfo(node NodeInfo) error {
// Print node info with colors
fmt.Printf(" IP: %s%s%s 端口: %s%d%s 用户名: %s%s%s 密码: %s%s%s\n",
ColorGreen, node.IP, ColorReset,
ColorGreen, node.Port, ColorReset,
ColorGreen, node.Username, ColorReset,
ColorGreen, node.Password, ColorReset)
// Save to file
file, err := os.OpenFile(SOCKS_FILE, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = fmt.Fprintf(file, "%s %d %s %s\n", node.IP, node.Port, node.Username, node.Password)
return err
}
// configureXray configures Xray with multiple IPs
func configureXray() error {
publicIPs, err := getPublicIPv4()
if err != nil {
return fmt.Errorf("获取公网IP失败: %v", err)
}
if len(publicIPs) == 0 {
return fmt.Errorf("未找到额外IP地址")
}
colorPrint(ColorCyan, "找到的公网 IPv4 地址: %v", publicIPs)
// Create initial config
config := XrayConfig{
Inbounds: []Inbound{},
Outbounds: []Outbound{},
Routing: Routing{
Rules: []Rule{},
},
}
// Configure each IP
port := START_PORT
for _, ip := range publicIPs {
colorPrint(ColorCyan, "正在配置 IP: %s 端口: %d", ip, port)
username := generateRandomString(8)
password := generateRandomString(8)
// Create inbound
inbound := Inbound{
Port: port,
Protocol: "socks",
Settings: InboundSettings{
Auth: "password",
Accounts: []Account{
{User: username, Pass: password},
},
UDP: true,
IP: "0.0.0.0",
},
StreamSettings: StreamSettings{
Network: "tcp",
},
Tag: fmt.Sprintf("in-%d", port),
}
// Create outbound
outbound := Outbound{
Protocol: "freedom",
Settings: map[string]interface{}{},
SendThrough: ip,
Tag: fmt.Sprintf("out-%d", port),
}
// Create routing rule
rule := Rule{
Type: "field",
InboundTag: []string{fmt.Sprintf("in-%d", port)},
OutboundTag: fmt.Sprintf("out-%d", port),
}
config.Inbounds = append(config.Inbounds, inbound)
config.Outbounds = append(config.Outbounds, outbound)
config.Routing.Rules = append(config.Routing.Rules, rule)
// Save node info
node := NodeInfo{
IP: ip,
Port: port,
Username: username,
Password: password,
}
if err := saveNodeInfo(node); err != nil {
return fmt.Errorf("保存节点信息失败: %v", err)
}
port++
}
// Write config file
configData, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("序列化配置失败: %v", err)
}
if err := os.WriteFile(CONFIG_FILE, configData, 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %v", err)
}
colorPrint(ColorGreen, "Xray 配置完成")
return nil
}
// restartXray restarts the Xray service
func restartXray() error {
colorPrint(ColorCyan, "正在重启 Xray 服务...")
// Restart service
cmd := exec.Command("systemctl", "restart", "xray")
if err := cmd.Run(); err != nil {
return fmt.Errorf("Xray 服务重启失败: %v", err)
}
// Enable service
cmd = exec.Command("systemctl", "enable", "xray")
if err := cmd.Run(); err != nil {
return fmt.Errorf("启用 Xray 服务失败: %v", err)
}
colorPrint(ColorGreen, "Xray 服务已重启")
return nil
}
// readUserInput reads user input for confirmation
func readUserInput(prompt string) string {
fmt.Print(prompt)
reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
return strings.TrimSpace(input)
}
func main() {
colorPrint(ColorCyan, "站群多IP源进源出sk5协议一键脚本")
colorPrint(ColorCyan, "当前为测试版,可以联系作者获取源码")
expireTime, err := time.ParseInLocation("2006-01-02 15:04:05", EXPIRE_DATE, time.FixedZone("CST", 8*3600))
if err == nil {
expireStr := fmt.Sprintf("%d年%d月%d日%d点%d分%d秒",
expireTime.Year(),
expireTime.Month(),
expireTime.Day(),
expireTime.Hour(),
expireTime.Minute(),
expireTime.Second())
colorPrint(ColorCyan, "脚本过期时间: %s", expireStr)
} else {
colorPrint(ColorYellow, "脚本过期时间解析失败")
}
fmt.Println()
// Check expiration
if err := checkExpiration(); err != nil {
colorPrint(ColorRed, "错误: %v", err)
os.Exit(1)
}
// Ensure socks file exists
if err := ensureSocksFileExists(); err != nil {
colorPrint(ColorRed, "创建socks文件失败: %v", err)
os.Exit(1)
}
// Install jq
if err := installJQ(); err != nil {
colorPrint(ColorRed, "安装jq失败: %v", err)
os.Exit(1)
}
// Install Xray
if err := installXray(); err != nil {
colorPrint(ColorRed, "安装Xray失败: %v", err)
os.Exit(1)
}
// Configure Xray
if err := configureXray(); err != nil {
colorPrint(ColorRed, "配置Xray失败: %v", err)
os.Exit(1)
}
// Restart Xray
if err := restartXray(); err != nil {
colorPrint(ColorRed, "重启Xray失败: %v", err)
os.Exit(1)
}
colorPrint(ColorGreen, "部署完成,所有节点信息已保存到 %s", SOCKS_FILE)
}

237
proxy/duosocks.sh Normal file
View File

@@ -0,0 +1,237 @@
#!/bin/bash
# 站群多IP源进源出节点脚本sk5协议
# 生成随机8位数的用户名和密码
generate_random_string() {
local length=8
tr -dc A-Za-z0-9 </dev/urandom | head -c $length
}
install_jq() {
if ! command -v jq &> /dev/null; then
echo "jq 未安装,正在安装 jq..."
if [[ -f /etc/debian_version ]]; then
apt update && apt install -yq jq
elif [[ -f /etc/redhat-release ]]; then
yum install -y epel-release jq
else
echo "无法确定系统发行版,请手动安装 jq。"
exit 1
fi
else
echo "jq 已安装。"
fi
}
install_xray() {
if ! command -v xray &> /dev/null; then
echo "Xray 未安装,正在安装 Xray..."
if ! bash <(curl -sSL https://gh-proxy.com/https://github.com/sky22333/shell/raw/main/proxy/xray.sh); then
echo "Xray 安装失败,请检查网络连接或安装脚本。"
exit 1
fi
echo "Xray 安装完成。"
else
echo "Xray 已安装。"
fi
}
get_public_ipv4() {
ip -4 addr show | awk '/inet / {ip = $2; sub(/\/.*/, "", ip); if (ip !~ /^127\./ && ip !~ /^10\./ && ip !~ /^192\.168\./ && ip !~ /^169\.254\./ && ip !~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./) print ip}'
}
# 确保 socks.txt 文件存在,如果不存在则创建
ensure_socks_file_exists() {
if [ ! -f /home/socks.txt ]; then
echo "socks.txt 文件不存在,正在创建..."
touch /home/socks.txt
fi
}
print_node_info() {
local ip=$1
local port=$2
local username=$3
local password=$4
local outfile=${5:-/home/socks.txt}
echo -e " IP: \033[32m$ip\033[0m 端口: \033[32m$port\033[0m 用户名: \033[32m$username\033[0m 密码: \033[32m$password\033[0m"
echo "$ip $port $username $password" >> "$outfile"
}
configure_xray() {
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到额外IP地址退出..."
exit 1
fi
echo "找到的公网 IPv4 地址: ${public_ips[@]}"
config_file="/usr/local/etc/xray/config.json"
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"routing": {
"rules": []
}
}
EOF
# 配置 inbounds 和 outbounds
port=10001
for ip in "${public_ips[@]}"; do
echo "正在配置 IP: $ip 端口: $port"
# 此处用户名和密码可以改为固定值
username=$(generate_random_string)
password=$(generate_random_string)
jq --argjson port "$port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"port": $port,
"protocol": "socks",
"settings": {
"auth": "password",
"accounts": [{
"user": $username,
"pass": $password
}],
"udp": true,
"ip": "0.0.0.0"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
print_node_info "$ip" "$port" "$username" "$password"
port=$((port + 1))
done
echo "Xray 配置完成。"
}
restart_xray() {
echo "正在重启 Xray 服务..."
if ! systemctl restart xray; then
echo "Xray 服务重启失败,请检查配置文件。"
exit 1
fi
systemctl enable xray
echo "Xray 服务已重启。"
}
add_mode=false
if [[ "$1" == "-add" ]]; then
add_mode=true
fi
main() {
ensure_socks_file_exists
install_jq
install_xray
if $add_mode; then
add_xray_nodes
else
config_file="/usr/local/etc/xray/config.json"
if [[ -f $config_file ]]; then
if jq -e '.inbounds[]? | select(.port==10001)' "$config_file" >/dev/null; then
echo "检测到已有节点配置,无需重复生成,如需添加节点请添加 -add命令"
exit 0
fi
fi
configure_xray
restart_xray
echo "部署完成,所有节点信息已保存到 /home/socks.txt"
fi
}
add_xray_nodes() {
public_ips=($(get_public_ipv4))
config_file="/usr/local/etc/xray/config.json"
if [[ ! -f $config_file ]]; then
echo "Xray 配置文件不存在,无法追加。"
exit 1
fi
# 获取已存在的IP
existing_ips=($(jq -r '.outbounds[].sendThrough' "$config_file" | grep -v null))
# 过滤出未添加的新IP
new_ips=()
for ip in "${public_ips[@]}"; do
found=false
for eip in "${existing_ips[@]}"; do
if [[ "$ip" == "$eip" ]]; then
found=true
break
fi
done
if ! $found; then
new_ips+=("$ip")
fi
done
if [[ ${#new_ips[@]} -eq 0 ]]; then
echo "没有新IP需要追加。"
return
fi
# 生成北京时间文件名
beijing_time=$(TZ=Asia/Shanghai date +"%Y%m%d_%H%M%S")
newfile="/home/socks_add_${beijing_time}.txt"
touch "$newfile"
# 找到当前最大端口
last_port=$(jq -r '.inbounds[].port' "$config_file" | sort -n | tail -1)
if [[ -z "$last_port" || "$last_port" == "null" ]]; then
port=10001
else
port=$((last_port + 1))
fi
for ip in "${new_ips[@]}"; do
echo "追加 IP: $ip 端口: $port"
username=$(generate_random_string)
password=$(generate_random_string)
jq --argjson port "$port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"port": $port,
"protocol": "socks",
"settings": {
"auth": "password",
"accounts": [{
"user": $username,
"pass": $password
}],
"udp": true,
"ip": "0.0.0.0"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
print_node_info "$ip" "$port" "$username" "$password" "$newfile"
port=$((port + 1))
done
echo "Xray 追加完成,新增节点信息已保存到 $newfile"
restart_xray
}
main

141
proxy/duovmess.sh Normal file
View File

@@ -0,0 +1,141 @@
#!/bin/bash
# 站群多IP源进源出节点脚本vmess+ws协议
# 作者sky22333
install_jq() {
# 检查 jq 和 uuidgen 是否已安装
if ! command -v jq &> /dev/null || ! command -v uuidgen &> /dev/null; then
echo "未找到 jq 或 uuidgen正在安装依赖..."
if [[ -f /etc/debian_version ]]; then
apt update && apt install -yq jq uuid-runtime
elif [[ -f /etc/redhat-release ]]; then
yum install -y jq util-linux
else
echo "无法确定系统发行版,请手动安装 jq 和 uuid-runtime。"
exit 1
fi
else
echo "jq 和 uuidgen 都已安装。"
fi
}
install_xray() {
if ! command -v xray &> /dev/null; then
echo "Xray 未安装,正在安装 Xray..."
if ! bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.4; then
echo "Xray 安装失败,请检查网络连接或安装脚本。"
exit 1
fi
echo "Xray 安装完成。"
else
echo "Xray 已安装。"
fi
}
get_public_ipv4() {
ip -4 addr show | awk '/inet / {ip = $2; sub(/\/.*/, "", ip); if (ip !~ /^127\./ && ip !~ /^10\./ && ip !~ /^192\.168\./ && ip !~ /^169\.254\./ && ip !~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./) print ip}'
}
# 确保 vmess.txt 文件存在,如果不存在则创建
ensure_vmess_file() {
if [ ! -f /home/vmess.txt ]; then
echo "vmess.txt 文件不存在,正在创建..."
touch /home/vmess.txt
fi
}
print_node_links() {
local port=$1
local id=$2
local outbound_ip=$3
local link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$outbound_ip\",\"add\":\"$outbound_ip\",\"port\":\"$port\",\"id\":\"$id\",\"aid\":\"0\",\"net\":\"ws\",\"type\":\"none\",\"host\":\"\",\"path\":\"/ws\",\"tls\":\"none\"}" | base64 | tr -d '\n')"
echo -e "端口: $port, 节点链接: \033[32m$link\033[0m"
# 将 vmess 链接保存到 /home/vmess.txt 文件中,每行一个链接
echo "$link" >> /home/vmess.txt
}
configure_xray() {
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到任何公网 IPv4 地址,退出..."
exit 1
fi
echo "找到的公网 IPv4 地址: ${public_ips[@]}"
config_file="/usr/local/etc/xray/config.json"
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"routing": {
"rules": []
}
}
EOF
# 配置 inbounds 和 outbounds
port=10001
for ip in "${public_ips[@]}"; do
echo "正在配置 IP: $ip 端口: $port"
id=$(uuidgen)
jq --argjson port "$port" --arg ip "$ip" --arg id "$id" '.inbounds += [{
"port": $port,
"protocol": "vmess",
"settings": {
"clients": [{
"id": $id,
"alterId": 0
}]
},
"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "/ws"
}
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
print_node_links "$port" "$id" "$ip"
port=$((port + 1))
done
echo "Xray 配置完成。"
}
restart_xray() {
echo "正在重启 Xray 服务..."
if ! systemctl restart xray; then
echo "Xray 服务重启失败,请检查配置文件。"
exit 1
fi
systemctl enable xray
echo "Xray 服务已重启。"
}
main() {
ensure_vmess_file
install_jq
install_xray
configure_xray
restart_xray
echo "部署完成,所有节点信息已保存在 /home/vmess.txt"
}
main

564
proxy/l2tp.sh Normal file
View File

@@ -0,0 +1,564 @@
#!/usr/bin/env bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#=======================================================================#
# 系统支持: Debian 10 + / Ubuntu 18.04 + #
# 描述: L2TP VPN 自动安装脚本 #
# 基于Teddysun版本修改 #
#=======================================================================#
cur_dir=`pwd`
rootness(){
if [[ $EUID -ne 0 ]]; then
echo "错误: 此脚本必须以root身份运行!" 1>&2
exit 1
fi
}
tunavailable(){
if [[ ! -e /dev/net/tun ]]; then
echo "错误: TUN/TAP 不可用!" 1>&2
exit 1
fi
}
disable_selinux(){
if [ -s /etc/selinux/config ] && grep 'SELINUX=enforcing' /etc/selinux/config; then
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
setenforce 0
fi
}
get_opsy(){
[ -f /etc/os-release ] && awk -F'[= "]' '/PRETTY_NAME/{print $3,$4,$5}' /etc/os-release && return
[ -f /etc/lsb-release ] && awk -F'[="]+' '/DESCRIPTION/{print $2}' /etc/lsb-release && return
}
get_os_info(){
IP=$( ip addr | egrep -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | egrep -v "^192\.168|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-2]\.|^10\.|^127\.|^255\.|^0\." | head -n 1 )
[ -z ${IP} ] && IP=$( wget -qO- -t1 -T2 ipinfo.io/ip )
if [ -z ${IP} ]; then
IP=$( wget -qO- -t1 -T2 ifconfig.me )
fi
local cname=$( awk -F: '/model name/ {name=$2} END {print name}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' )
local cores=$( awk -F: '/model name/ {core++} END {print core}' /proc/cpuinfo )
local freq=$( awk -F: '/cpu MHz/ {freq=$2} END {print freq}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' )
local tram=$( free -m | awk '/Mem/ {print $2}' )
local swap=$( free -m | awk '/Swap/ {print $2}' )
local up=$( awk '{a=$1/86400;b=($1%86400)/3600;c=($1%3600)/60;d=$1%60} {printf("%d天 %d:%d:%d\n",a,b,c,d)}' /proc/uptime )
local load=$( w | head -1 | awk -F'load average:' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//' )
local opsy=$( get_opsy )
local arch=$( uname -m )
local lbit=$( getconf LONG_BIT )
local host=$( hostname )
local kern=$( uname -r )
echo "########## 系统信息 ##########"
echo
echo "CPU型号 : ${cname}"
echo "CPU核心数 : ${cores}"
echo "CPU频率 : ${freq} MHz"
echo "总内存大小 : ${tram} MB"
echo "总交换分区大小 : ${swap} MB"
echo "系统运行时间 : ${up}"
echo "平均负载 : ${load}"
echo "操作系统 : ${opsy}"
echo "系统架构 : ${arch} (${lbit} Bit)"
echo "内核版本 : ${kern}"
echo "主机名 : ${host}"
echo "IPv4地址 : ${IP}"
echo
echo "##################################"
}
check_sys(){
local checkType=$1
local value=$2
local release=''
local systemPackage=''
if cat /etc/issue | grep -Eqi "debian"; then
release="debian"
systemPackage="apt"
elif cat /etc/issue | grep -Eqi "ubuntu"; then
release="ubuntu"
systemPackage="apt"
elif cat /proc/version | grep -Eqi "debian"; then
release="debian"
systemPackage="apt"
elif cat /proc/version | grep -Eqi "ubuntu"; then
release="ubuntu"
systemPackage="apt"
else
echo "错误: 不支持的系统请使用Debian或Ubuntu系统"
exit 1
fi
if [[ ${checkType} == "sysRelease" ]]; then
if [ "$value" == "$release" ];then
return 0
else
return 1
fi
elif [[ ${checkType} == "packageManager" ]]; then
if [ "$value" == "$systemPackage" ];then
return 0
else
return 1
fi
fi
}
rand(){
index=0
str=""
for i in {a..z}; do arr[index]=${i}; index=`expr ${index} + 1`; done
for i in {A..Z}; do arr[index]=${i}; index=`expr ${index} + 1`; done
for i in {0..9}; do arr[index]=${i}; index=`expr ${index} + 1`; done
for i in {1..10}; do str="$str${arr[$RANDOM%$index]}"; done
echo ${str}
}
is_64bit(){
if [ `getconf WORD_BIT` = '32' ] && [ `getconf LONG_BIT` = '64' ] ; then
return 0
else
return 1
fi
}
versionget(){
if [ -f /etc/os-release ];then
grep -oE "[0-9.]+" /etc/os-release | head -1
else
grep -oE "[0-9.]+" /etc/issue
fi
}
debianversion(){
if check_sys sysRelease debian;then
local version=$( get_opsy )
local code=${1}
local main_ver=$( echo ${version} | sed 's/[^0-9]//g')
if [ "${main_ver}" == "${code}" ];then
return 0
else
return 1
fi
else
return 1
fi
}
version_check(){
if check_sys packageManager apt; then
if debianversion 5; then
echo "错误: Debian 5 不支持请重新安装OS并重试。"
exit 1
fi
fi
}
get_char(){
SAVEDSTTY=`stty -g`
stty -echo
stty cbreak
dd if=/dev/tty bs=1 count=1 2> /dev/null
stty -raw
stty echo
stty $SAVEDSTTY
}
preinstall_l2tp(){
echo
if [ -d "/proc/vz" ]; then
echo -e "\033[41;37m 警告: \033[0m 您的VPS基于OpenVZ内核可能不支持IPSec。"
echo "是否继续安装? (y/n)"
read -p "(默认: n)" agree
[ -z ${agree} ] && agree="n"
if [ "${agree}" == "n" ]; then
echo
echo "L2TP安装已取消。"
echo
exit 0
fi
fi
echo
echo "请输入IP范围:"
read -p "(默认范围: 192.168.18):" iprange
[ -z ${iprange} ] && iprange="192.168.18"
echo "请输入PSK密钥:"
read -p "(默认PSK: admin123@l2tp):" mypsk
[ -z ${mypsk} ] && mypsk="admin123@l2tp"
echo "请输入用户名:"
read -p "(默认用户名: admin123):" username
[ -z ${username} ] && username="admin123"
password=`rand`
echo "请输入 ${username} 的密码:"
read -p "(默认密码: ${password}):" tmppassword
[ ! -z ${tmppassword} ] && password=${tmppassword}
echo
echo "服务器IP: ${IP}"
echo "服务器本地IP: ${iprange}.1"
echo "客户端远程IP范围: ${iprange}.2-${iprange}.254"
echo "PSK密钥: ${mypsk}"
echo
echo "按任意键开始安装...或按Ctrl+C取消。"
char=`get_char`
}
# 安装依赖
install_l2tp(){
mknod /dev/random c 1 9
apt -y update
apt -yq install curl wget ppp xl2tpd libreswan
config_install
}
config_install(){
cat > /etc/ipsec.conf<<EOF
version 2.0
config setup
protostack=netkey
nhelpers=0
uniqueids=no
interfaces=%defaultroute
virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:!${iprange}.0/24
conn l2tp-psk
rightsubnet=vhost:%priv
also=l2tp-psk-nonat
conn l2tp-psk-nonat
authby=secret
pfs=no
auto=add
keyingtries=3
rekey=no
ikelifetime=8h
keylife=1h
type=transport
left=%defaultroute
leftid=${IP}
leftprotoport=17/1701
right=%any
rightprotoport=17/%any
dpddelay=40
dpdtimeout=130
dpdaction=clear
sha2-truncbug=yes
EOF
cat > /etc/ipsec.secrets<<EOF
%any %any : PSK "${mypsk}"
EOF
cat > /etc/xl2tpd/xl2tpd.conf<<EOF
[global]
port = 1701
[lns default]
ip range = ${iprange}.2-${iprange}.254
local ip = ${iprange}.1
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tpd
ppp debug = yes
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
EOF
cat > /etc/ppp/options.xl2tpd<<EOF
ipcp-accept-local
ipcp-accept-remote
require-mschap-v2
ms-dns 8.8.8.8
ms-dns 8.8.4.4
noccp
auth
hide-password
idle 1800
mtu 1410
mru 1410
nodefaultroute
debug
proxyarp
connect-delay 5000
EOF
rm -f /etc/ppp/chap-secrets
cat > /etc/ppp/chap-secrets<<EOF
# Secrets for authentication using CHAP
# client server secret IP addresses
${username} l2tpd ${password} *
EOF
cp -pf /etc/sysctl.conf /etc/sysctl.conf.bak
sed -i 's/net.ipv4.ip_forward = 0/net.ipv4.ip_forward = 1/g' /etc/sysctl.conf
for each in `ls /proc/sys/net/ipv4/conf/`; do
echo "net.ipv4.conf.${each}.accept_source_route=0" >> /etc/sysctl.conf
echo "net.ipv4.conf.${each}.accept_redirects=0" >> /etc/sysctl.conf
echo "net.ipv4.conf.${each}.send_redirects=0" >> /etc/sysctl.conf
echo "net.ipv4.conf.${each}.rp_filter=0" >> /etc/sysctl.conf
done
sysctl -p
[ -f /etc/iptables.rules ] && cp -pf /etc/iptables.rules /etc/iptables.rules.old.`date +%Y%m%d`
# 确保IP变量已正确获取
if [ -z "${IP}" ]; then
IP=$(wget -qO- ipinfo.io/ip)
fi
cat > /etc/iptables.rules <<EOF
# Added by L2TP VPN script
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp --dport 22 -j ACCEPT
-A INPUT -p udp -m multiport --dports 500,4500,1701 -j ACCEPT
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s ${iprange}.0/24 -j ACCEPT
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s ${iprange}.0/24 -j SNAT --to-source ${IP}
COMMIT
EOF
# 创建rc.local文件如果不存在
if [ ! -f /etc/rc.local ]; then
cat > /etc/rc.local <<EOF
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
echo 1 > /proc/sys/net/ipv4/ip_forward
/usr/sbin/service ipsec start
/usr/sbin/service xl2tpd start
/sbin/iptables-restore < /etc/iptables.rules
exit 0
EOF
chmod +x /etc/rc.local
else
# 如果已存在rc.local则追加内容
sed -i '/^exit 0/d' /etc/rc.local
cat >> /etc/rc.local <<EOF
# Added by L2TP VPN script
echo 1 > /proc/sys/net/ipv4/ip_forward
/usr/sbin/service ipsec start
/usr/sbin/service xl2tpd start
/sbin/iptables-restore < /etc/iptables.rules
exit 0
EOF
fi
cat > /etc/network/if-up.d/iptables <<EOF
#!/bin/sh
/sbin/iptables-restore < /etc/iptables.rules
EOF
chmod +x /etc/network/if-up.d/iptables
if [ ! -f /etc/ipsec.d/cert9.db ]; then
echo > /var/tmp/libreswan-nss-pwd
certutil -N -f /var/tmp/libreswan-nss-pwd -d /etc/ipsec.d
rm -f /var/tmp/libreswan-nss-pwd
fi
update-rc.d -f xl2tpd defaults
# 启用并启动服务
systemctl enable ipsec
systemctl enable xl2tpd
echo 1 > /proc/sys/net/ipv4/ip_forward
/sbin/iptables-restore < /etc/iptables.rules
systemctl restart ipsec
systemctl restart xl2tpd
}
finally(){
cp -f ${cur_dir}/l2tp.sh /usr/bin/l2tp 2>/dev/null || true
echo "请稍候..."
sleep 3
ipsec verify
echo
echo "###############################################################"
echo "# L2TP 安装脚本 #"
echo "###############################################################"
echo
echo "默认用户名和密码如下:"
echo
echo "服务器IP: ${IP}"
echo "PSK密钥 : ${mypsk}"
echo "用户名 : ${username}"
echo "密码 : ${password}"
echo
echo "如果您想修改用户设置,请使用以下命令:"
echo "-a (添加用户)"
echo "-d (删除用户)"
echo "-l (列出所有用户)"
echo "-m (修改用户密码)"
echo
}
l2tp(){
clear
echo
echo "###############################################################"
echo "# L2TP 安装脚本 #"
echo "###############################################################"
echo
rootness
tunavailable
disable_selinux
version_check
get_os_info
preinstall_l2tp
install_l2tp
finally
}
list_users(){
if [ ! -f /etc/ppp/chap-secrets ];then
echo "错误: /etc/ppp/chap-secrets 文件未找到."
exit 1
fi
local line="+-------------------------------------------+\n"
local string=%20s
printf "${line}|${string} |${string} |\n${line}" 用户名 密码
grep -v "^#" /etc/ppp/chap-secrets | awk '{printf "|'${string}' |'${string}' |\n", $1,$3}'
printf ${line}
}
add_user(){
while :
do
read -p "请输入用户名:" user
if [ -z ${user} ]; then
echo "用户名不能为空"
else
grep -w "${user}" /etc/ppp/chap-secrets > /dev/null 2>&1
if [ $? -eq 0 ];then
echo "用户名 (${user}) 已存在。请重新输入用户名。"
else
break
fi
fi
done
pass=`rand`
echo "请输入 ${user} 的密码:"
read -p "(默认密码: ${pass}):" tmppass
[ ! -z ${tmppass} ] && pass=${tmppass}
echo "${user} l2tpd ${pass} *" >> /etc/ppp/chap-secrets
echo "用户 (${user}) 添加完成。"
}
del_user(){
while :
do
read -p "请输入要删除的用户名:" user
if [ -z ${user} ]; then
echo "用户名不能为空"
else
grep -w "${user}" /etc/ppp/chap-secrets >/dev/null 2>&1
if [ $? -eq 0 ];then
break
else
echo "用户名 (${user}) 不存在。请重新输入用户名。"
fi
fi
done
sed -i "/^\<${user}\>/d" /etc/ppp/chap-secrets
echo "用户 (${user}) 删除完成。"
}
mod_user(){
while :
do
read -p "请输入要修改密码的用户名:" user
if [ -z ${user} ]; then
echo "用户名不能为空"
else
grep -w "${user}" /etc/ppp/chap-secrets >/dev/null 2>&1
if [ $? -eq 0 ];then
break
else
echo "用户名 (${user}) 不存在。请重新输入用户名。"
fi
fi
done
pass=`rand`
echo "请输入 ${user} 的新密码:"
read -p "(默认密码: ${pass}):" tmppass
[ ! -z ${tmppass} ] && pass=${tmppass}
sed -i "/^\<${user}\>/d" /etc/ppp/chap-secrets
echo "${user} l2tpd ${pass} *" >> /etc/ppp/chap-secrets
echo "用户 ${user} 的密码已更改。"
}
# 主程序
action=$1
if [ -z ${action} ] && [ "`basename $0`" != "l2tp" ]; then
action=install
fi
case ${action} in
install)
l2tp 2>&1 | tee ${cur_dir}/l2tp.log
;;
-l|--list)
list_users
;;
-a|--add)
add_user
;;
-d|--del)
del_user
;;
-m|--mod)
mod_user
;;
-h|--help)
echo "用法: -l,--list 列出所有用户"
echo " -a,--add 添加用户"
echo " -d,--del 删除用户"
echo " -m,--mod 修改用户密码"
echo " -h,--help 打印此帮助信息"
;;
*)
echo "用法: [-l,--查看用户|-a,--添加用户|-d,--删除用户|-m,--修改密码|-h,--帮助信息]" && exit
;;
esac

569
proxy/singbox-zhanqun.sh Normal file
View File

@@ -0,0 +1,569 @@
#!/bin/bash
# sing-box站群多IP源进源出节点脚本 支持sk5和vless+tcp协议
# 生成随机8位数的用户名和密码
generate_random_string() {
local length=8
tr -dc A-Za-z0-9 </dev/urandom | head -c $length
}
# 生成随机UUID
generate_uuid() {
cat /proc/sys/kernel/random/uuid
}
# 获取当前北京时间,精确到秒
get_beijing_time() {
TZ=Asia/Shanghai date +"%Y年%m月%d日%H点%M分%S秒"
}
# 全局变量,保存当前操作使用的输出文件名
OUTPUT_FILE=""
# 初始化输出文件名
init_output_file() {
OUTPUT_FILE="/home/$(get_beijing_time).txt"
# 确保文件存在并清空内容
touch "$OUTPUT_FILE"
> "$OUTPUT_FILE"
echo "将使用输出文件: $OUTPUT_FILE"
}
install_jq() {
if ! command -v jq &> /dev/null; then
echo "jq 未安装,正在安装 jq..."
if [[ -f /etc/debian_version ]]; then
apt update && apt install -yq jq
elif [[ -f /etc/redhat-release ]]; then
yum install -y epel-release jq
else
echo "无法确定系统发行版,请手动安装 jq。"
exit 1
fi
else
echo "jq 已安装。"
fi
}
install_xray() {
if ! command -v sing-box &> /dev/null; then
echo "sing-box 未安装,正在安装 sing-box..."
VERSION="1.11.5"
curl -Lo sing-box.deb "https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_amd64.deb"
if ! dpkg -i sing-box.deb; then
echo "sing-box 安装失败请检查dpkg输出。"
rm -f sing-box.deb
exit 1
fi
rm -f sing-box.deb
echo "sing-box 安装完成。"
else
echo "sing-box 已安装。"
fi
}
# 检查是否已有节点配置
check_existing_nodes() {
local config_file="/etc/sing-box/config.json"
# 如果配置文件不存在,则没有节点配置
if [ ! -f "$config_file" ]; then
return 1
fi
# 检查 route.rules 数组数量是否大于等于2
local rules_count=$(jq '.route.rules | length' "$config_file" 2>/dev/null)
# 如果jq命令失败或rules为空或小于2则认为没有足够节点配置
if [ -z "$rules_count" ] || [ "$rules_count" -lt 2 ]; then
return 1
fi
# 有2个及以上路由规则视为已有节点配置
return 0
}
get_public_ipv4() {
ip -4 addr show | awk '/inet / {ip = $2; sub(/\/.*/, "", ip); if (ip !~ /^127\./ && ip !~ /^10\./ && ip !~ /^192\.168\./ && ip !~ /^169\.254\./ && ip !~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./) print ip}'
}
# 获取已配置的IP列表
get_configured_ips() {
local config_file="/etc/sing-box/config.json"
if [ ! -f "$config_file" ]; then
echo ""
return
fi
jq -r '.outbounds[] | .inet4_bind_address' "$config_file" | sort | uniq
}
print_node_info() {
local ip=$1
local socks_port=$2
local vless_port=$3
local username=$4
local password=$5
local uuid=$6
echo -e " IP: \033[32m$ip\033[0m"
echo -e " Socks5 端口: \033[32m$socks_port\033[0m 用户名: \033[32m$username\033[0m 密码: \033[32m$password\033[0m"
echo -e " VLESS 端口: \033[32m$vless_port\033[0m UUID: \033[32m$uuid\033[0m"
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 保存节点信息到文件
echo "$ip:$socks_port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo "节点信息已保存到 $OUTPUT_FILE"
}
# 导出所有节点配置
export_all_nodes() {
local config_file="/etc/sing-box/config.json"
if [ ! -f "$config_file" ]; then
echo "Xray配置文件不存在无法导出节点信息。"
return 1
fi
# 初始化输出文件
init_output_file
echo "正在导出所有节点配置到 $OUTPUT_FILE..."
# 获取所有Socks5节点
local socks_nodes=$(jq -r '.inbounds[] | select(.type == "socks") | {port: .listen_port, tag: .tag}' "$config_file")
if [ -z "$socks_nodes" ]; then
echo "未找到任何节点配置。"
return 1
fi
# 遍历所有Socks5节点查找对应的信息
for row in $(jq -r '.inbounds[] | select(.type == "socks") | @base64' "$config_file"); do
inbound=$(echo $row | base64 --decode)
local port=$(echo "$inbound" | jq -r '.listen_port')
local tag=$(echo "$inbound" | jq -r '.tag')
# 查找对应的outbound以获取IP
local outbound_tag="out-$port"
local ip=$(jq -r --arg tag "$outbound_tag" '.outbounds[] | select(.tag == $tag) | .inet4_bind_address' "$config_file")
# 获取Socks5的用户名和密码
local username=$(echo "$inbound" | jq -r '.users[0].username')
local password=$(echo "$inbound" | jq -r '.users[0].password')
# 查找相应的VLESS节点
local vless_port=$((port + 1))
local vless_tag="in-$vless_port"
# 获取VLESS的UUID
local uuid=$(jq -r --arg tag "$vless_tag" '.inbounds[] | select(.tag == $tag) | .users[0].uuid' "$config_file")
if [ -n "$uuid" ]; then
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 输出节点信息
echo "$ip:$port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo -e "已导出节点: \033[32m$ip\033[0m Socks5端口:\033[32m$port\033[0m VLESS端口:\033[32m$vless_port\033[0m"
fi
done
echo "所有节点导出完成,信息已保存到 $OUTPUT_FILE"
return 0
}
# 查找配置中未使用的端口号
find_next_unused_port() {
local config_file="/etc/sing-box/config.json"
if [ ! -f "$config_file" ]; then
echo "10001" # 如果配置文件不存在从10001开始
return
fi
# 获取所有已使用的端口
local used_ports=$(jq -r '.inbounds[].port' "$config_file" | sort -n)
if [ -z "$used_ports" ]; then
echo "10001" # 如果没有已使用的端口从10001开始
return
fi
# 获取最大的端口号并加1
local max_port=$(echo "$used_ports" | tail -1)
local next_port=$((max_port + 1))
# 确保端口号是奇数用于socks5
if [ $((next_port % 2)) -eq 0 ]; then
next_port=$((next_port + 1))
fi
echo "$next_port"
}
# 添加新节点只添加未配置的IP
add_new_nodes() {
# 获取当前系统的所有公网IP
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到公网IP地址退出..."
return 1
fi
# 获取已经配置的IP列表
configured_ips=($(get_configured_ips))
# 初始化新IP列表
new_ips=()
# 比对IP找出未配置的IP
for ip in "${public_ips[@]}"; do
is_configured=false
for configured_ip in "${configured_ips[@]}"; do
if [[ "$ip" == "$configured_ip" ]]; then
is_configured=true
break
fi
done
if ! $is_configured; then
new_ips+=("$ip")
fi
done
# 检查是否有新的IP需要配置
if [[ ${#new_ips[@]} -eq 0 ]]; then
echo "所有IP都已配置无需添加新节点。"
return 0
fi
echo "发现 ${#new_ips[@]} 个未配置的IP: ${new_ips[@]}"
# 初始化输出文件
init_output_file
# 获取配置文件路径
config_file="/etc/sing-box/config.json"
# 如果配置文件不存在,创建基础配置
if [ ! -f "$config_file" ]; then
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"route": {
"rules": []
}
}
EOF
fi
# 获取下一个可用的端口
socks_port=$(find_next_unused_port)
echo "将从端口 $socks_port 开始配置新节点"
# 为每个新IP配置节点
for ip in "${new_ips[@]}"; do
echo "正在配置 IP: $ip"
# Socks5配置 (奇数端口)
username=$(generate_random_string)
password=$(generate_random_string)
# VLESS配置 (偶数端口)
vless_port=$((socks_port + 1))
uuid=$(generate_uuid)
# 添加Socks5配置
jq --argjson port "$socks_port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"type": "socks",
"tag": ("in-\($port)"),
"listen": "0.0.0.0",
"listen_port": $port,
"users": [{
"username": $username,
"password": $password
}]
}] | .outbounds += [{
"type": "direct",
"tag": ("out-\($port)"),
"inet4_bind_address": $ip
}] | .route.rules += [{
"inbound": ["in-\($port)"],
"outbound": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 添加VLESS配置
jq --argjson port "$vless_port" --arg ip "$ip" --arg uuid "$uuid" '.inbounds += [{
"type": "vless",
"tag": ("in-\($port)"),
"listen": "0.0.0.0",
"listen_port": $port,
"users": [{
"uuid": $uuid
}]
}] | .outbounds += [{
"type": "direct",
"tag": ("out-\($port)"),
"inet4_bind_address": $ip
}] | .route.rules += [{
"inbound": ["in-\($port)"],
"outbound": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 输出节点信息
print_node_info "$ip" "$socks_port" "$vless_port" "$username" "$password" "$uuid"
# 增加端口号为下一个IP准备
socks_port=$((vless_port + 1))
done
echo "新节点配置完成,共添加了 ${#new_ips[@]} 个节点"
return 0
}
configure_xray() {
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到额外IP地址退出..."
exit 1
fi
echo "找到的公网 IPv4 地址: ${public_ips[@]}"
# 初始化输出文件
init_output_file
config_file="/etc/sing-box/config.json"
# 创建基础配置文件
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"route": {
"rules": []
}
}
EOF
# 初始端口
socks_port=10001
# 配置 inbounds 和 outbounds
for ip in "${public_ips[@]}"; do
echo "正在配置 IP: $ip"
# Socks5配置 (奇数端口)
username=$(generate_random_string)
password=$(generate_random_string)
# VLESS配置 (偶数端口)
vless_port=$((socks_port + 1))
uuid=$(generate_uuid)
# 添加Socks5配置
jq --argjson port "$socks_port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"type": "socks",
"tag": ("in-\($port)"),
"listen": "0.0.0.0",
"listen_port": $port,
"users": [{
"username": $username,
"password": $password
}]
}] | .outbounds += [{
"type": "direct",
"tag": ("out-\($port)"),
"inet4_bind_address": $ip
}] | .route.rules += [{
"inbound": ["in-\($port)"],
"outbound": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 添加VLESS配置
jq --argjson port "$vless_port" --arg ip "$ip" --arg uuid "$uuid" '.inbounds += [{
"type": "vless",
"tag": ("in-\($port)"),
"listen": "0.0.0.0",
"listen_port": $port,
"users": [{
"uuid": $uuid
}]
}] | .outbounds += [{
"type": "direct",
"tag": ("out-\($port)"),
"inet4_bind_address": $ip
}] | .route.rules += [{
"inbound": ["in-\($port)"],
"outbound": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 输出节点信息
print_node_info "$ip" "$socks_port" "$vless_port" "$username" "$password" "$uuid"
# 增加端口号为下一个IP准备
socks_port=$((vless_port + 1))
done
echo "sing-box 配置完成。"
}
modify_by_ip() {
local modify_file="/home/xiugai.txt"
if [ ! -f "$modify_file" ]; then
echo "修改文件 $modify_file 不存在,跳过修改操作。"
return
fi
echo "检测到修改文件开始根据IP修改节点..."
# 读取当前配置
local config_file="/etc/sing-box/config.json"
if [ ! -f "$config_file" ]; then
echo "Xray配置文件不存在请先配置Xray。"
exit 1
fi
# 初始化输出文件
init_output_file
local modify_success=false
# 逐行读取修改文件中的IP
while IFS= read -r ip || [[ -n "$ip" ]]; do
# 跳过空行和注释行
[[ -z "$ip" || "$ip" =~ ^# ]] && continue
echo "正在处理IP: $ip"
# 查找此IP对应的出站配置
local ip_exists=$(jq --arg ip "$ip" '.outbounds[] | select(.inet4_bind_address == $ip) | .tag' "$config_file")
if [[ -z "$ip_exists" ]]; then
echo "错误: IP $ip 在当前配置中未找到,停止脚本执行。"
exit 1
fi
# 找到对应的入站端口和标签
local outbound_tags=$(jq -r --arg ip "$ip" '.outbounds[] | select(.inet4_bind_address == $ip) | .tag' "$config_file")
for outbound_tag in $outbound_tags; do
local port=$(echo $outbound_tag | cut -d'-' -f2)
local inbound_tag="in-$port"
# 检查协议类型
local type=$(jq -r --arg tag "$inbound_tag" '.inbounds[] | select(.tag == $tag) | .type' "$config_file")
if [[ "$type" == "socks" ]]; then
# 更新socks协议的用户名和密码
local username=$(generate_random_string)
local password=$(generate_random_string)
jq --arg tag "$inbound_tag" --arg username "$username" --arg password "$password" '
.inbounds[] |= if .tag == $tag then
.users[0].username = $username |
.users[0].password = $password
else . end' "$config_file" > temp.json && mv temp.json "$config_file"
# 找到对应的vless端口
local vless_port=$((port + 1))
local vless_tag="in-$vless_port"
# 确认vless端口存在
local vless_exists=$(jq --arg tag "$vless_tag" '.inbounds[] | select(.tag == $tag) | .tag' "$config_file")
# 如果存在更新vless协议的UUID
if [[ -n "$vless_exists" ]]; then
local uuid=$(generate_uuid)
jq --arg tag "$vless_tag" --arg uuid "$uuid" '
.inbounds[] |= if .tag == $tag then
.users[0].uuid = $uuid
else . end' "$config_file" > temp.json && mv temp.json "$config_file"
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 保存修改后的节点信息
echo "$ip:$port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo "已修改 IP: $ip 的Socks5(端口:$port)和VLESS(端口:$vless_port)配置"
modify_success=true
else
echo "警告: 未找到IP $ip 对应的VLESS配置"
fi
fi
done
done < "$modify_file"
if $modify_success; then
echo "节点修改完成,信息已保存到 $OUTPUT_FILE"
else
echo "未进行任何修改"
fi
}
restart_xray() {
echo "正在重启 sing-box 服务..."
if ! systemctl restart sing-box; then
echo "sing-box 服务重启失败,请检查配置文件。"
exit 1
fi
systemctl enable sing-box
echo "sing-box 服务已重启。"
}
# 显示交互式菜单
show_menu() {
echo -e "\n\033[36m==== 站群多IP节点管理菜单 ====\033[0m"
echo -e "\033[33m1. 部署节点(首次部署)\033[0m"
echo -e "\033[33m2. 修改节点\033[0m"
echo -e "\033[33m3. 导出所有节点\033[0m"
echo -e "\033[33m4. 新增节点(自动添加未配置的IP)\033[0m"
echo -e "\033[33m0. 退出\033[0m"
echo -e "\033[36m==========================\033[0m"
read -p "请输入选项 [0-4]: " choice
case $choice in
1)
if check_existing_nodes; then
echo -e "\033[31m警告: 检测到已有节点配置!\033[0m"
echo -e "\033[31m选择此选项将会清空所有现有节点并重新部署所有IP的节点\033[0m"
echo -e "\033[31m如果您只想添加新的IP节点请使用选项4\033[0m"
read -p "是否确认清空所有节点并重新部署? (y/n): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "已取消操作"
show_menu
return
fi
fi
configure_xray
restart_xray
echo "节点部署完成"
;;
2)
echo "请确保 /home/xiugai.txt 文件中包含需要修改的IP地址列表每行一个IP"
read -p "是否继续修改? (y/n): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
modify_by_ip
restart_xray
echo "节点修改完成"
fi
;;
3)
export_all_nodes
;;
4)
add_new_nodes
if [ $? -eq 0 ]; then
restart_xray
echo "新节点添加完成"
fi
;;
0)
echo "退出程序"
exit 0
;;
*)
echo "无效选项,请重新选择"
show_menu
;;
esac
}
# 主函数
main() {
# 检查是否已有节点配置
if check_existing_nodes; then
echo "检测到已有节点配置,跳过依赖安装..."
show_menu
else
echo "未检测到节点配置,开始安装必要依赖..."
install_jq
install_xray
show_menu
fi
}
main

270
proxy/vmess-sk5.sh Normal file
View File

@@ -0,0 +1,270 @@
#!/bin/bash
red='\e[31m'
yellow='\e[33m'
green='\e[32m'
none='\e[0m'
config_file="/usr/local/etc/xray/config.json"
default_config='
{
"inbounds": [
{
"listen": "127.0.0.1",
"port": 9999,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "sky22333"
}
]
},
"streamSettings": {
"network": "ws",
"security": "none",
"wsSettings": {
"path": "/sky22333"
}
},
"tag": "inbound0"
}
],
"outbounds": [
{
"protocol": "socks",
"settings": {
"servers": [
{
"address": "127.0.0.2",
"port": 2222,
"users": [
{
"user": "admin123",
"pass": "admin333"
}
]
}
]
},
"tag": "outbound0"
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["inbound0"],
"outboundTag": "outbound0"
}
]
}
}
'
# 检查并安装curl
check_and_install_curl() {
if ! type curl &>/dev/null; then
echo -e "${yellow}正在安装curl...${none}"
apt update && apt install -yq curl
fi
}
# 检查并安装jq
check_and_install_jq() {
if ! type jq &>/dev/null; then
echo -e "${yellow}正在安装jq...${none}"
apt update && apt install -yq jq
fi
}
# 检查并安装uuid-runtime
check_and_install_uuid_runtime() {
if ! type uuidgen &>/dev/null; then
echo -e "${yellow}正在安装 uuid-runtime...${none}"
apt update && apt install -yq uuid-runtime
fi
}
# 检查并安装xray
check_and_install_xray() {
if ! type xray &>/dev/null; then
echo -e "${yellow}正在安装 xray...${none}"
bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.4
fi
}
# 检查是否已存在入站配置
check_existing_inbound_config() {
if grep -q '"tag":' "$config_file"; then
return 0 # 已存在入站配置
else
return 1 # 不存在入站配置
fi
}
# 创建默认配置文件
create_default_config() {
if ! check_existing_inbound_config; then
echo "$default_config" > "$config_file"
echo -e "${green}已创建默认配置文件。${none}"
else
echo -e "${yellow}入站配置已存在,跳过创建默认配置文件。${none}"
fi
}
# 获取本机公网 IP
get_local_ip() {
local ip=$(curl -s http://ipinfo.io/ip)
if [[ $ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$ip"
else
echo "无法自动获取公网IP地址请手动输入。"
read -p "请输入您的公网IP地址: " manual_ip
if [[ $manual_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$manual_ip"
else
echo "输入的IP地址格式不正确请重新运行脚本并输入有效的公网IP地址。"
exit 1
fi
fi
}
# 显示所有入站配置和 Vmess 链接以及对应的出站配置(出战只显示地址、端口、用户名和密码)
show_inbound_configs() {
local local_ip=$(get_local_ip) # 获取本机IP
local config=$(jq '.inbounds | map(select(.port != 9999))' "$config_file")
local outbounds=$(jq '.outbounds' "$config_file")
echo -e "${green}入站节点配置:${none}"
local length=$(jq '. | length' <<< "$config")
for ((i = 0; i < length; i++)); do
local port=$(jq -r ".[$i].port" <<< "$config")
local id=$(jq -r ".[$i].settings.clients[0].id" <<< "$config")
local path=$(jq -r ".[$i].streamSettings.wsSettings.path" <<< "$config")
# 将节点地址设置为本机IP
local node_address="$local_ip"
local vmess_link="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"节点$(($i + 1))\",\"add\":\"$node_address\",\"port\":$port,\"id\":\"$id\",\"aid\":0,\"net\":\"ws\",\"path\":\"$path\",\"type\":\"none\"}" | base64 -w 0)"
echo -e "${yellow}节点: $(($i + 1))${none} - 端口: ${port}, Vmess 链接: ${vmess_link}"
# 构造出站配置的标签
local outbound_tag="outbound$port"
# 根据构造的标签查找对应的出站配置
local outbound_config=$(jq --arg tag "$outbound_tag" '.[] | select(.tag == $tag) | .settings.servers[] | {address, port, user: .users[0].user, pass: .users[0].pass}' <<< "$outbounds")
if [[ ! -z $outbound_config ]]; then
echo -e "${green}出站配置:${none} 地址: $(jq -r '.address' <<< "$outbound_config"), 端口: $(jq -r '.port' <<< "$outbound_config"), 用户名: $(jq -r '.user' <<< "$outbound_config"), 密码: $(jq -r '.pass' <<< "$outbound_config")"
else
echo -e "${red}未找到对应的出站配置。${none}"
fi
done
}
# 添加新节点
add_new_nodes() {
read -p "请输入要添加的节点数量: " num_nodes
if ! [[ $num_nodes =~ ^[0-9]+$ ]]; then
echo -e "${red}错误!${none} 请输入有效的数量。\n"
return
fi
local max_port=$(jq '[.inbounds[].port] | max // 10000' "$config_file")
local start_port=$((max_port+1))
for ((i=0; i<num_nodes; i++)); do
local new_port=$((start_port+i))
local new_tag="inbound$new_port"
local new_outbound_tag="outbound$new_port"
local new_id=$(uuidgen)
# 用户输入出站代理信息
echo "配置第 $((i+1)) 个sk5出站 (入站端口是$new_port)"
read -p "请输入socks5出站地址, 端口, 用户名, 密码 (按顺序以空格分隔): " outbound_addr outbound_port outbound_user outbound_pass
# 添加入站配置(入站地址设置为 "0.0.0.0"
jq --argjson port "$new_port" --arg id "$new_id" --arg tag "$new_tag" '
.inbounds += [{
listen: "0.0.0.0",
port: $port,
protocol: "vmess",
settings: { clients: [{ id: $id }] },
streamSettings: { network: "ws", security: "none", wsSettings: { path: "/websocket" } },
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加出站配置
jq --arg tag "$new_outbound_tag" --arg addr "$outbound_addr" --argjson port "$outbound_port" --arg user "$outbound_user" --arg pass "$outbound_pass" '
.outbounds += [{
protocol: "socks",
settings: { servers: [{ address: $addr, port: $port, users: [{ user: $user, pass: $pass }] }] },
tag: $tag
}]' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 添加路由规则
jq --arg inTag "$new_tag" --arg outTag "$new_outbound_tag" '
.routing.rules += [{ type: "field", inboundTag: [$inTag], outboundTag: $outTag }]
' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
done
echo -e "${green}已成功添加 $num_nodes 个节点。${none}"
systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 删除特定端口号的节点
delete_node_by_port() {
read -p "请输入要删除的vmess节点端口号: " port_to_delete
if ! [[ $port_to_delete =~ ^[0-9]+$ ]]; then
echo -e "${red}错误!${none} 请输入有效的端口号。\n"
return
fi
local inbound_tag="inbound$port_to_delete"
local outbound_tag="outbound$port_to_delete"
# 删除入站配置
jq --argjson port "$port_to_delete" 'del(.inbounds[] | select(.port == $port))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 删除出站配置
jq --arg tag "$outbound_tag" 'del(.outbounds[] | select(.tag == $tag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
# 删除路由规则
jq --arg inTag "$inbound_tag" --arg outTag "$outbound_tag" 'del(.routing.rules[] | select(.inboundTag[] == $inTag and .outboundTag == $outTag))' "$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
echo -e "${green}已成功删除端口号为 $port_to_delete 的节点。${none}"
systemctl restart xray
echo -e "${green}Xray 服务已重新启动。${none}"
}
# 主菜单
main_menu() {
while true; do
echo -e "\n${green}sky22333-快速批量搭建二级代理脚本-管理菜单:${none}"
echo "1. 查看所有节点"
echo "2. 新增vmess入站sk5出站"
echo "3. 删除节点"
echo "4. 退出"
read -p "请输入选项: " choice
case $choice in
1) show_inbound_configs ;;
2) add_new_nodes ;;
3) delete_node_by_port ;;
4) break ;;
*) echo -e "${red}无效的选项,请重新选择。${none}" ;;
esac
done
}
# 调用主菜单函数
check_and_install_curl
check_and_install_jq
check_and_install_uuid_runtime
check_and_install_xray
create_default_config
get_local_ip
main_menu

86
proxy/vmess.sh Normal file
View File

@@ -0,0 +1,86 @@
#!/bin/bash
# 定义变量
SERVER_IP="192.168.12.23" # 目标服务器的IP地址
SERVER_PASSWORD="password" # 目标服务器的登录密码
NODE_NAME="美国独享" # 用于标识节点的名称
TARGET_DIR="/home/xray.txt"
green='\e[32m'
none='\e[0m'
config_file="/usr/local/etc/xray/config.json"
# 检查并安装依赖项
install_dependencies() {
if ! type jq &>/dev/null || ! type uuidgen &>/dev/null || ! type sshpass &>/dev/null; then
echo -e "${green}正在安装 jq, uuid-runtime 和 sshpass...${none}"
apt update && apt install -yq jq uuid-runtime sshpass
fi
if ! type xray &>/dev/null; then
echo -e "${green}正在安装 xray...${none}"
bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.4
fi
}
# 生成配置和传输逻辑
configure_and_transfer() {
PORT=$(shuf -i 10000-65535 -n 1)
UUID=$(uuidgen)
RANDOM_PATH=$(cat /dev/urandom | tr -dc 'a-z' | head -c 6)
cat > "$config_file" << EOF
{
"inbounds": [
{
"port": $PORT,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "$UUID"
}
]
},
"streamSettings": {
"network": "ws",
"wsSettings": {
"path": "/$RANDOM_PATH"
}
},
"listen": "0.0.0.0"
}
],
"outbounds": [
{
"protocol": "freedom",
"settings": {}
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["inbound0"],
"outboundTag": "direct"
}
]
}
}
EOF
local ip=$(curl -s http://ipinfo.io/ip)
local config="vmess://$(echo -n "{\"v\":\"2\",\"ps\":\"$NODE_NAME\",\"add\":\"$ip\",\"port\":$PORT,\"id\":\"$UUID\",\"aid\":\"0\",\"net\":\"ws\",\"path\":\"/$RANDOM_PATH\",\"type\":\"none\",\"host\":\"\",\"tls\":\"\"}" | base64 -w 0)"
echo -e "${green}Vmess-ws节点链接:${none}"
echo $config
echo $config > /tmp/xray_config.txt
sshpass -p "$SERVER_PASSWORD" ssh -o StrictHostKeyChecking=no root@$SERVER_IP "cat >> $TARGET_DIR" < /tmp/xray_config.txt
}
# 主执行逻辑
install_dependencies
configure_and_transfer
systemctl restart xray
systemctl enable xray
echo -e "${green}Xray 服务已启动。${none}"

182
proxy/xray.sh Normal file
View File

@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# Xray 安装脚本(极简版)
# 固定版本 v1.8.4
# 支持通过 -p 参数设置 GitHub 加速前缀(如 https://gh-proxy.com/
# 仅适用于 Linux 系统,需 root 权限
XRAY_VERSION="v1.8.4"
XRAY_BIN_URL="github.com/XTLS/Xray-core/releases/download/${XRAY_VERSION}/Xray-linux-64.zip"
INSTALL_PATH="/usr/local/bin/xray"
SERVICE_PATH="/etc/systemd/system/xray.service"
CONFIG_PATH="/usr/local/etc/xray/config.json"
# github文件加速前缀
GH_PROXY="https://gh-proxy.com"
show_help() {
echo "用法: $0 [-p <gh-proxy前缀>] [-u|--uninstall]"
echo " -p 可选GitHub 文件加速前缀,如 https://gh-proxy.com"
echo " -u, --uninstall 卸载 Xray 及所有相关文件和服务"
echo "此脚本会自动下载安装 Xray ${XRAY_VERSION},并注册 systemd 服务。"
exit 0
}
# 检查 root 权限
if [[ "$(id -u)" -ne 0 ]]; then
echo "请以 root 用户运行此脚本。"
exit 1
fi
# 解析参数
while [[ $# -gt 0 ]]; do
case "$1" in
-p)
shift
GH_PROXY="$1"
;;
-u|--uninstall)
echo "正在卸载 Xray ..."
systemctl stop xray 2>/dev/null
systemctl disable xray 2>/dev/null
rm -f /usr/local/bin/xray
rm -rf /usr/local/etc/xray
rm -f /etc/systemd/system/xray.service
rm -rf /var/log/xray
systemctl daemon-reload
echo "Xray 及相关文件已卸载。"
exit 0
;;
-h|--help)
show_help
;;
*)
show_help
;;
esac
shift
done
# 自动安装依赖curl 和 unzip
install_pkg() {
PKG_NAME="$1"
if command -v apt >/dev/null 2>&1; then
apt update && apt install -y "$PKG_NAME"
elif command -v dnf >/dev/null 2>&1; then
dnf install -y "$PKG_NAME"
elif command -v yum >/dev/null 2>&1; then
yum install -y "$PKG_NAME"
elif command -v zypper >/dev/null 2>&1; then
zypper install -y "$PKG_NAME"
elif command -v pacman >/dev/null 2>&1; then
pacman -Sy --noconfirm "$PKG_NAME"
elif command -v emerge >/dev/null 2>&1; then
emerge -qv "$PKG_NAME"
else
echo "未检测到支持的包管理器,请手动安装 $PKG_NAME 后重试。"
exit 1
fi
}
for cmd in curl unzip; do
if ! command -v $cmd >/dev/null 2>&1; then
echo "缺少依赖: $cmd,正在尝试自动安装..."
install_pkg "$cmd"
if ! command -v $cmd >/dev/null 2>&1; then
echo "$cmd 安装失败,请手动安装后重试。"
exit 1
fi
fi
done
TMP_DIR="$(mktemp -d)"
ZIP_FILE="$TMP_DIR/xray.zip"
# 拼接加速前缀
if [[ -n "$GH_PROXY" ]]; then
DOWNLOAD_URL="${GH_PROXY%/}/$XRAY_BIN_URL"
else
DOWNLOAD_URL="https://$XRAY_BIN_URL"
fi
echo "下载 Xray: $DOWNLOAD_URL"
curl -L -o "$ZIP_FILE" "$DOWNLOAD_URL"
if [[ $? -ne 0 ]]; then
echo "下载失败,请检查网络或加速前缀。"
rm -rf "$TMP_DIR"
exit 1
fi
unzip -q "$ZIP_FILE" -d "$TMP_DIR"
if [[ $? -ne 0 ]]; then
echo "解压失败。"
rm -rf "$TMP_DIR"
exit 1
fi
install -m 755 "$TMP_DIR/xray" "$INSTALL_PATH"
# 生成 systemd 服务文件(与原脚本一致,自动适配 User 和权限)
INSTALL_USER="root"
if [[ -f '/usr/local/bin/xray' ]]; then
# 若已存在旧服务文件,尝试读取 User 字段
OLD_USER=$(grep '^[ \t]*User[ \t]*=' /etc/systemd/system/xray.service 2>/dev/null | tail -n 1 | awk -F = '{print $2}' | awk '{print $1}')
if [[ -n "$OLD_USER" ]]; then
INSTALL_USER="$OLD_USER"
fi
fi
if ! id "$INSTALL_USER" >/dev/null 2>&1; then
INSTALL_USER="root"
fi
INSTALL_USER_UID=$(id -u "$INSTALL_USER")
# 权限相关字段
temp_CapabilityBoundingSet="CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE"
temp_AmbientCapabilities="AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE"
temp_NoNewPrivileges="NoNewPrivileges=true"
if [[ "$INSTALL_USER_UID" -eq 0 ]]; then
temp_CapabilityBoundingSet="#${temp_CapabilityBoundingSet}"
temp_AmbientCapabilities="#${temp_AmbientCapabilities}"
temp_NoNewPrivileges="#${temp_NoNewPrivileges}"
fi
cat > "$SERVICE_PATH" <<EOF
[Unit]
Description=Xray Service
Documentation=https://github.com/xtls
After=network.target nss-lookup.target
[Service]
User=$INSTALL_USER
${temp_CapabilityBoundingSet}
${temp_AmbientCapabilities}
${temp_NoNewPrivileges}
ExecStart=$INSTALL_PATH run -config $CONFIG_PATH
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=10000
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
EOF
chmod 644 "$SERVICE_PATH"
systemctl daemon-reload
# 生成最简配置文件
mkdir -p "$(dirname $CONFIG_PATH)"
echo '{}' > "$CONFIG_PATH"
# 启动并设置开机自启
systemctl enable xray
systemctl restart xray
sleep 1
if systemctl is-active --quiet xray; then
echo "Xray ${XRAY_VERSION} 安装并启动成功。"
else
echo "Xray 启动失败,请检查日志。"
fi
# 清理临时文件
rm -rf "$TMP_DIR"

629
proxy/zhanqun.sh Normal file
View File

@@ -0,0 +1,629 @@
#!/bin/bash
# 站群多IP源进源出节点脚本 支持sk5和vless+tcp协议
# 生成随机8位数的用户名和密码
generate_random_string() {
local length=8
tr -dc A-Za-z0-9 </dev/urandom | head -c $length
}
# 生成随机UUID
generate_uuid() {
cat /proc/sys/kernel/random/uuid
}
# 获取当前北京时间,精确到秒
get_beijing_time() {
TZ=Asia/Shanghai date +"%Y年%m月%d日%H点%M分%S秒"
}
# 全局变量,保存当前操作使用的输出文件名
OUTPUT_FILE=""
# 初始化输出文件名
init_output_file() {
OUTPUT_FILE="/home/$(get_beijing_time).txt"
# 确保文件存在并清空内容
touch "$OUTPUT_FILE"
> "$OUTPUT_FILE"
echo "将使用输出文件: $OUTPUT_FILE"
}
install_jq() {
if ! command -v jq &> /dev/null; then
echo "jq 未安装,正在安装 jq..."
if [[ -f /etc/debian_version ]]; then
apt update && apt install -yq jq
elif [[ -f /etc/redhat-release ]]; then
yum install -y epel-release jq
else
echo "无法确定系统发行版,请手动安装 jq。"
exit 1
fi
else
echo "jq 已安装。"
fi
}
install_xray() {
if ! command -v xray &> /dev/null; then
echo "Xray 未安装,正在安装 Xray..."
if ! bash <(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh) install --version v1.8.4; then
echo "Xray 安装失败,请检查网络连接或安装脚本。"
exit 1
fi
echo "Xray 安装完成。"
else
echo "Xray 已安装。"
fi
}
# 检查是否已有节点配置
check_existing_nodes() {
local config_file="/usr/local/etc/xray/config.json"
# 如果配置文件不存在,则没有节点配置
if [ ! -f "$config_file" ]; then
return 1
fi
# 检查inbounds数量是否大于0
local inbounds_count=$(jq '.inbounds | length' "$config_file" 2>/dev/null)
# 如果jq命令失败或inbounds为空则认为没有节点配置
if [ -z "$inbounds_count" ] || [ "$inbounds_count" -eq 0 ]; then
return 1
fi
# 有节点配置
return 0
}
get_public_ipv4() {
ip -4 addr show | awk '/inet / {ip = $2; sub(/\/.*/, "", ip); if (ip !~ /^127\./ && ip !~ /^10\./ && ip !~ /^192\.168\./ && ip !~ /^169\.254\./ && ip !~ /^172\.(1[6-9]|2[0-9]|3[0-1])\./) print ip}'
}
# 获取已配置的IP列表
get_configured_ips() {
local config_file="/usr/local/etc/xray/config.json"
if [ ! -f "$config_file" ]; then
echo ""
return
fi
jq -r '.outbounds[] | .sendThrough' "$config_file" | sort | uniq
}
print_node_info() {
local ip=$1
local socks_port=$2
local vless_port=$3
local username=$4
local password=$5
local uuid=$6
echo -e " IP: \033[32m$ip\033[0m"
echo -e " Socks5 端口: \033[32m$socks_port\033[0m 用户名: \033[32m$username\033[0m 密码: \033[32m$password\033[0m"
echo -e " VLESS 端口: \033[32m$vless_port\033[0m UUID: \033[32m$uuid\033[0m"
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 保存节点信息到文件
echo "$ip:$socks_port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo "节点信息已保存到 $OUTPUT_FILE"
}
# 导出所有节点配置
export_all_nodes() {
local config_file="/usr/local/etc/xray/config.json"
if [ ! -f "$config_file" ]; then
echo "Xray配置文件不存在无法导出节点信息。"
return 1
fi
# 初始化输出文件
init_output_file
echo "正在导出所有节点配置到 $OUTPUT_FILE..."
# 获取所有Socks5节点
local socks_nodes=$(jq -r '.inbounds[] | select(.protocol == "socks") | {port: .port, tag: .tag}' "$config_file")
if [ -z "$socks_nodes" ]; then
echo "未找到任何节点配置。"
return 1
fi
# 遍历所有Socks5节点查找对应的信息
for row in $(jq -r '.inbounds[] | select(.protocol == "socks") | @base64' "$config_file"); do
inbound=$(echo $row | base64 --decode)
local port=$(echo "$inbound" | jq -r '.port')
local tag=$(echo "$inbound" | jq -r '.tag')
# 查找对应的outbound以获取IP
local outbound_tag="out-$port"
local ip=$(jq -r --arg tag "$outbound_tag" '.outbounds[] | select(.tag == $tag) | .sendThrough' "$config_file")
# 获取Socks5的用户名和密码
local username=$(echo "$inbound" | jq -r '.settings.accounts[0].user')
local password=$(echo "$inbound" | jq -r '.settings.accounts[0].pass')
# 查找相应的VLESS节点
local vless_port=$((port + 1))
local vless_tag="in-$vless_port"
# 获取VLESS的UUID
local uuid=$(jq -r --arg tag "$vless_tag" '.inbounds[] | select(.tag == $tag) | .settings.clients[0].id' "$config_file")
if [ -n "$uuid" ]; then
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 输出节点信息
echo "$ip:$port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo -e "已导出节点: \033[32m$ip\033[0m Socks5端口:\033[32m$port\033[0m VLESS端口:\033[32m$vless_port\033[0m"
fi
done
echo "所有节点导出完成,信息已保存到 $OUTPUT_FILE"
return 0
}
# 查找配置中未使用的端口号
find_next_unused_port() {
local config_file="/usr/local/etc/xray/config.json"
if [ ! -f "$config_file" ]; then
echo "10001" # 如果配置文件不存在从10001开始
return
fi
# 获取所有已使用的端口
local used_ports=$(jq -r '.inbounds[].port' "$config_file" | sort -n)
if [ -z "$used_ports" ]; then
echo "10001" # 如果没有已使用的端口从10001开始
return
fi
# 获取最大的端口号并加1
local max_port=$(echo "$used_ports" | tail -1)
local next_port=$((max_port + 1))
# 确保端口号是奇数用于socks5
if [ $((next_port % 2)) -eq 0 ]; then
next_port=$((next_port + 1))
fi
echo "$next_port"
}
# 添加新节点只添加未配置的IP
add_new_nodes() {
# 获取当前系统的所有公网IP
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到公网IP地址退出..."
return 1
fi
# 获取已经配置的IP列表
configured_ips=($(get_configured_ips))
# 初始化新IP列表
new_ips=()
# 比对IP找出未配置的IP
for ip in "${public_ips[@]}"; do
is_configured=false
for configured_ip in "${configured_ips[@]}"; do
if [[ "$ip" == "$configured_ip" ]]; then
is_configured=true
break
fi
done
if ! $is_configured; then
new_ips+=("$ip")
fi
done
# 检查是否有新的IP需要配置
if [[ ${#new_ips[@]} -eq 0 ]]; then
echo "所有IP都已配置无需添加新节点。"
return 0
fi
echo "发现 ${#new_ips[@]} 个未配置的IP: ${new_ips[@]}"
# 初始化输出文件
init_output_file
# 获取配置文件路径
config_file="/usr/local/etc/xray/config.json"
# 如果配置文件不存在,创建基础配置
if [ ! -f "$config_file" ]; then
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"routing": {
"rules": []
}
}
EOF
fi
# 获取下一个可用的端口
socks_port=$(find_next_unused_port)
echo "将从端口 $socks_port 开始配置新节点"
# 为每个新IP配置节点
for ip in "${new_ips[@]}"; do
echo "正在配置 IP: $ip"
# Socks5配置 (奇数端口)
username=$(generate_random_string)
password=$(generate_random_string)
# VLESS配置 (偶数端口)
vless_port=$((socks_port + 1))
uuid=$(generate_uuid)
# 添加Socks5配置
jq --argjson port "$socks_port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"port": $port,
"protocol": "socks",
"settings": {
"auth": "password",
"accounts": [{
"user": $username,
"pass": $password
}],
"udp": true,
"ip": "0.0.0.0"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 添加VLESS配置
jq --argjson port "$vless_port" --arg ip "$ip" --arg uuid "$uuid" '.inbounds += [{
"port": $port,
"protocol": "vless",
"settings": {
"clients": [{
"id": $uuid,
"level": 0
}],
"decryption": "none"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 输出节点信息
print_node_info "$ip" "$socks_port" "$vless_port" "$username" "$password" "$uuid"
# 增加端口号为下一个IP准备
socks_port=$((vless_port + 1))
done
echo "新节点配置完成,共添加了 ${#new_ips[@]} 个节点"
return 0
}
configure_xray() {
public_ips=($(get_public_ipv4))
if [[ ${#public_ips[@]} -eq 0 ]]; then
echo "未找到额外IP地址退出..."
exit 1
fi
echo "找到的公网 IPv4 地址: ${public_ips[@]}"
# 初始化输出文件
init_output_file
config_file="/usr/local/etc/xray/config.json"
# 创建基础配置文件
cat > $config_file <<EOF
{
"inbounds": [],
"outbounds": [],
"routing": {
"rules": []
}
}
EOF
# 初始端口
socks_port=10001
# 配置 inbounds 和 outbounds
for ip in "${public_ips[@]}"; do
echo "正在配置 IP: $ip"
# Socks5配置 (奇数端口)
username=$(generate_random_string)
password=$(generate_random_string)
# VLESS配置 (偶数端口)
vless_port=$((socks_port + 1))
uuid=$(generate_uuid)
# 添加Socks5配置
jq --argjson port "$socks_port" --arg ip "$ip" --arg username "$username" --arg password "$password" '.inbounds += [{
"port": $port,
"protocol": "socks",
"settings": {
"auth": "password",
"accounts": [{
"user": $username,
"pass": $password
}],
"udp": true,
"ip": "0.0.0.0"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 添加VLESS配置
jq --argjson port "$vless_port" --arg ip "$ip" --arg uuid "$uuid" '.inbounds += [{
"port": $port,
"protocol": "vless",
"settings": {
"clients": [{
"id": $uuid,
"level": 0
}],
"decryption": "none"
},
"streamSettings": {
"network": "tcp"
},
"tag": ("in-\($port)")
}] | .outbounds += [{
"protocol": "freedom",
"settings": {},
"sendThrough": $ip,
"tag": ("out-\($port)")
}] | .routing.rules += [{
"type": "field",
"inboundTag": ["in-\($port)"],
"outboundTag": "out-\($port)"
}]' "$config_file" > temp.json && mv temp.json "$config_file"
# 输出节点信息
print_node_info "$ip" "$socks_port" "$vless_port" "$username" "$password" "$uuid"
# 增加端口号为下一个IP准备
socks_port=$((vless_port + 1))
done
echo "Xray 配置完成。"
}
modify_by_ip() {
local modify_file="/home/xiugai.txt"
if [ ! -f "$modify_file" ]; then
echo "修改文件 $modify_file 不存在,跳过修改操作。"
return
fi
echo "检测到修改文件开始根据IP修改节点..."
# 读取当前配置
local config_file="/usr/local/etc/xray/config.json"
if [ ! -f "$config_file" ]; then
echo "Xray配置文件不存在请先配置Xray。"
exit 1
fi
# 初始化输出文件
init_output_file
local modify_success=false
# 逐行读取修改文件中的IP
while IFS= read -r ip || [[ -n "$ip" ]]; do
# 跳过空行和注释行
[[ -z "$ip" || "$ip" =~ ^# ]] && continue
echo "正在处理IP: $ip"
# 查找此IP对应的出站配置
local ip_exists=$(jq --arg ip "$ip" '.outbounds[] | select(.sendThrough == $ip) | .tag' "$config_file")
if [[ -z "$ip_exists" ]]; then
echo "错误: IP $ip 在当前配置中未找到,停止脚本执行。"
exit 1
fi
# 找到对应的入站端口和标签
local outbound_tags=$(jq -r --arg ip "$ip" '.outbounds[] | select(.sendThrough == $ip) | .tag' "$config_file")
for outbound_tag in $outbound_tags; do
local port=$(echo $outbound_tag | cut -d'-' -f2)
local inbound_tag="in-$port"
# 检查协议类型
local protocol=$(jq -r --arg tag "$inbound_tag" '.inbounds[] | select(.tag == $tag) | .protocol' "$config_file")
if [[ "$protocol" == "socks" ]]; then
# 更新socks协议的用户名和密码
local username=$(generate_random_string)
local password=$(generate_random_string)
jq --arg tag "$inbound_tag" --arg username "$username" --arg password "$password" '
.inbounds[] |= if .tag == $tag then
.settings.accounts[0].user = $username |
.settings.accounts[0].pass = $password
else . end' "$config_file" > temp.json && mv temp.json "$config_file"
# 找到对应的vless端口
local vless_port=$((port + 1))
local vless_tag="in-$vless_port"
# 确认vless端口存在
local vless_exists=$(jq --arg tag "$vless_tag" '.inbounds[] | select(.tag == $tag) | .tag' "$config_file")
# 如果存在更新vless协议的UUID
if [[ -n "$vless_exists" ]]; then
local uuid=$(generate_uuid)
jq --arg tag "$vless_tag" --arg uuid "$uuid" '
.inbounds[] |= if .tag == $tag then
.settings.clients[0].id = $uuid
else . end' "$config_file" > temp.json && mv temp.json "$config_file"
# 构建vless链接使用IP作为备注
local vless_link="vless://$uuid@$ip:$vless_port?security=none&type=tcp#$ip"
# 保存修改后的节点信息
echo "$ip:$port:$username:$password————$vless_link" >> "$OUTPUT_FILE"
echo "已修改 IP: $ip 的Socks5(端口:$port)和VLESS(端口:$vless_port)配置"
modify_success=true
else
echo "警告: 未找到IP $ip 对应的VLESS配置"
fi
fi
done
done < "$modify_file"
if $modify_success; then
echo "节点修改完成,信息已保存到 $OUTPUT_FILE"
else
echo "未进行任何修改"
fi
}
restart_xray() {
echo "正在重启 Xray 服务..."
if ! systemctl restart xray; then
echo "Xray 服务重启失败,请检查配置文件。"
exit 1
fi
systemctl enable xray
echo "Xray 服务已重启。"
}
# 显示交互式菜单
show_menu() {
echo -e "\n\033[36m==== 站群多IP节点管理菜单 ====\033[0m"
echo -e "\033[33m1. 部署节点(首次部署)\033[0m"
echo -e "\033[33m2. 修改节点\033[0m"
echo -e "\033[33m3. 导出所有节点\033[0m"
echo -e "\033[33m4. 新增节点(自动添加未配置的IP)\033[0m"
echo -e "\033[33m0. 退出\033[0m"
echo -e "\033[36m==========================\033[0m"
read -p "请输入选项 [0-4]: " choice
case $choice in
1)
if check_existing_nodes; then
echo -e "\033[31m警告: 检测到已有节点配置!\033[0m"
echo -e "\033[31m选择此选项将会清空所有现有节点并重新部署所有IP的节点\033[0m"
echo -e "\033[31m如果您只想添加新的IP节点请使用选项4\033[0m"
read -p "是否确认清空所有节点并重新部署? (y/n): " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "已取消操作"
show_menu
return
fi
fi
configure_xray
restart_xray
echo "节点部署完成"
;;
2)
echo "请确保 /home/xiugai.txt 文件中包含需要修改的IP地址列表每行一个IP"
read -p "是否继续修改? (y/n): " confirm
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
modify_by_ip
restart_xray
echo "节点修改完成"
fi
;;
3)
export_all_nodes
;;
4)
add_new_nodes
if [ $? -eq 0 ]; then
restart_xray
echo "新节点添加完成"
fi
;;
0)
echo "退出程序"
exit 0
;;
*)
echo "无效选项,请重新选择"
show_menu
;;
esac
}
# 主函数
main() {
# 检查是否已有节点配置
if check_existing_nodes; then
echo "检测到已有节点配置,跳过依赖安装..."
show_menu
else
echo "未检测到节点配置,开始安装必要依赖..."
install_jq
install_xray
show_menu
fi
}
main

25
web/README.md Normal file
View File

@@ -0,0 +1,25 @@
### 完全卸载删除站点及环境
- 删除站点文件
```
sudo rm -r /var/www/
```
- 停止相关服务
```
sudo systemctl stop caddy apache2 mariadb
```
- 禁用开机自启
```
sudo systemctl disable caddy apache2 mariadb
```
- 卸载软件包
```
sudo apt remove --purge caddy apache2 php* mariadb-server mariadb-client -y
```
- 清理残留配置和依赖
```
sudo apt autoremove -y
```

196
web/acgfaka.sh Normal file
View File

@@ -0,0 +1,196 @@
#!/bin/bash
# 一键部署异次元发卡脚本
# 项目地址https://github.com/sky22333/shell
# 检查是否已经安装 acgfaka
if [ -d "/var/www/html/acgfaka" ]; then
echo -e "\033[32m检测到 acgfaka 已经安装。\033[0m"
echo -e "\033[33m如需重新安装请删除站点文件/var/www/html/acgfaka 并做好相关备份。\033[0m"
exit 0
fi
while true; do
echo -e "\033[33m请输入您的域名(确保已经解析到本机): \033[0m"
read DOMAIN
echo -e "\033[32m您输入的域名是: $DOMAIN\033[0m"
echo -e "\033[33m请确认这个域名是否正确 (yes/no, 默认回车确认): \033[0m"
read CONFIRM
# 如果用户按回车,则默认为确认
if [[ -z "${CONFIRM// }" ]]; then
CONFIRM="yes"
fi
if [[ "${CONFIRM,,}" == "yes" || "${CONFIRM,,}" == "y" ]]; then
echo -e "\033[32m域名确认成功: $DOMAIN\033[0m"
break
else
echo -e "\033[31m请重新输入域名。\033[0m"
fi
done
# 安装必要的软件包
echo -e "\033[32m安装必要的软件包...首次安装可能较慢...请耐心等待。。。\033[0m"
# 创建 sources.list.d 目录(如果不存在的话)
if [ ! -d /etc/apt/sources.list.d/ ]; then
mkdir -p /etc/apt/sources.list.d/
fi
# 添加 Caddy 源和密钥
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update -q
# 检查操作系统
if grep -Eqi "debian" /etc/issue || grep -Eqi "debian" /proc/version; then
OS="debian"
# Debian 系统设置 PHP 仓库
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" >/etc/apt/sources.list.d/php.list
elif grep -Eqi "ubuntu" /etc/issue || grep -Eqi "ubuntu" /proc/version; then
OS="ubuntu"
# Ubuntu 系统设置 PHP PPA
sudo apt update -q
sudo apt install -yq software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
else
echo "不支持的操作系统。本脚本仅支持 Debian 或 Ubuntu。"
exit 1
fi
# 项目地址https://github.com/sky22333/shell
# 更新源列表
sudo apt update -q
# 安装必要的软件包
sudo apt install -yq mariadb-server php8.1 php8.1-mysql php8.1-fpm php8.1-curl php8.1-cgi php8.1-mbstring php8.1-xml php8.1-gd php8.1-xmlrpc php8.1-soap php8.1-intl php8.1-opcache php8.1-zip wget unzip socat curl caddy
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
PHP_INI_FILE="/etc/php/${PHP_VERSION}/fpm/php.ini"
OPCACHE_FILE_CACHE_DIR="/var/cache/opcache"
# 确保缓存目录存在并设置权限
if [ ! -d "$OPCACHE_FILE_CACHE_DIR" ]; then
echo -e "\033[32m创建 OPcache 缓存目录...\033[0m"
sudo mkdir -p "$OPCACHE_FILE_CACHE_DIR"
sudo chown -R www-data:www-data "$OPCACHE_FILE_CACHE_DIR"
fi
# 确保 OPcache 配置存在
if ! grep -q "^opcache.enable=1" "$PHP_INI_FILE"; then
echo -e "\033[32m启用 OPcache 扩展...请稍等...\033[0m"
# 写入 OPcache 配置
sudo tee -a "$PHP_INI_FILE" > /dev/null <<EOL
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=5000
opcache.revalidate_freq=5
opcache.save_comments=1
opcache.file_cache=${OPCACHE_FILE_CACHE_DIR}
opcache.file_cache_size=128
opcache.file_cache_only=0
opcache.file_cache_consistency_checks=1
EOL
fi
# 设置时区
TIMEZONE="Asia/Shanghai"
# 检查并设置 date.timezone
if grep -q "^date.timezone" "$PHP_INI_FILE"; then
# 如果存在 date.timezone 配置但不是 Asia/Shanghai则更新
if ! grep -q "^date.timezone = $TIMEZONE" "$PHP_INI_FILE"; then
echo -e "\033[32m更新 date.timezone 为 $TIMEZONE...\033[0m"
sudo sed -i "s#^date.timezone.*#date.timezone = $TIMEZONE#g" "$PHP_INI_FILE"
fi
else
# 如果 date.timezone 配置不存在,则添加
echo -e "\033[32m设置 date.timezone 为 $TIMEZONE...\033[0m"
sudo tee -a "$PHP_INI_FILE" > /dev/null <<EOL
date.timezone = $TIMEZONE
EOL
fi
# 重启 PHP-FPM 服务
sudo systemctl restart php${PHP_VERSION}-fpm
if systemctl is-active --quiet apache2; then
sudo systemctl stop apache2
sudo systemctl disable apache2
sudo apt remove --purge apache2 -y
else
echo -e "环境检查通过。"
fi
# 启动并启用 Caddy 服务
sudo systemctl start caddy
sudo systemctl enable caddy
sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo mysql_secure_installation <<EOF
y
y
y
y
y
EOF
# 创建acgfaka数据库和用户
DB_NAME="acgfaka"
DB_USER="acguser"
DB_PASSWORD=$(openssl rand -base64 12)
sudo mysql -u root -e "DROP USER IF EXISTS '${DB_USER}'@'localhost';"
sudo mysql -u root -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -u root -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASSWORD}';"
sudo mysql -u root -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';"
sudo mysql -u root -e "FLUSH PRIVILEGES;"
# 下载并配置acgfaka
mkdir -p /var/www/html
cd /var/www/html
wget https://github.com/lizhipay/acg-faka/archive/refs/heads/main.zip
unzip main.zip > /dev/null 2>&1
rm main.zip
mv acg-faka-main acgfaka
sudo chown -R www-data:www-data /var/www/html/acgfaka
sudo find /var/www/html/acgfaka/ -type d -exec chmod 750 {} \;
sudo find /var/www/html/acgfaka/ -type f -exec chmod 640 {} \;
# 配置 Caddyfile
CADDY_CONF="/etc/caddy/Caddyfile"
sudo tee $CADDY_CONF > /dev/null <<EOL
$DOMAIN {
root * /var/www/html/acgfaka
encode zstd gzip
file_server
# PHP 处理
php_fastcgi unix//run/php/php${PHP_VERSION}-fpm.sock
# 伪静态
try_files {path} {path}/ /index.php?s={path}&{query}
}
EOL
# 重新加载 Caddy 配置
sudo systemctl reload caddy
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[32m 数据库信息: \033[0m"
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[33m数据库名: \033[36m${DB_NAME}\033[0m"
echo -e "\033[33m数据库账号: \033[36m${DB_USER}\033[0m"
echo -e "\033[33m数据库密码: \033[36m${DB_PASSWORD}\033[0m"
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[32m站点域名: \033[36m${DOMAIN}\033[0m"
echo -e "\033[32m站点已经部署完成请记录好相关信息。\033[0m"
echo -e "\033[32m============================================================\033[0m"

77
web/mariadb.sh Normal file
View File

@@ -0,0 +1,77 @@
#!/bin/bash
# MariaDB安装脚本
_info() {
echo -e "\e[32m$1\e[0m"
}
_error() {
echo -e "\e[31m错误: $1\e[0m"
exit 1
}
_error_detect() {
if ! eval "$1"; then
_error "执行命令 ($1) 失败"
fi
}
generate_random_password() {
# 生成一个随机密码
tr -dc 'A-Za-z0-9_@%+~#!' </dev/urandom | head -c 16
}
install_mariadb() {
_info "开始安装MariaDB"
_error_detect "apt update"
_error_detect "apt install -yq mariadb-server"
_error_detect "systemctl start mariadb"
_error_detect "systemctl enable mariadb"
_info "MariaDB安装完成"
}
configure_database() {
database_name="mymariadb"
mysql_password=$(generate_random_password)
db_root_password="${mysql_password}"
# 设置 root 密码
_error_detect "mysql -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '${db_root_password}';\""
cat >/tmp/.add_mysql.sql<<EOF
CREATE USER '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
CREATE USER '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'localhost' IDENTIFIED BY '${mysql_password}';
GRANT USAGE ON *.* TO '${database_name}'@'127.0.0.1' IDENTIFIED BY '${mysql_password}';
CREATE DATABASE IF NOT EXISTS ${database_name};
GRANT ALL PRIVILEGES ON ${database_name}.* TO '${database_name}'@'localhost';
GRANT ALL PRIVILEGES ON ${database_name}.* TO '${database_name}'@'127.0.0.1';
FLUSH PRIVILEGES;
EOF
if mysql -uroot -p"${db_root_password}" < /tmp/.add_mysql.sql; then
_info "数据库 ${database_name} 创建成功"
else
_error "数据库 ${database_name} 创建失败"
fi
rm -f /tmp/.add_mysql.sql
}
print_db_info() {
_info "数据库信息:"
_info "数据库名称:${database_name}"
_info "数据库用户:${database_name}"
_info "数据库密码:${mysql_password}"
_info "MariaDB root 密码:${db_root_password}"
_info "请妥善保管以上信息"
}
_info "MariaDB 安装脚本开始执行"
install_mariadb
configure_database
print_db_info
_info "MariaDB安装、配置和数据库创建已完成"

74
web/php.sh Normal file
View File

@@ -0,0 +1,74 @@
#!/bin/bash
# 检查apt源配置目录
if [ ! -d /etc/apt/sources.list.d/ ]; then
mkdir -p /etc/apt/sources.list.d/
fi
# 检查操作系统并设置 PHP 仓库
if grep -Eqi "debian" /etc/issue || grep -Eqi "debian" /proc/version; then
OS="debian"
# Debian 系统设置 PHP 仓库
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" >/etc/apt/sources.list.d/php.list
elif grep -Eqi "ubuntu" /etc/issue || grep -Eqi "ubuntu" /proc/version; then
OS="ubuntu"
# Ubuntu 系统设置 PHP PPA
sudo apt update -q
sudo apt install -yq software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
else
echo "不支持的操作系统。本脚本仅支持 Debian 或 Ubuntu。"
exit 1
fi
# 更新源列表并安装必要的软件包
sudo apt update -q
sudo apt install -yq mariadb-server php8.1 php8.1-mysql php8.1-fpm php8.1-curl php8.1-cgi php8.1-mbstring \
php8.1-xml php8.1-gd php8.1-xmlrpc php8.1-soap php8.1-intl php8.1-opcache php8.1-zip unzip socat
# 获取 PHP 版本信息
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
PHP_INI_FILE="/etc/php/${PHP_VERSION}/fpm/php.ini"
OPCACHE_FILE_CACHE_DIR="/var/cache/opcache"
# 创建 OPcache 缓存目录并设置权限
if [ ! -d "$OPCACHE_FILE_CACHE_DIR" ]; then
echo -e "\033[32m创建 OPcache 缓存目录...\033[0m"
sudo mkdir -p "$OPCACHE_FILE_CACHE_DIR"
sudo chown -R www-data:www-data "$OPCACHE_FILE_CACHE_DIR"
fi
# 配置 OPcache
if ! grep -q "^opcache.enable=1" "$PHP_INI_FILE"; then
echo -e "\033[32m启用 OPcache 扩展...请稍等...\033[0m"
sudo tee -a "$PHP_INI_FILE" > /dev/null <<EOL
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=5000
opcache.revalidate_freq=5
opcache.save_comments=1
opcache.file_cache=${OPCACHE_FILE_CACHE_DIR}
opcache.file_cache_size=128
opcache.file_cache_only=0
opcache.file_cache_consistency_checks=1
EOL
fi
# 设置时区
TIMEZONE="Asia/Shanghai"
# 更新或添加 date.timezone 配置
if grep -q "^date.timezone" "$PHP_INI_FILE"; then
sudo sed -i "s#^date.timezone.*#date.timezone = $TIMEZONE#g" "$PHP_INI_FILE"
else
echo -e "\033[32m设置 date.timezone 为 $TIMEZONE...\033[0m"
echo "date.timezone = $TIMEZONE" | sudo tee -a "$PHP_INI_FILE" > /dev/null
fi
# 重启 PHP-FPM 服务
sudo systemctl restart php${PHP_VERSION}-fpm
echo -e "\033[32mPHP 配置已更新并重启 PHP-FPM 服务。\033[0m"

136
web/wp.sh Normal file
View File

@@ -0,0 +1,136 @@
#!/bin/bash
# 一键部署WordPress脚本
# 项目地址https://github.com/sky22333/shell
# 检查是否已经安装 WordPress
if [ -d "/var/www/html/wordpress" ]; then
echo -e "\033[32m检测到 WordPress 已经安装。\033[0m"
echo -e "\033[33m如需重新安装请删除站点文件/var/www/html/wordpress 并做好相关备份。\033[0m"
exit 0
fi
while true; do
echo -e "\033[33m请输入您的域名(确保已经解析到本机): \033[0m"
read DOMAIN
echo -e "\033[32m您输入的域名是: $DOMAIN\033[0m"
echo -e "\033[33m为防止输错请核对域名是否正确 (yes/no直接回车代表正确): \033[0m"
read CONFIRM
# 如果用户按回车,则默认为确认
if [[ -z "${CONFIRM// }" ]]; then
CONFIRM="yes"
fi
if [[ "${CONFIRM,,}" == "yes" || "${CONFIRM,,}" == "y" ]]; then
echo -e "\033[32m域名确认成功: $DOMAIN\033[0m"
break
else
echo -e "\033[31m请重新输入域名。\033[0m"
fi
done
echo -e "\033[32m更新系统包...首次更新可能较慢...请耐心等待。。。\033[0m"
sudo apt-get update -yq
echo -e "\033[32m安装必要的软件包...首次安装可能较慢...请耐心等待。。。\033[0m"
sudo apt-get install -y -q mariadb-server php php-mysql php-fpm php-curl php-json php-cgi php-mbstring php-xml php-gd php-xmlrpc php-soap php-intl php-opcache php-zip wget unzip
sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo mysql_secure_installation <<EOF
y
y
y
y
y
EOF
# 项目地址https://github.com/sky22333/shell
# 创建WordPress数据库和用户
DB_NAME="wordpress"
DB_USER="wpuser"
DB_PASSWORD=$(openssl rand -base64 12)
sudo mysql -u root -e "DROP USER IF EXISTS '${DB_USER}'@'localhost';"
sudo mysql -u root -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -u root -e "CREATE USER '${DB_USER}'@'localhost' IDENTIFIED BY '${DB_PASSWORD}';"
sudo mysql -u root -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'localhost';"
sudo mysql -u root -e "FLUSH PRIVILEGES;"
# 下载并配置WordPress
mkdir -p /var/www/html
cd /var/www/html
wget https://zh-cn.wordpress.org/latest-zh_CN.tar.gz
tar -xzvf latest-zh_CN.tar.gz > /dev/null 2>&1
rm latest-zh_CN.tar.gz
sudo chown -R www-data:www-data /var/www/html/wordpress
sudo find /var/www/html/wordpress/ -type d -exec chmod 750 {} \;
sudo find /var/www/html/wordpress/ -type f -exec chmod 640 {} \;
if [ ! -d /etc/apt/sources.list.d/ ]; then
sudo mkdir -p /etc/apt/sources.list.d/
fi
sudo apt install -y -q debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update -yq
sudo apt install -y -q caddy
PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
OPCACHE_FILE_CACHE_DIR="/var/www/html/wordpress/wp-content/opcache"
sudo mkdir -p $OPCACHE_FILE_CACHE_DIR
sudo chown www-data:www-data $OPCACHE_FILE_CACHE_DIR
# 配置OPcache
sudo bash -c "cat > /etc/php/${PHP_VERSION}/fpm/conf.d/10-opcache.ini" <<EOF
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=9000
opcache.revalidate_freq=5
opcache.save_comments=1
opcache.file_cache=${OPCACHE_FILE_CACHE_DIR}
opcache.file_cache_size=128
opcache.file_cache_only=0
opcache.file_cache_consistency_checks=1
EOF
# 重启PHP-FPM服务
sudo systemctl restart php${PHP_VERSION}-fpm
if systemctl is-active --quiet apache2; then
sudo systemctl stop apache2
sudo systemctl disable apache2
sudo apt remove --purge apache2 -y
else
echo -e "当前环境是正常状态。"
fi
sudo bash -c "cat > /etc/caddy/Caddyfile" <<EOF
$DOMAIN {
root * /var/www/html/wordpress
encode zstd gzip
php_fastcgi unix//run/php/php${PHP_VERSION}-fpm.sock
file_server
}
EOF
sudo systemctl restart caddy
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[32m 数据库信息: \033[0m"
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[33m数据库名: \033[36m${DB_NAME}\033[0m"
echo -e "\033[33m用户名: \033[36m${DB_USER}\033[0m"
echo -e "\033[33m密码: \033[36m${DB_PASSWORD}\033[0m"
echo -e "\033[32m============================================================\033[0m"
echo -e "\033[32m站点域名: \033[36m${DOMAIN}\033[0m"
echo -e "\033[32m网站后台: \033[36m${DOMAIN}/wp-admin\033[0m"
echo -e "\033[32m您的 WordPress 站点已经部署完成,请记录好相关信息。\033[0m"
echo -e "\033[32m============================================================\033[0m"