update
This commit is contained in:
41
.github/workflows/docker-ghcr.yml
vendored
Normal file
41
.github/workflows/docker-ghcr.yml
vendored
Normal 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
44
README.md
Normal 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
8
ansible/Dockerfile
Normal 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
235
ansible/README.md
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
### 1:Ansibler容器环境搭建
|
||||||
|
|
||||||
|
#### 运行示例
|
||||||
|
```
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
- 临时对1,3号主机执行shell命令
|
||||||
|
```
|
||||||
|
ansible 1,3 -m shell -a "你的命令"
|
||||||
|
```
|
||||||
|
- 临时对1,3号主机执行普通命令
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
3
ansible/ansible/ansible.cfg
Normal file
3
ansible/ansible/ansible.cfg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[defaults]
|
||||||
|
host_key_checking = False
|
||||||
|
ansible_ssh_common_args = '-o StrictHostKeyChecking=no'
|
||||||
4
ansible/ansible/hosts
Normal file
4
ansible/ansible/hosts
Normal 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
17
ansible/ansible/renwu.yml
Normal 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
2
ansible/ansible/shell.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "Hello, world!"
|
||||||
55
dev/README.md
Normal file
55
dev/README.md
Normal 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
157
dev/acme.sh
Normal 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
242
dev/ansible.md
Normal 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"
|
||||||
|
```
|
||||||
|
- 临时对1,3号主机执行shell命令
|
||||||
|
```
|
||||||
|
ansible 1,3 -m shell -a "你的命令"
|
||||||
|
```
|
||||||
|
- 临时对1,3号主机执行普通命令
|
||||||
|
```
|
||||||
|
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
48
dev/ansible.sh
Normal 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
366
dev/bbr.sh
Normal 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
278
dev/cf-setup.ps1
Normal 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
133
dev/cf-tunnel.sh
Normal 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
29
dev/check_ntpdate.sh
Normal 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
1161
dev/docker.sh
Normal file
File diff suppressed because it is too large
Load Diff
121
dev/go.sh
Normal file
121
dev/go.sh
Normal 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
127
dev/image.sh
Normal 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
82
dev/mbit.sh
Normal 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
5702
dev/mirrors.sh
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
584
docs/index.html
Normal file
584
docs/index.html
Normal 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
691
docs/md/index.html
Normal 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)
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 图片
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|

|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## 高级语法
|
||||||
|
|
||||||
|
### 表格
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
| 表头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
1090
docs/nezha/index.html
Normal file
File diff suppressed because it is too large
Load Diff
15
gost/README.md
Normal file
15
gost/README.md
Normal 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
7
gost/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Debug": true,
|
||||||
|
"Retries": 0,
|
||||||
|
"ServeNodes": [
|
||||||
|
"udp://127.0.0.1:65532"
|
||||||
|
]
|
||||||
|
}
|
||||||
15
gost/gost.service
Normal file
15
gost/gost.service
Normal 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
942
gost/gost.sh
Normal 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
10
nezha/README.md
Normal 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
411
nezha/agent.sh
Normal 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
901
nezha/install.sh
Normal 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
31
proxy/README.md
Normal 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
556
proxy/duosk5-ss2022.sh
Normal 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
578
proxy/duosk5-vmess.sh
Normal 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
469
proxy/duosk5.go
Normal 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
237
proxy/duosocks.sh
Normal 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
141
proxy/duovmess.sh
Normal 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
564
proxy/l2tp.sh
Normal 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
569
proxy/singbox-zhanqun.sh
Normal 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
270
proxy/vmess-sk5.sh
Normal 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
86
proxy/vmess.sh
Normal 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
182
proxy/xray.sh
Normal 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
629
proxy/zhanqun.sh
Normal 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
25
web/README.md
Normal 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
196
web/acgfaka.sh
Normal 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
77
web/mariadb.sh
Normal 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
74
web/php.sh
Normal 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
136
web/wp.sh
Normal 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"
|
||||||
Reference in New Issue
Block a user