feat(ansible): extract playbooks and roles into standalone repository
This commit is contained in:
parent
99029986aa
commit
3344b1e530
6
common_setup.yml
Normal file
6
common_setup.yml
Normal file
@ -0,0 +1,6 @@
|
||||
- name: Run infrastructure setup
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
roles:
|
||||
- vhosts/common
|
||||
6
k3s-cluster.yaml
Normal file
6
k3s-cluster.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
- name: Run K3S-cluster setup
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
roles:
|
||||
- roles/vhosts/k3s-cluster
|
||||
6734
roles/grafana-dashboard/K8S-Dashboard-2025-01015.json
Normal file
6734
roles/grafana-dashboard/K8S-Dashboard-2025-01015.json
Normal file
File diff suppressed because it is too large
Load Diff
5890
roles/grafana-dashboard/Node-Exporter-Dashboard-202501015.json
Normal file
5890
roles/grafana-dashboard/Node-Exporter-Dashboard-202501015.json
Normal file
File diff suppressed because it is too large
Load Diff
18
roles/vhosts/common/defaults/main.yml
Normal file
18
roles/vhosts/common/defaults/main.yml
Normal file
@ -0,0 +1,18 @@
|
||||
enable_set_timezone: true # 默认启用 Set timezone
|
||||
enable_set_hostname: true # 默认启用 Set hostname
|
||||
enable_install_packages: true # 默认不安装额外的软件包
|
||||
enable_all_hosts_update: false # 默认不更新所有主机的条目
|
||||
|
||||
rsyslog_log_rotation: # 可选的日志管理配置
|
||||
enable: true # 启用 rsyslog 日志管理
|
||||
rotate_count: 4 # 默认保留的日志文件数量
|
||||
rotate_frequency: weekly # 默认每周轮换, 可选:daily, hourly
|
||||
max_log_size: 100M # 默认日志文件最大大小
|
||||
|
||||
journald_log_rotation: # 启用 journald 日志管理
|
||||
enable: true # 启用 journald 日志管理
|
||||
max_log_size: 100M # 默认日志文件最大大小
|
||||
max_files: 100 # 默认保留的最大日志文件数
|
||||
max_file_sec: 1month # 默认日志文件保存的最大时长
|
||||
system_max_use: 1G # 默认系统日志最大使用空间
|
||||
runtime_max_use: 500M # 默认运行时日志最大使用空间
|
||||
5
roles/vhosts/common/files/install-packages.sh
Normal file
5
roles/vhosts/common/files/install-packages.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y vim iputils-ping rsync wireguard-tools
|
||||
11
roles/vhosts/common/files/secure_ssh.sh
Normal file
11
roles/vhosts/common/files/secure_ssh.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 设置 ~/.ssh/ 目录的权限
|
||||
sudo chmod 700 ~/.ssh
|
||||
|
||||
# 设置 ~/.ssh/authorized_keys 文件的权限
|
||||
sudo chmod 600 ~/.ssh/authorized_keys
|
||||
|
||||
# 使用 chattr +i 确保 authorized_keys 文件不能被删除
|
||||
sudo chattr +i ~/.ssh/authorized_keys || true
|
||||
|
||||
10
roles/vhosts/common/handlers/main.yml
Normal file
10
roles/vhosts/common/handlers/main.yml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Restart logrotate service
|
||||
service:
|
||||
name: logrotate
|
||||
state: restarted
|
||||
|
||||
- name: Restart systemd-journald service
|
||||
service:
|
||||
name: systemd-journald
|
||||
state: restarted
|
||||
7
roles/vhosts/common/tasks/configure_journald.yml
Normal file
7
roles/vhosts/common/tasks/configure_journald.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Configure journald log rotation using template
|
||||
template:
|
||||
src: journald_logrotate.j2
|
||||
dest: /etc/systemd/journald.conf
|
||||
when: journald_log_rotation.enable
|
||||
notify: Restart systemd-journald service
|
||||
7
roles/vhosts/common/tasks/configure_logrotate.yaml
Normal file
7
roles/vhosts/common/tasks/configure_logrotate.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Configure logrotate for rsyslog using template
|
||||
template:
|
||||
src: rsyslog_logrotate.j2
|
||||
dest: /etc/logrotate.d/rsyslog
|
||||
when: rsyslog_log_rotation.enable
|
||||
notify: Restart logrotate service
|
||||
27
roles/vhosts/common/tasks/disable-systemd-resolved.yml
Normal file
27
roles/vhosts/common/tasks/disable-systemd-resolved.yml
Normal file
@ -0,0 +1,27 @@
|
||||
- name: Stop systemd-resolved
|
||||
systemd:
|
||||
name: systemd-resolved
|
||||
state: stopped
|
||||
enabled: no
|
||||
|
||||
- name: Remove /etc/resolv.conf if it's a symlink
|
||||
file:
|
||||
path: /etc/resolv.conf
|
||||
state: absent
|
||||
force: true
|
||||
|
||||
- name: Create static /etc/resolv.conf
|
||||
copy:
|
||||
dest: /etc/resolv.conf
|
||||
content: |
|
||||
nameserver 8.8.8.8
|
||||
nameserver 1.1.1.1
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
|
||||
- name: Optionally make resolv.conf immutable to prevent changes
|
||||
command: chattr +i /etc/resolv.conf
|
||||
args:
|
||||
warn: false
|
||||
when: make_resolv_conf_immutable | default(false)
|
||||
17
roles/vhosts/common/tasks/include_gpu.yaml
Normal file
17
roles/vhosts/common/tasks/include_gpu.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
- name: Add NVIDIA repository
|
||||
shell: |
|
||||
add-apt-repository -y ppa:graphics-drivers
|
||||
curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | apt-key add -
|
||||
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
|
||||
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | tee /etc/apt/sources.list.d/nvidia-container-runtime.list
|
||||
apt-get update
|
||||
|
||||
- name: Install NVIDIA driver and container runtime
|
||||
apt:
|
||||
name:
|
||||
- nvidia-modprobe
|
||||
- nvidia-driver-535
|
||||
- nvidia-headless-535
|
||||
- nvidia-container-runtime
|
||||
state: present
|
||||
update_cache: yes
|
||||
38
roles/vhosts/common/tasks/main.yml
Normal file
38
roles/vhosts/common/tasks/main.yml
Normal file
@ -0,0 +1,38 @@
|
||||
- name: Set timezone
|
||||
shell: "timedatectl set-timezone Asia/Shanghai"
|
||||
|
||||
- name: Set hostname
|
||||
shell: "hostname -F /etc/hostname"
|
||||
|
||||
- name: update /etc/hostname
|
||||
template: src=templates/hostname dest=/etc/hostname owner=root group=root mode=0644 unsafe_writes=yes
|
||||
|
||||
- name: Update /etc/hosts
|
||||
template: src=templates/hosts dest=/etc/hosts owner=root group=root mode=0644 force=yes unsafe_writes=yes
|
||||
|
||||
- name: Set systemd-resolved and set static DNS
|
||||
include_tasks: setup-systemd-resolved.yml
|
||||
|
||||
- name: Install packages
|
||||
script: files/install-packages.sh
|
||||
when: (ansible_facts['distribution'] == "Ubuntu") or (ansible_facts['distribution'] == "Debian")
|
||||
|
||||
- name: Include Privoxy sub task for SOCKS5 to HTTP proxy (optional)
|
||||
include_tasks: setup-privoxy.yml
|
||||
when: privoxy.enable | default(false)
|
||||
|
||||
#- name: Include GPU Configuration
|
||||
# include_tasks: include_gpu.yaml
|
||||
# when: (ansible_facts['distribution'] == "Ubuntu") or (ansible_facts['distribution'] == "Debian")
|
||||
# tags:
|
||||
# - k3s
|
||||
# - gpu
|
||||
# - nvidia
|
||||
|
||||
#- name: enable ip_forward
|
||||
# shell: 'echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf; echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf ; sysctl -p /etc/sysctl.conf'
|
||||
|
||||
|
||||
#- name: Install packages
|
||||
# shell: "yum makecache && yum install -y audit container-selinux"
|
||||
# when: (ansible_facts['distribution'] != "Ubuntu") or (ansible_facts['distribution'] != "Debian")
|
||||
12
roles/vhosts/common/tasks/set_hostname.yaml
Normal file
12
roles/vhosts/common/tasks/set_hostname.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
- name: Check if systemctl is available
|
||||
command: which hostnamectl
|
||||
register: systemctl_check
|
||||
ignore_errors: true
|
||||
|
||||
- name: Set hostname using systemctl if available
|
||||
shell: "hostnamectl set-hostname {{ inventory_hostname }}"
|
||||
when: systemctl_check.rc == 0
|
||||
|
||||
- name: Set hostname using hostname -F if systemctl is not available
|
||||
shell: "hostname -F /etc/hostname"
|
||||
when: systemctl_check.rc != 0
|
||||
2
roles/vhosts/common/tasks/set_timezone.yaml
Normal file
2
roles/vhosts/common/tasks/set_timezone.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
- name: Set timezone
|
||||
shell: "timedatectl set-timezone Asia/Shanghai"
|
||||
24
roles/vhosts/common/tasks/setup-privoxy.yml
Normal file
24
roles/vhosts/common/tasks/setup-privoxy.yml
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Install privoxy (Debian)
|
||||
apt:
|
||||
name: privoxy
|
||||
state: present
|
||||
when: ansible_os_family == 'Debian'
|
||||
|
||||
- name: Install privoxy (RedHat)
|
||||
yum:
|
||||
name: privoxy
|
||||
state: present
|
||||
when: ansible_os_family == 'RedHat'
|
||||
|
||||
- name: Ensure SOCKS5 forwarding is configured in privoxy
|
||||
lineinfile:
|
||||
path: /etc/privoxy/config
|
||||
line: "forward-socks5t / {{ proxy.socks5_host }}:{{ proxcy.socks5_port }} ."
|
||||
state: present
|
||||
|
||||
- name: Restart and enable privoxy
|
||||
systemd:
|
||||
name: privoxy
|
||||
state: restarted
|
||||
enabled: yes
|
||||
36
roles/vhosts/common/tasks/setup-systemd-resolved.yml
Normal file
36
roles/vhosts/common/tasks/setup-systemd-resolved.yml
Normal file
@ -0,0 +1,36 @@
|
||||
# playbooks/setup-systemd-resolved.yml
|
||||
- name: Ensure systemd-resolved is installed
|
||||
package:
|
||||
name: systemd-resolved
|
||||
state: present
|
||||
|
||||
- name: Enable and start systemd-resolved
|
||||
systemd:
|
||||
name: systemd-resolved
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: Configure /etc/systemd/resolved.conf
|
||||
ini_file:
|
||||
path: /etc/systemd/resolved.conf
|
||||
section: "Resolve"
|
||||
option: "{{ item.option }}"
|
||||
value: "{{ item.value }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { option: "DNSStubListener", value: "no" }
|
||||
- { option: "DNS", value: "" }
|
||||
- { option: "FallbackDNS", value: "" }
|
||||
|
||||
- name: Restart systemd-resolved
|
||||
systemd:
|
||||
name: systemd-resolved
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Ensure /etc/resolv.conf points to /run/systemd/resolve/resolv.conf
|
||||
file:
|
||||
src: /run/systemd/resolve/resolv.conf
|
||||
dest: /etc/resolv.conf
|
||||
state: link
|
||||
force: true
|
||||
3
roles/vhosts/common/templates/authorized_keys
Executable file
3
roles/vhosts/common/templates/authorized_keys
Executable file
@ -0,0 +1,3 @@
|
||||
{% for item in ssh_keys %}
|
||||
{{ item }}
|
||||
{% endfor %}
|
||||
1
roles/vhosts/common/templates/hostname
Executable file
1
roles/vhosts/common/templates/hostname
Executable file
@ -0,0 +1 @@
|
||||
{{ inventory_hostname }}
|
||||
26
roles/vhosts/common/templates/hosts
Normal file
26
roles/vhosts/common/templates/hosts
Normal file
@ -0,0 +1,26 @@
|
||||
# IPv4 localhost configuration
|
||||
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
||||
|
||||
# IPv6 localhost configuration
|
||||
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
||||
|
||||
# IPv6 Local addresses (desirable for IPv6 capable hosts)
|
||||
::1 ip6-localhost ip6-loopback
|
||||
fe00::0 ip6-localnet
|
||||
ff00::0 ip6-mcastprefix
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
|
||||
{{ ansible_default_ipv4.address }} {{ inventory_hostname }}
|
||||
|
||||
{% if enable_all_hosts_update is defined and enable_all_hosts_update %}
|
||||
{% for item in groups['all'] %}
|
||||
{{ hostvars[item]['ansible_host'] }} {{ item }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if extra_domain is defined %}
|
||||
{% for ip, domain_name in extra_domain.items() %}
|
||||
{{ ip }} {{ domain_name }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
5
roles/vhosts/common/templates/journald_logrotate.j2
Normal file
5
roles/vhosts/common/templates/journald_logrotate.j2
Normal file
@ -0,0 +1,5 @@
|
||||
[Journal]
|
||||
SystemMaxUse={{ journald_log_rotation.system_max_use }} # 设置最大日志使用空间
|
||||
SystemMaxFiles={{ journald_log_rotation.max_files }} # 设置最大日志文件数
|
||||
MaxFileSec={{ journald_log_rotation.max_file_sec }} # 设置日志文件的轮换频率(例如 weekly, daily, hourly)
|
||||
RuntimeMaxUse={{ journald_log_rotation.runtime_max_use }} # 设置运行时日志最大使用空间
|
||||
8
roles/vhosts/common/templates/logrotate-monitor-agent
Normal file
8
roles/vhosts/common/templates/logrotate-monitor-agent
Normal file
@ -0,0 +1,8 @@
|
||||
/var/log/prometheus-agent.log
|
||||
/var/log/prometheus-transfer.log {
|
||||
rotate 12
|
||||
monthly
|
||||
compress
|
||||
missingok
|
||||
notifempty
|
||||
}
|
||||
23
roles/vhosts/common/templates/rsyslog_logrotate.j2
Normal file
23
roles/vhosts/common/templates/rsyslog_logrotate.j2
Normal file
@ -0,0 +1,23 @@
|
||||
/var/log/syslog
|
||||
/var/log/mail* # 包括所有以 mail 开头的日志文件
|
||||
/var/log/daemon.log
|
||||
/var/log/kern.log
|
||||
/var/log/auth.log
|
||||
/var/log/user.log
|
||||
/var/log/lpr.log
|
||||
/var/log/cron.log
|
||||
/var/log/debug
|
||||
/var/log/messages
|
||||
{
|
||||
rotate {{ rsyslog_log_rotation.rotate_count }}
|
||||
{{ rsyslog_log_rotation.rotate_frequency }}
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
delaycompress
|
||||
sharedscripts
|
||||
postrotate
|
||||
/usr/lib/rsyslog/rsyslog-rotate
|
||||
endscript
|
||||
maxsize {{ rsyslog_log_rotation.max_log_size }}
|
||||
}
|
||||
300
roles/vhosts/k3s-cluster/files/set-registry.sh
Normal file
300
roles/vhosts/k3s-cluster/files/set-registry.sh
Normal file
@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
|
||||
#https://github.com/containerd/nerdctl/releases/download/v2.0.2/nerdctl-2.0.2-linux-amd64.tar.gz
|
||||
#https://github.com/containerd/nerdctl/releases/download/v2.0.2/nerdctl-full-2.0.2-linux-amd64.tar.gz
|
||||
#wget https://github.com/containernetworking/plugins/releases/download/v1.6.2/cni-plugins-linux-amd64-v1.6.2.tgz
|
||||
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# =============================================
|
||||
# ✅ 环境变量检查(可配置)
|
||||
# =============================================
|
||||
: "${REGISTRY_DOMAIN:=kube.registry.local}"
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
: "${NERDCTL_VERSION:=v2.0.2}"
|
||||
: "${CNI_VERSION:=v1.6.2}"
|
||||
: "${CNI_DIR:=/opt/cni/bin}"
|
||||
: "${CERT_DIR:=/opt/registry/certs}"
|
||||
: "${CONFIG_DIR:=/opt/registry/config}"
|
||||
: "${REGISTRY_DATA:=/var/lib/registry}"
|
||||
: "${REGISTRY_YAML:=registry.yaml}"
|
||||
: "${COMPOSE_YAML:=compose.yaml}"
|
||||
: "${TAR_FILE:=registry.tar}"
|
||||
|
||||
# =============================================
|
||||
# ✅ 自动检测 containerd.sock
|
||||
# =============================================
|
||||
if [[ -S "/run/k3s/containerd/containerd.sock" ]]; then
|
||||
export CONTAINERD_ADDRESS="/run/k3s/containerd/containerd.sock"
|
||||
elif [[ -S "/run/containerd/containerd.sock" ]]; then
|
||||
export CONTAINERD_ADDRESS="/run/containerd/containerd.sock"
|
||||
elif [[ -S "/var/run/containerd/containerd.sock" ]]; then
|
||||
export CONTAINERD_ADDRESS="/var/run/containerd/containerd.sock"
|
||||
else
|
||||
echo "❌ 未检测到有效的 containerd.sock,请确认 containerd 是否正常运行。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export NERDCTL_NAMESPACE="k8s.io"
|
||||
|
||||
# =============================================
|
||||
echo "📦 准备 nerdctl 全功能版..."
|
||||
if ! command -v nerdctl &>/dev/null; then
|
||||
if [ ! -f /tmp/nerdctl-full.tgz ]; then
|
||||
echo "⬇️ 下载 nerdctl..."
|
||||
wget -O /tmp/nerdctl-full.tgz \
|
||||
"https://github.com/containerd/nerdctl/releases/download/${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION#v}-linux-amd64.tar.gz"
|
||||
else
|
||||
echo "📦 已存在 nerdctl-full.tgz,跳过下载"
|
||||
fi
|
||||
|
||||
echo "📦 解压 nerdctl 到 /usr/local..."
|
||||
sudo tar -C /usr/local -xzf /tmp/nerdctl-full.tgz
|
||||
echo "✅ nerdctl 安装完成: $(nerdctl --version)"
|
||||
else
|
||||
echo "✅ nerdctl 已存在: $(nerdctl --version)"
|
||||
fi
|
||||
|
||||
# =============================================
|
||||
echo "📦 安装 CNI 插件..."
|
||||
if [ ! -f "${CNI_DIR}/bridge" ]; then
|
||||
if [ ! -f /tmp/cni.tgz ]; then
|
||||
echo "⬇️ 下载 CNI 插件..."
|
||||
wget -O /tmp/cni.tgz \
|
||||
"https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-amd64-${CNI_VERSION}.tgz"
|
||||
else
|
||||
echo "📦 已存在 cni.tgz,跳过下载"
|
||||
fi
|
||||
|
||||
sudo mkdir -p "${CNI_DIR}"
|
||||
sudo tar -C "${CNI_DIR}" -xzf /tmp/cni.tgz
|
||||
echo "✅ CNI 插件已安装到: ${CNI_DIR}"
|
||||
else
|
||||
echo "✅ CNI 插件已存在: ${CNI_DIR}/bridge"
|
||||
fi
|
||||
|
||||
# =============================================
|
||||
echo "📦 解压 SSL 证书..."
|
||||
|
||||
if [ ! -f "ssl_certificates.tar.gz" ]; then
|
||||
echo "⬇️ 未找到 ssl_certificates.tar.gz,尝试从 GitHub 下载..."
|
||||
wget -O ssl_certificates.tar.gz \
|
||||
"https://github.com/svc-design/ansible/releases/download/release-self-signed-cert_kube.registry.local/ssl_certificates.tar.gz" || {
|
||||
echo "❌ 无法下载 ssl_certificates.tar.gz,终止执行"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
if [ -f "ssl_certificates.tar.gz" ]; then
|
||||
mkdir -p "$CERT_DIR"
|
||||
tar -xvpf ssl_certificates.tar.gz
|
||||
tar -xvpf ssl_certificates.tar.gz -C "$CERT_DIR"
|
||||
echo "✅ 证书已解压至: $CERT_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================
|
||||
|
||||
# ============ 生成 registry-config ============
|
||||
echo "⚙️ 准备 registry 配置..."
|
||||
sudo mkdir -pv "$CONFIG_DIR"
|
||||
sudo mkdir -pv "$REGISTRY_DATA"
|
||||
echo "📝 写入 registry-config.yaml..."
|
||||
sudo cat > "${CONFIG_DIR}/${REGISTRY_YAML}" <<EOF
|
||||
version: 0.1
|
||||
log:
|
||||
fields:
|
||||
service: registry
|
||||
storage:
|
||||
cache:
|
||||
blobdescriptor: inmemory
|
||||
filesystem:
|
||||
rootdirectory: /var/lib/registry
|
||||
delete:
|
||||
enabled: true
|
||||
http:
|
||||
addr: :$REGISTRY_PORT
|
||||
headers:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
tls:
|
||||
certificate: /etc/docker/registry/domain.crt
|
||||
key: /etc/docker/registry/domain.key
|
||||
health:
|
||||
storagedriver:
|
||||
enabled: true
|
||||
interval: 10s
|
||||
threshold: 3
|
||||
EOF
|
||||
|
||||
echo "✅ 写入完成: $REGISTRY_CONFIG"
|
||||
|
||||
# ========== 生成 registry.yaml ==========
|
||||
echo "🛠️ 生成 registry 配置..."
|
||||
sudo mkdir -p "$CONFIG_DIR"
|
||||
cat <<EOF | sudo tee "${CONFIG_DIR}/registry.yaml" > /dev/null
|
||||
version: 0.1
|
||||
log:
|
||||
fields:
|
||||
service: registry
|
||||
storage:
|
||||
cache:
|
||||
blobdescriptor: inmemory
|
||||
filesystem:
|
||||
rootdirectory: /var/lib/registry
|
||||
delete:
|
||||
enabled: true
|
||||
http:
|
||||
addr: :${REGISTRY_PORT}
|
||||
headers:
|
||||
X-Content-Type-Options: [nosniff]
|
||||
tls:
|
||||
certificate: /etc/docker/registry/domain.crt
|
||||
key: /etc/docker/registry/domain.key
|
||||
health:
|
||||
storagedriver:
|
||||
enabled: true
|
||||
interval: 10s
|
||||
threshold: 3
|
||||
EOF
|
||||
echo "✅ registry.yaml 已创建"
|
||||
|
||||
# ========== 生成 compose.yaml ==========
|
||||
echo "🛠️ 生成 compose 配置..."
|
||||
cat <<EOF | sudo tee "${CONFIG_DIR}/compose.yaml" > /dev/null
|
||||
services:
|
||||
registry:
|
||||
image: registry:latest
|
||||
container_name: registry
|
||||
restart: always
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/lib/registry:/var/lib/registry
|
||||
- ${CONFIG_DIR}/registry.yaml:/etc/docker/registry/config.yml
|
||||
- ${CERT_DIR}/kube.registry.local.cert:/etc/docker/registry/domain.crt
|
||||
- ${CERT_DIR}/kube.registry.local.key:/etc/docker/registry/domain.key
|
||||
EOF
|
||||
echo "✅ compose.yaml 已创建"
|
||||
|
||||
# =============================================
|
||||
echo "📦 导入本地 registry 镜像..."
|
||||
if [ -f "/usr/local/deepflow/$TAR_FILE" ]; then
|
||||
sudo CONTAINERD_ADDRESS="$CONTAINERD_ADDRESS" nerdctl --namespace $NERDCTL_NAMESPACE load -i "/usr/local/deepflow/$TAR_FILE"
|
||||
else
|
||||
echo "⚠️ 本地镜像文件不存在:/usr/local/deepflow/$TAR_FILE"
|
||||
fi
|
||||
|
||||
# =============================================
|
||||
echo "🔁 重启 registry 服务..."
|
||||
sudo CONTAINERD_ADDRESS="$CONTAINERD_ADDRESS" nerdctl --namespace $NERDCTL_NAMESPACE compose -f "$CONFIG_DIR/compose.yaml" down || true
|
||||
sudo CONTAINERD_ADDRESS="$CONTAINERD_ADDRESS" nerdctl --namespace $NERDCTL_NAMESPACE compose -f "$CONFIG_DIR/compose.yaml" up -d
|
||||
|
||||
# =============================================
|
||||
echo "🔗 添加 hosts 映射..."
|
||||
if ! grep -q "$REGISTRY_DOMAIN" /etc/hosts; then
|
||||
echo "127.0.0.1 $REGISTRY_DOMAIN" | sudo tee -a /etc/hosts
|
||||
echo "✅ /etc/hosts 已添加 $REGISTRY_DOMAIN"
|
||||
else
|
||||
echo "✅ hosts 中已存在 $REGISTRY_DOMAIN"
|
||||
fi
|
||||
|
||||
echo "✅ Registry 启动成功: https://$REGISTRY_DOMAIN:$REGISTRY_PORT"
|
||||
|
||||
# =============================================
|
||||
echo "🔐 安装 CA 证书到系统信任目录..."
|
||||
|
||||
CA_CERT="${CERT_DIR}/ca.cert"
|
||||
if [ ! -f "$CA_CERT" ]; then
|
||||
echo "❌ 未找到 CA 证书: $CA_CERT"
|
||||
else
|
||||
if grep -qi "ubuntu\|debian" /etc/os-release; then
|
||||
sudo cp "$CA_CERT" "/usr/local/share/ca-certificates/kube-registry-ca.crt"
|
||||
sudo update-ca-certificates
|
||||
echo "✅ 已导入 CA 到 Ubuntu/Debian 系统信任目录"
|
||||
elif grep -qi "rhel\|centos\|rocky" /etc/os-release; then
|
||||
sudo cp "$CA_CERT" "/etc/pki/ca-trust/source/anchors/kube-registry-ca.crt"
|
||||
sudo update-ca-trust extract
|
||||
echo "✅ 已导入 CA 到 RHEL/CentOS 系统信任目录"
|
||||
else
|
||||
echo "⚠️ 未知发行版,跳过系统 CA 导入"
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================
|
||||
echo "🐳 安装 CA 到容器运行时 (Docker/Containerd)..."
|
||||
|
||||
# --- Docker CA ---
|
||||
if command -v docker &>/dev/null; then
|
||||
echo "🔧 配置 Docker..."
|
||||
DOCKER_CA_DIR="/etc/docker/certs.d/kube.registry.local"
|
||||
sudo mkdir -p "$DOCKER_CA_DIR"
|
||||
sudo cp "$CA_CERT" "${DOCKER_CA_DIR}/ca.crt"
|
||||
echo "✅ 已导入 CA 到 Docker: $DOCKER_CA_DIR"
|
||||
sudo systemctl restart docker
|
||||
fi
|
||||
|
||||
# --- Containerd CA ---
|
||||
if command -v containerd &>/dev/null || [ -S "$CONTAINERD_SOCK" ]; then
|
||||
echo "🔧 配置 Containerd..."
|
||||
|
||||
# Alpine/K3s: /etc/containerd/certs.d
|
||||
# cri-o/nerdctl: /etc/containerd/certs.d/kube.registry.local/ca.crt
|
||||
CONTAINERD_CA_DIR="/etc/containerd/certs.d/kube.registry.local"
|
||||
sudo mkdir -p "$CONTAINERD_CA_DIR"
|
||||
sudo cp "$CA_CERT" "${CONTAINERD_CA_DIR}/ca.crt"
|
||||
echo "✅ 已导入 CA 到 Containerd: $CONTAINERD_CA_DIR"
|
||||
sudo systemctl restart containerd || echo "⚠️ containerd 重启失败,可能在 K3s 中不适用"
|
||||
fi
|
||||
|
||||
# --- K3s CA ---
|
||||
if [[ -S "/run/k3s/containerd/containerd.sock" ]]; then
|
||||
echo "🔧 检测到 K3s 环境,准备配置自定义 registry CA..."
|
||||
|
||||
# === 配置参数 ===
|
||||
REGISTRY_DOMAIN="kube.registry.local"
|
||||
REGISTRY_PORT="5000"
|
||||
CA_CERT_PATH="/opt/registry/certs/ca.cert"
|
||||
REGISTRIES_YAML="/etc/rancher/k3s/registries.yaml"
|
||||
CA_DST_DIR="/etc/rancher/k3s/registries.d/${REGISTRY_DOMAIN}"
|
||||
CA_DST_FILE="${CA_DST_DIR}/ca.crt"
|
||||
|
||||
# === 准备目录并拷贝证书 ===
|
||||
sudo mkdir -p "${CA_DST_DIR}"
|
||||
sudo cp "${CA_CERT_PATH}" "${CA_DST_FILE}"
|
||||
|
||||
# === 写入 registries.yaml ===
|
||||
echo "[INFO] 写入 registries.yaml 配置..."
|
||||
sudo tee "${REGISTRIES_YAML}" > /dev/null <<EOF
|
||||
mirrors:
|
||||
"${REGISTRY_DOMAIN}:${REGISTRY_PORT}":
|
||||
endpoint:
|
||||
- "https://${REGISTRY_DOMAIN}:${REGISTRY_PORT}"
|
||||
|
||||
configs:
|
||||
"${REGISTRY_DOMAIN}:${REGISTRY_PORT}":
|
||||
tls:
|
||||
ca_file: "${CA_DST_FILE}"
|
||||
EOF
|
||||
|
||||
cat /etc/rancher/k3s/registries.yaml << EOF
|
||||
mirrors:
|
||||
"kube.registry.local:5000":
|
||||
endpoint:
|
||||
- "http://kube.registry.local:5000"
|
||||
|
||||
configs:
|
||||
"kube.registry.local:5000":
|
||||
tls:
|
||||
insecure_skip_verify: true
|
||||
EOF
|
||||
|
||||
# === 重启 K3s 生效 ===
|
||||
echo "[INFO] 重启 K3s 服务..."
|
||||
if systemctl list-units --type=service | grep -q 'k3s-agent'; then
|
||||
sudo systemctl restart k3s-agent
|
||||
else
|
||||
sudo systemctl restart k3s
|
||||
fi
|
||||
|
||||
echo "[✅ SUCCESS] 已配置自定义 registry 并导入 CA:https://${REGISTRY_DOMAIN}:${REGISTRY_PORT}"
|
||||
fi
|
||||
|
||||
37
roles/vhosts/k3s-cluster/files/setup_k3s.sh
Normal file
37
roles/vhosts/k3s-cluster/files/setup_k3s.sh
Normal file
@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# === 必要参数 ===
|
||||
INSTALL_K3S_URL="https://get.k3s.io"
|
||||
FLANNEL_IFACE=${FLANNEL_IFACE:-br0}
|
||||
|
||||
# === 安装命令拼接(最简)===
|
||||
INSTALL_K3S_EXEC="server \
|
||||
--disable=traefik,servicelb,local-storage \
|
||||
--data-dir=/opt/rancher/k3s \
|
||||
--advertise-address=$(hostname -I | awk '{print $1}') \
|
||||
--kube-apiserver-arg=service-node-port-range=0-50000"
|
||||
|
||||
[[ -n "$FLANNEL_IFACE" ]] && INSTALL_K3S_EXEC+=" --flannel-iface=${FLANNEL_IFACE}"
|
||||
|
||||
# === 下载并执行安装 ===
|
||||
curl -sfL ${INSTALL_K3S_URL} -o install_k3s.sh && chmod +x install_k3s.sh
|
||||
INSTALL_K3S_EXEC="$INSTALL_K3S_EXEC" ./install_k3s.sh
|
||||
|
||||
# === 等待 CoreDNS 启动 ===
|
||||
echo "⏳ 等待 CoreDNS 启动..."
|
||||
until kubectl get pods -A 2>/dev/null | grep -q "coredns.*Running"; do
|
||||
sleep 3
|
||||
done
|
||||
|
||||
# === 设置本地 kubeconfig ===
|
||||
mkdir -p ~/.kube
|
||||
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
chmod 600 ~/.kube/config
|
||||
export KUBECONFIG=~/.kube/config
|
||||
|
||||
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||
|
||||
echo "✅ K3s 安装完成,kubectl/helm 已就绪"
|
||||
|
||||
|
||||
44
roles/vhosts/k3s-cluster/tasks/main.yml
Normal file
44
roles/vhosts/k3s-cluster/tasks/main.yml
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
- name: Ensure remote tmp directory exists
|
||||
file:
|
||||
path: /tmp/ansible-{{ ansible_user }}
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
|
||||
- name: Sync setup_k3s.sh to remote
|
||||
synchronize:
|
||||
src: files/setup_k3s.sh
|
||||
dest: /tmp/ansible-{{ ansible_user }}/setup_k3s.sh
|
||||
mode: push
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Sync set-registry.sh to remote
|
||||
synchronize:
|
||||
src: files/set-registry.sh
|
||||
dest: /tmp/ansible-{{ ansible_user }}/set-registry.sh
|
||||
mode: push
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Ensure setup_k3s.sh is executable
|
||||
file:
|
||||
path: /tmp/ansible-{{ ansible_user }}/setup_k3s.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Ensure set-registry.sh is executable
|
||||
file:
|
||||
path: /tmp/ansible-{{ ansible_user }}/set-registry.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Run setup_k3s.sh
|
||||
command: ./setup_k3s.sh
|
||||
args:
|
||||
chdir: /tmp/ansible-{{ ansible_user }}
|
||||
|
||||
- name: Run set-registry.sh
|
||||
command: ./set-registry.sh
|
||||
args:
|
||||
chdir: /tmp/ansible-{{ ansible_user }}
|
||||
6
roles/vhosts/vpn-overlay/setup-dnat/defaults/main.yml
Normal file
6
roles/vhosts/vpn-overlay/setup-dnat/defaults/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
# 从 vpn-overlay.yaml 加载 overlay 结构
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
|
||||
# 脚本默认路径
|
||||
dnat_script_path: /usr/local/bin/setup-dnat.sh
|
||||
40
roles/vhosts/vpn-overlay/setup-dnat/tasks/main.yml
Normal file
40
roles/vhosts/vpn-overlay/setup-dnat/tasks/main.yml
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
- name: 加载 overlay 配置(标准 YAML)
|
||||
set_fact:
|
||||
overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: 提取当前节点信息(作为 current_node)
|
||||
set_fact:
|
||||
current_node: >-
|
||||
{{ (overlay_data.sites + overlay_data.hubs)
|
||||
| selectattr('name', 'equalto', inventory_hostname)
|
||||
| list | first }}
|
||||
|
||||
- name: 设置本节点 DNAT 所需变量
|
||||
set_fact:
|
||||
dnat_public_ip: "{{ current_node.public_ip }}"
|
||||
dnat_internal_ip: "{{ current_node.wg_ip }}"
|
||||
pod_cidr: "{{ current_node.pod_cidr }}"
|
||||
wireguard_cidr: "{{ current_node.wireguard_cidr }}"
|
||||
|
||||
- name: 模板渲染 DNAT 脚本
|
||||
template:
|
||||
src: setup-dnat.sh.j2
|
||||
dest: "{{ dnat_script_path }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: 安装 systemd 服务
|
||||
template:
|
||||
src: dnat-rules.service.j2
|
||||
dest: /etc/systemd/system/dnat-rules.service
|
||||
mode: "0644"
|
||||
|
||||
- name: Reload systemd daemon
|
||||
command: systemctl daemon-reexec
|
||||
changed_when: false
|
||||
|
||||
- name: 启动并启用 DNAT 服务
|
||||
systemd:
|
||||
name: dnat-rules.service
|
||||
enabled: true
|
||||
state: started
|
||||
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Setup DNAT rules for exposing services via WireGuard
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{ dnat_script_path }}
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
ACTION=$1
|
||||
|
||||
if [[ "$ACTION" == "clean" ]]; then
|
||||
echo "[DNAT] 清理规则..."
|
||||
|
||||
iptables -t nat -D PREROUTING -p tcp -d {{ dnat_public_ip }} --dport 80 -j DNAT --to-destination {{ dnat_internal_ip }}:80
|
||||
iptables -t nat -D PREROUTING -p tcp -d {{ dnat_public_ip }} --dport 443 -j DNAT --to-destination {{ dnat_internal_ip }}:443
|
||||
|
||||
iptables -D FORWARD -p tcp -d {{ pod_cidr }} --dport 80 -j ACCEPT
|
||||
iptables -D FORWARD -p tcp -d {{ pod_cidr }} --dport 443 -j ACCEPT
|
||||
iptables -D FORWARD -p tcp -d {{ wireguard_cidr }} --dport 80 -j ACCEPT
|
||||
iptables -D FORWARD -p tcp -d {{ wireguard_cidr }} --dport 443 -j ACCEPT
|
||||
|
||||
else
|
||||
echo "[DNAT] 添加规则..."
|
||||
|
||||
iptables -t nat -A PREROUTING -p tcp -d {{ dnat_public_ip }} --dport 80 -j DNAT --to-destination {{ dnat_internal_ip }}:80
|
||||
iptables -t nat -A PREROUTING -p tcp -d {{ dnat_public_ip }} --dport 443 -j DNAT --to-destination {{ dnat_internal_ip }}:443
|
||||
|
||||
iptables -A FORWARD -p tcp -d {{ pod_cidr }} --dport 80 -j ACCEPT
|
||||
iptables -A FORWARD -p tcp -d {{ pod_cidr }} --dport 443 -j ACCEPT
|
||||
iptables -A FORWARD -p tcp -d {{ wireguard_cidr }} --dport 80 -j ACCEPT
|
||||
iptables -A FORWARD -p tcp -d {{ wireguard_cidr }} --dport 443 -j ACCEPT
|
||||
fi
|
||||
7
roles/vhosts/vpn-overlay/vxlan/hub/defaults/main.yml
Normal file
7
roles/vhosts/vpn-overlay/vxlan/hub/defaults/main.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
# Default VXLAN settings for site nodes
|
||||
vxlan_dev_if: wg0
|
||||
vxlan_vni: 100
|
||||
vxlan_cidr_suffix: 16
|
||||
overlay_config_path: config/sit/vpn-overlay.yaml
|
||||
|
||||
83
roles/vhosts/vpn-overlay/vxlan/hub/files/setup_sit_vxlan.sh
Normal file
83
roles/vhosts/vpn-overlay/vxlan/hub/files/setup_sit_vxlan.sh
Normal file
@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
# 安全增强版 VXLAN Overlay 脚本(支持 wg0 作为通道设备 + 可选公网 DNAT 映射)
|
||||
# 用法: ./setup_sit_vxlan.sh <dev_if> <local_ip> <remote_ip> <br0_ip> [cidr_suffix] [vxlan_id] [mtu] [expose_port]
|
||||
|
||||
set -e
|
||||
|
||||
DEV_IF="$1"
|
||||
LOCAL_IP="$2"
|
||||
REMOTE_IP="$3"
|
||||
BRIDGE_IP="$4"
|
||||
CIDR_SUFFIX="${5:-16}"
|
||||
VNI="${6:-100}"
|
||||
MTU="${7:-1400}"
|
||||
EXPOSE_PORT="${8:-443}"
|
||||
|
||||
if [[ -z "$DEV_IF" || -z "$LOCAL_IP" || -z "$REMOTE_IP" || -z "$BRIDGE_IP" ]]; then
|
||||
echo "Usage: $0 <dev_if> <local_ip> <remote_ip> <br0_ip> [cidr_suffix] [vxlan_id] [mtu] [expose_port]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VXLAN_IF="vxlan${VNI}"
|
||||
BR_IF="br0"
|
||||
BRIDGE_CIDR="${BRIDGE_IP}/${CIDR_SUFFIX}"
|
||||
SUBNET="$(echo "$BRIDGE_IP" | cut -d. -f1-2).0.0/${CIDR_SUFFIX}"
|
||||
|
||||
# 自动判断 dev 是否能用于 VXLAN(需支持广播)
|
||||
function is_vxlan_dev_usable() {
|
||||
[[ -d "/sys/class/net/$1" ]] && grep -q "broadcast" "/sys/class/net/$1/flags"
|
||||
}
|
||||
|
||||
echo "🔍 检查 $DEV_IF 是否可用于 VXLAN..."
|
||||
USE_DEV_PARAM=true
|
||||
if ! is_vxlan_dev_usable "$DEV_IF"; then
|
||||
echo "⚠️ $DEV_IF 不支持广播,将省略 dev 参数(通过路由走隧道)"
|
||||
USE_DEV_PARAM=false
|
||||
fi
|
||||
|
||||
# 清理旧接口
|
||||
for iface in "$VXLAN_IF" "$BR_IF"; do
|
||||
ip link show "$iface" &>/dev/null && ip link set "$iface" down && ip link del "$iface"
|
||||
done
|
||||
|
||||
# 创建 VXLAN 接口
|
||||
echo "🛠️ 创建 VXLAN 接口 $VXLAN_IF ..."
|
||||
if $USE_DEV_PARAM; then
|
||||
ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" dev "$DEV_IF"
|
||||
else
|
||||
ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP"
|
||||
fi
|
||||
ip link set "$VXLAN_IF" mtu "$MTU"
|
||||
ip link set "$VXLAN_IF" up
|
||||
|
||||
# 创建 br0 桥
|
||||
ip link add "$BR_IF" type bridge
|
||||
ip link set "$BR_IF" mtu "$MTU"
|
||||
ip link set "$VXLAN_IF" master "$BR_IF"
|
||||
ip link set "$BR_IF" up
|
||||
|
||||
# 配置 IP
|
||||
ip addr add "$BRIDGE_CIDR" dev "$BR_IF"
|
||||
|
||||
# 启用转发 + SNAT(仅主机出网时)
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
iptables -t nat -C POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE 2>/dev/null || \
|
||||
iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE
|
||||
|
||||
# ⚠️ 可选:仅在 EXPOSE_PORT 被定义时启用 DNAT 公网端口映射
|
||||
if [[ -n "$EXPOSE_PORT" ]]; then
|
||||
echo "🌐 添加 DNAT 映射规则:公网:$EXPOSE_PORT → ${BRIDGE_IP}:443"
|
||||
|
||||
iptables -t nat -C PREROUTING -p tcp -m tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443" 2>/dev/null || \
|
||||
iptables -t nat -A PREROUTING -p tcp -m tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443"
|
||||
fi
|
||||
|
||||
# 展示结果
|
||||
echo ""
|
||||
echo "✅ VXLAN Overlay 已建立"
|
||||
echo " - bridge: $BR_IF ($BRIDGE_CIDR)"
|
||||
echo " - vxlan: $VXLAN_IF ($LOCAL_IP ⇄ $REMOTE_IP, id=$VNI, mtu=$MTU)"
|
||||
echo " - SNAT: $SUBNET → $DEV_IF"
|
||||
if [[ -n "$EXPOSE_PORT" ]]; then
|
||||
echo " - DNAT: 公网:$EXPOSE_PORT → ${BRIDGE_IP}:443"
|
||||
fi
|
||||
50
roles/vhosts/vpn-overlay/vxlan/hub/tasks/main.yml
Normal file
50
roles/vhosts/vpn-overlay/vxlan/hub/tasks/main.yml
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
- name: Load site overlay config
|
||||
set_fact:
|
||||
overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: Select current site from config
|
||||
set_fact:
|
||||
current_hub: "{{ overlay_data.hubs | selectattr('name', 'equalto', inventory_hostname) | list | first }}"
|
||||
|
||||
- name: Fail if no matching site found
|
||||
fail:
|
||||
msg: "当前主机 {{ inventory_hostname }} 未在 config 中定义"
|
||||
when: current_hub is not defined
|
||||
|
||||
- name: Set VXLAN parameters
|
||||
set_fact:
|
||||
vxlan_dev_if: "{{ current_hub.interface}}"
|
||||
vxlan_local_ip: "{{ current_hub.local_ip }}"
|
||||
vxlan_remote_ip: "{{ current_hub.remote_ip }}"
|
||||
vxlan_br_ip: "{{ current_hub.br_ip }}"
|
||||
vxlan_cidr_suffix: "{{ overlay_data.vxlan_cidr_suffix | default(16) }}"
|
||||
vxlan_vni: "{{ overlay_data.vxlan_id | default(100) }}"
|
||||
|
||||
- name: 使用 rsync 分发 VXLAN 脚本
|
||||
synchronize:
|
||||
src: "files/setup_sit_vxlan.sh"
|
||||
dest: /tmp/setup_sit_vxlan.sh
|
||||
mode: push
|
||||
|
||||
- name: 移动脚本并赋权到 /usr/local/bin
|
||||
shell: |
|
||||
mv /tmp/setup_sit_vxlan.sh /usr/local/bin/setup_sit_vxlan.sh
|
||||
chmod +x /usr/local/bin/setup_sit_vxlan.sh
|
||||
become: true
|
||||
|
||||
- name: Render systemd unit for VXLAN
|
||||
template:
|
||||
src: vxlan-setup.service.j2
|
||||
dest: /etc/systemd/system/vxlan-setup.service
|
||||
mode: '0644'
|
||||
|
||||
- name: Reload systemd daemon
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Enable and start VXLAN overlay setup
|
||||
systemd:
|
||||
name: vxlan-setup
|
||||
enabled: true
|
||||
state: started
|
||||
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=VXLAN Overlay Setup for {{ inventory_hostname }}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/setup_sit_vxlan.sh {{ vxlan_dev_if }} {{ vxlan_local_ip }} {{ vxlan_remote_ip }} {{ vxlan_br_ip }} {{ vxlan_cidr_suffix }} {{ vxlan_vni }}
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
7
roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml
Normal file
7
roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
# Default VXLAN settings for site nodes
|
||||
vxlan_dev_if: wg0
|
||||
vxlan_vni: 100
|
||||
vxlan_cidr_suffix: 16
|
||||
overlay_config_path: config/sit/vpn-overlay.yaml
|
||||
|
||||
104
roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh
Normal file
104
roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh
Normal file
@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# 多 peer 自动化 VXLAN Overlay 脚本(读取 /etc/vxlan-config.yaml)
|
||||
# 用法: ./setup_sit_vxlan.sh [reset]
|
||||
|
||||
set -e
|
||||
|
||||
CONFIG_FILE="/etc/vxlan-config.yaml"
|
||||
BR_IF="br0"
|
||||
|
||||
# 需要 yq 解析 yaml
|
||||
command -v yq >/dev/null 2>&1 || { echo >&2 "❌ 请安装 yq 命令(https://github.com/mikefarah/yq)"; exit 1; }
|
||||
|
||||
if [[ "$1" == "reset" ]]; then
|
||||
echo "🔄 正在清理 VXLAN Overlay 配置..."
|
||||
|
||||
# 删除所有 vxlan 接口
|
||||
ip -o link show | awk -F': ' '/vxlan[0-9]+/ {print $2}' | while read -r iface; do
|
||||
ip link set "$iface" down
|
||||
ip link del "$iface"
|
||||
echo "🧹 已删除接口 $iface"
|
||||
done
|
||||
|
||||
ip link show "$BR_IF" &>/dev/null && {
|
||||
ip link set "$BR_IF" down
|
||||
ip link del "$BR_IF"
|
||||
echo "🧹 已删除桥接器 $BR_IF"
|
||||
}
|
||||
|
||||
echo "✅ 清理完成"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 解析 config
|
||||
DEV_IF="$(yq e '.dev_if' "$CONFIG_FILE")"
|
||||
BRIDGE_IP="$(yq e '.bridge_ip' "$CONFIG_FILE")"
|
||||
CIDR_SUFFIX="$(yq e '.cidr_suffix' "$CONFIG_FILE")"
|
||||
PEER_COUNT=$(yq e '.peers | length' "$CONFIG_FILE")
|
||||
|
||||
if [[ -z "$DEV_IF" || -z "$BRIDGE_IP" || "$PEER_COUNT" -eq 0 ]]; then
|
||||
echo "❌ 配置错误:请检查 $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BRIDGE_CIDR="${BRIDGE_IP}/${CIDR_SUFFIX}"
|
||||
|
||||
# 检查 dev_if 是否可用于 VXLAN
|
||||
function is_vxlan_dev_usable() {
|
||||
[[ -d "/sys/class/net/$1" ]] && grep -q "broadcast" "/sys/class/net/$1/flags"
|
||||
}
|
||||
|
||||
echo "🔍 检查 $DEV_IF 是否可用于 VXLAN..."
|
||||
USE_DEV_PARAM=true
|
||||
if ! is_vxlan_dev_usable "$DEV_IF"; then
|
||||
echo "⚠️ $DEV_IF 不支持广播,将省略 dev 参数(通过路由走隧道)"
|
||||
USE_DEV_PARAM=false
|
||||
fi
|
||||
|
||||
# 创建 bridge
|
||||
if ! ip link show "$BR_IF" &>/dev/null; then
|
||||
echo "🛠️ 创建桥接器 $BR_IF"
|
||||
ip link add "$BR_IF" type bridge
|
||||
ip link set "$BR_IF" up
|
||||
ip addr add "$BRIDGE_CIDR" dev "$BR_IF"
|
||||
fi
|
||||
|
||||
# 启用转发
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
|
||||
# 遍历 peers
|
||||
for i in $(seq 0 $((PEER_COUNT - 1))); do
|
||||
LOCAL_IP=$(yq e ".peers[$i].local_ip" "$CONFIG_FILE")
|
||||
REMOTE_IP=$(yq e ".peers[$i].remote_ip" "$CONFIG_FILE")
|
||||
VNI=$(yq e ".peers[$i].vxlan_id" "$CONFIG_FILE")
|
||||
MTU=$(yq e ".peers[$i].mtu" "$CONFIG_FILE")
|
||||
EXPOSE_PORT=$(yq e ".peers[$i].expose_port" "$CONFIG_FILE")
|
||||
|
||||
VXLAN_IF="vxlan${VNI}"
|
||||
|
||||
echo "🛠️ 创建 VXLAN 接口 $VXLAN_IF (local: $LOCAL_IP, remote: $REMOTE_IP, vni: $VNI)"
|
||||
|
||||
# 清理旧接口
|
||||
ip link show "$VXLAN_IF" &>/dev/null && ip link set "$VXLAN_IF" down && ip link del "$VXLAN_IF"
|
||||
|
||||
# 创建 vxlan 接口
|
||||
if $USE_DEV_PARAM; then
|
||||
ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" dev "$DEV_IF"
|
||||
else
|
||||
ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP"
|
||||
fi
|
||||
ip link set "$VXLAN_IF" mtu "$MTU"
|
||||
ip link set "$VXLAN_IF" up
|
||||
ip link set "$VXLAN_IF" master "$BR_IF"
|
||||
|
||||
# 可选添加 DNAT
|
||||
if [[ -n "$EXPOSE_PORT" && "$EXPOSE_PORT" != "null" ]]; then
|
||||
echo "🌐 添加 DNAT 规则:公网:$EXPOSE_PORT → ${BRIDGE_IP}:443"
|
||||
iptables -t nat -C PREROUTING -p tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443" 2>/dev/null || \
|
||||
iptables -t nat -A PREROUTING -p tcp --dport "$EXPOSE_PORT" -j DNAT --to-destination "${BRIDGE_IP}:443"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ 所有 VXLAN Overlay 配置完成"
|
||||
54
roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml
Normal file
54
roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
- name: Load site overlay config
|
||||
set_fact:
|
||||
overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: Select current site from config
|
||||
set_fact:
|
||||
current_site: "{{ overlay_data.sites | selectattr('name', 'equalto', inventory_hostname) | list | first }}"
|
||||
|
||||
- name: Fail if no matching site found
|
||||
fail:
|
||||
msg: "当前主机 {{ inventory_hostname }} 未在 config 中定义"
|
||||
when: current_site is not defined
|
||||
|
||||
- name: Select first hub as default
|
||||
set_fact:
|
||||
selected_hub: "{{ overlay_data.hubs[0] }}"
|
||||
|
||||
- name: Set VXLAN parameters
|
||||
set_fact:
|
||||
vxlan_dev_if: "{{ current_site.interface}}"
|
||||
vxlan_local_ip: "{{ current_site.local_ip }}"
|
||||
vxlan_remote_ip: "{{ current_site.remote_ip }}"
|
||||
vxlan_br_ip: "{{ current_site.br_ip }}"
|
||||
vxlan_cidr_suffix: "{{ overlay_data.vxlan_cidr_suffix | default(16) }}"
|
||||
vxlan_vni: "{{ overlay_data.vxlan_id | default(100) }}"
|
||||
|
||||
- name: 使用 rsync 分发 VXLAN 脚本
|
||||
synchronize:
|
||||
src: "files/setup_sit_vxlan.sh"
|
||||
dest: /tmp/setup_sit_vxlan.sh
|
||||
mode: push
|
||||
|
||||
- name: 移动脚本并赋权到 /usr/local/bin
|
||||
shell: |
|
||||
mv /tmp/setup_sit_vxlan.sh /usr/local/bin/setup_sit_vxlan.sh
|
||||
chmod +x /usr/local/bin/setup_sit_vxlan.sh
|
||||
become: true
|
||||
|
||||
- name: Render systemd unit for VXLAN
|
||||
template:
|
||||
src: vxlan-setup.service.j2
|
||||
dest: /etc/systemd/system/vxlan-setup.service
|
||||
mode: '0644'
|
||||
|
||||
- name: Reload systemd daemon
|
||||
systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Enable and start VXLAN overlay setup
|
||||
systemd:
|
||||
name: vxlan-setup
|
||||
enabled: true
|
||||
state: started
|
||||
@ -0,0 +1,12 @@
|
||||
dev_if: eth0
|
||||
bridge_ip: 10.253.253.1
|
||||
cidr_suffix: 16
|
||||
peers:
|
||||
- local_ip: 172.30.0.1
|
||||
remote_ip: 172.30.0.10
|
||||
vxlan_id: 100
|
||||
mtu: 1400
|
||||
- local_ip: 172.30.0.1
|
||||
remote_ip: 172.31.0.1
|
||||
vxlan_id: 100
|
||||
mtu: 1400
|
||||
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=VXLAN Overlay Setup for {{ inventory_hostname }}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/setup_sit_vxlan.sh add {{ vxlan_dev_if }} {{ vxlan_local_ip }} {{ vxlan_remote_ip }} {{ vxlan_br_ip }} {{ vxlan_cidr_suffix }} {{ vxlan_vni }}
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
4
roles/vhosts/vpn-overlay/wireguard/hub/defaults/main.yml
Normal file
4
roles/vhosts/vpn-overlay/wireguard/hub/defaults/main.yml
Normal file
@ -0,0 +1,4 @@
|
||||
wg_interface: "wg0"
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
wg_port: "51820"
|
||||
56
roles/vhosts/vpn-overlay/wireguard/hub/tasks/main.yml
Normal file
56
roles/vhosts/vpn-overlay/wireguard/hub/tasks/main.yml
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
- name: 加载 overlay 配置(标准 YAML)
|
||||
set_fact:
|
||||
overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: 加载密钥配置(支持 !vault)
|
||||
include_vars:
|
||||
file: "{{ overlay_keys_path }}"
|
||||
name: overlay_keys
|
||||
|
||||
- name: 提取当前 Hub 节点信息
|
||||
set_fact:
|
||||
current_node: >-
|
||||
{{ overlay_data.hubs | selectattr('name', 'equalto', inventory_hostname) | list | first }}
|
||||
current_key: >-
|
||||
{{ overlay_keys['keys'] | selectattr('name', 'equalto', inventory_hostname) | list | first }}
|
||||
|
||||
- name: 提取所有对端 Peer 信息
|
||||
set_fact:
|
||||
peer_nodes: >-
|
||||
{{ (overlay_data.sites + overlay_data.hubs)
|
||||
| selectattr('name', 'in', current_node.wireguard_peer) | list }}
|
||||
peer_keys: >-
|
||||
{{ overlay_keys['keys']
|
||||
| selectattr('name', 'in', current_node.wireguard_peer) | list }}
|
||||
|
||||
- name: 校验 Peer 节点数量
|
||||
fail:
|
||||
msg: "没有找到任何对端节点或对应密钥,请检查 wireguard_peer 设置"
|
||||
when: peer_nodes | length == 0 or peer_keys | length == 0
|
||||
|
||||
- name: Ensure wireguard-tools is installed (Debian/Ubuntu)
|
||||
apt:
|
||||
name: wireguard-tools
|
||||
state: present
|
||||
when: ansible_os_family == 'Debian'
|
||||
|
||||
- name: Ensure wireguard-tools is installed (RHEL/CentOS)
|
||||
yum:
|
||||
name: wireguard-tools
|
||||
state: present
|
||||
when: ansible_os_family == 'RedHat'
|
||||
|
||||
- name: 渲染 wg0.conf
|
||||
template:
|
||||
src: wg0.conf.j2
|
||||
dest: /etc/wireguard/wg0.conf
|
||||
mode: '0600'
|
||||
become: true
|
||||
|
||||
- name: 启用并启动 wg-quick@wg0
|
||||
systemd:
|
||||
name: wg-quick@wg0
|
||||
enabled: true
|
||||
state: started
|
||||
become: true
|
||||
16
roles/vhosts/vpn-overlay/wireguard/hub/templates/wg0.conf.j2
Normal file
16
roles/vhosts/vpn-overlay/wireguard/hub/templates/wg0.conf.j2
Normal file
@ -0,0 +1,16 @@
|
||||
[Interface]
|
||||
PrivateKey = {{ current_key.private_key }}
|
||||
Address = {{ current_node.wg_ip }}/32
|
||||
ListenPort = {{ wg_port }}
|
||||
DNS = 8.8.8.8
|
||||
MTU = 1400
|
||||
|
||||
{% for peer, key in peer_nodes | zip(peer_keys) %}
|
||||
{% if peer.name != inventory_hostname %}
|
||||
[Peer]
|
||||
PublicKey = {{ key.public_key }}
|
||||
AllowedIPs = {{ peer.wg_ip }}/32
|
||||
Endpoint = {{ peer.public_ip }}:{{ overlay_data.hub_port | default(wg_port) }}
|
||||
PersistentKeepalive = 25
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@ -0,0 +1,8 @@
|
||||
wg_interface: "wg0"
|
||||
|
||||
# 默认配置文件路径,可在 playbook 中覆盖
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
|
||||
# WireGuard 默认端口(若 overlay_data.hub_port 未定义)
|
||||
wg_port: "51820"
|
||||
71
roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml
Normal file
71
roles/vhosts/vpn-overlay/wireguard/site/tasks/main.yml
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
- name: 加载 overlay 配置(标准 YAML)
|
||||
set_fact:
|
||||
overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: 加载密钥配置(支持 !vault)
|
||||
include_vars:
|
||||
file: "{{ overlay_keys_path }}"
|
||||
name: overlay_keys
|
||||
|
||||
- name: 提取当前节点信息(作为 current)
|
||||
set_fact:
|
||||
current_node: >-
|
||||
{{ (overlay_data.sites + overlay_data.hubs)
|
||||
| selectattr('name', 'equalto', inventory_hostname)
|
||||
| list | first }}
|
||||
current_key: >-
|
||||
{{ overlay_keys['keys']
|
||||
| selectattr('name', 'equalto', inventory_hostname)
|
||||
| list | first }}
|
||||
features: "{{ overlay_data.features }}"
|
||||
|
||||
- name: 标准化 wireguard_peer 为列表
|
||||
set_fact:
|
||||
wireguard_peers: >-
|
||||
{{ [current_node.wireguard_peer] if current_node.wireguard_peer is string else current_node.wireguard_peer }}
|
||||
|
||||
- name: 提取对端 peer 节点列表
|
||||
set_fact:
|
||||
peer_node_list: >-
|
||||
{{ (overlay_data.sites + overlay_data.hubs)
|
||||
| selectattr('name', 'in', wireguard_peers)
|
||||
| list }}
|
||||
peer_key_list: >-
|
||||
{{ overlay_keys['keys']
|
||||
| selectattr('name', 'in', wireguard_peers)
|
||||
| list }}
|
||||
|
||||
- name: 校验 wireguard_peer 是否匹配成功
|
||||
fail:
|
||||
msg: "未找到对端节点 '{{ current_node.wireguard_peer }}' 或其密钥"
|
||||
when: peer_node_list | length == 0 or peer_key_list | length == 0
|
||||
|
||||
- name: 设置对端节点与密钥
|
||||
set_fact:
|
||||
peer_nodes: "{{ peer_node_list }}"
|
||||
peer_keys: "{{ peer_key_list }}"
|
||||
|
||||
- name: 提取最终配置变量(私钥、公钥、端口等)
|
||||
set_fact:
|
||||
wg_port: "{{ wg_port }}"
|
||||
current_wg_ip: "{{ current_node.wg_ip }}"
|
||||
current_interface: "{{ current_node.interface }}"
|
||||
current_private_key: "{{ current_key.private_key }}"
|
||||
current_allowed_ips: "{{ current_node.allowed_ips }}"
|
||||
peer_public_key: "{{ peer_keys[0].public_key }}"
|
||||
peer_endpoint: "{{ peer_nodes[0].public_ip }}:{{ overlay_data.hub_port | default(wg_port) }}"
|
||||
|
||||
- name: 渲染 wg0.conf
|
||||
template:
|
||||
src: wg0.conf.j2
|
||||
dest: /etc/wireguard/wg0.conf
|
||||
mode: '0600'
|
||||
become: true
|
||||
|
||||
- name: 启用并启动 wg-quick@wg0
|
||||
systemd:
|
||||
name: wg-quick@wg0
|
||||
enabled: true
|
||||
state: started
|
||||
become: true
|
||||
@ -0,0 +1,24 @@
|
||||
[Interface]
|
||||
PrivateKey = {{ current_private_key }}
|
||||
Address = {{ current_wg_ip }}/32
|
||||
ListenPort = {{ wg_port }}
|
||||
DNS = 8.8.8.8
|
||||
MTU = 1400
|
||||
|
||||
PostUp = iptables -I FORWARD -o %i -j ACCEPT
|
||||
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o {{ current_interface }} -j MASQUERADE
|
||||
|
||||
PostDown = iptables -D FORWARD -o %i -j ACCEPT
|
||||
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o {{ current_interface }} -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
PublicKey = {{ peer_public_key }}
|
||||
AllowedIPs = {{ current_allowed_ips }}
|
||||
{% if features.enable_vless %}
|
||||
Endpoint = 127.0.0.1:51830
|
||||
{% else %}
|
||||
Endpoint = {{ peer_endpoint }}
|
||||
{% endif %}
|
||||
PersistentKeepalive = 25
|
||||
5
roles/vhosts/vpn-overlay/xray/hub/defaults/main.yml
Normal file
5
roles/vhosts/vpn-overlay/xray/hub/defaults/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
xray_main_port: 1443
|
||||
xray_cert_path: "/etc/ssl/onwalk.net.pem"
|
||||
xray_key_path: "/etc/ssl/onwalk.net.key"
|
||||
xray_bin_path: /usr/local/bin/xray
|
||||
xray_config_dir: /usr/local/etc/xray
|
||||
6
roles/vhosts/vpn-overlay/xray/hub/handlers/main.yml
Normal file
6
roles/vhosts/vpn-overlay/xray/hub/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart xray service
|
||||
systemd:
|
||||
name: xray.service
|
||||
state: restarted
|
||||
enabled: yes
|
||||
70
roles/vhosts/vpn-overlay/xray/hub/tasks/main.yml
Normal file
70
roles/vhosts/vpn-overlay/xray/hub/tasks/main.yml
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
- name: Load overlay config from file
|
||||
set_fact:
|
||||
overlay_config: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: Convert overlay_config.hubs list to dict (hubs_map)
|
||||
set_fact:
|
||||
hubs_map: "{{ dict(overlay_config.hubs | map(attribute='name') | zip(overlay_config.hubs)) }}"
|
||||
when: overlay_config.hubs is defined
|
||||
|
||||
- name: Convert overlay_config.sites list to dict (sites_map)
|
||||
set_fact:
|
||||
sites_map: "{{ dict(overlay_config.sites | map(attribute='name') | zip(overlay_config.sites)) }}"
|
||||
when: overlay_config.sites is defined
|
||||
|
||||
- name: 显示主机名
|
||||
debug:
|
||||
var: overlay_config
|
||||
when: debug | default(false)
|
||||
|
||||
- set_fact:
|
||||
xray_uuid: "{{ hubs_map[inventory_hostname].xray.uuid }}"
|
||||
xray_remote_domain: "{{ hubs_map[inventory_hostname].xray.remote_domain }}"
|
||||
xray_cert_path: "{{ hubs_map[inventory_hostname].xray.cert_path }}"
|
||||
xray_key_path: "{{ hubs_map[inventory_hostname].xray.key_path }}"
|
||||
|
||||
- name: Install Xray using official script
|
||||
shell: |
|
||||
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)"
|
||||
args:
|
||||
creates: /usr/local/bin/xray
|
||||
notify:
|
||||
- Restart xray service
|
||||
|
||||
- name: Ensure required directories exist
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ xray_bin_path | dirname }}"
|
||||
- "{{ xray_config_dir }}"
|
||||
|
||||
- name: Deploy Xray config templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "config.json.j2", dest: "{{ xray_config_dir }}/config.json" }
|
||||
|
||||
- name: Deploy systemd service templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "xray.service.j2", dest: "/etc/systemd/system/xray.service" }
|
||||
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Enable and start xray services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: yes
|
||||
state: started
|
||||
loop:
|
||||
- xray.service
|
||||
84
roles/vhosts/vpn-overlay/xray/hub/templates/config.json.j2
Normal file
84
roles/vhosts/vpn-overlay/xray/hub/templates/config.json.j2
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning"
|
||||
},
|
||||
"routing": {
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"geoip:cn"
|
||||
],
|
||||
"outboundTag": "block"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "0.0.0.0",
|
||||
"port": {{ xray_main_port }},
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "{{ xray_uuid }}",
|
||||
"flow": "xtls-rprx-vision"
|
||||
}
|
||||
],
|
||||
"decryption": "none",
|
||||
"fallbacks": [
|
||||
{
|
||||
"dest": "8001",
|
||||
"xver": 1
|
||||
},
|
||||
{
|
||||
"alpn": "h2",
|
||||
"dest": "8002",
|
||||
"xver": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"rejectUnknownSni": true,
|
||||
"minVersion": "1.2",
|
||||
"certificates": [
|
||||
{
|
||||
"ocspStapling": 3600,
|
||||
"certificateFile": "{{ xray_cert_path }}",
|
||||
"keyFile": "{{ xray_key_path }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"handshake": 2,
|
||||
"connIdle": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
roles/vhosts/vpn-overlay/xray/hub/templates/xray.service.j2
Normal file
18
roles/vhosts/vpn-overlay/xray/hub/templates/xray.service.j2
Normal file
@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Xray Service
|
||||
Documentation=https://github.com/xtls
|
||||
After=network.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
User=nobody
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
NoNewPrivileges=true
|
||||
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/config.json
|
||||
Restart=on-failure
|
||||
RestartPreventExitStatus=23
|
||||
LimitNPROC=10000
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
6
roles/vhosts/vpn-overlay/xray/site/defaults/main.yml
Normal file
6
roles/vhosts/vpn-overlay/xray/site/defaults/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
xray_main_port: 1443
|
||||
xray_tproxy_port: 51830
|
||||
xray_cert_path: "/etc/ssl/onwalk.net.pem"
|
||||
xray_key_path: "/etc/ssl/onwalk.net.key"
|
||||
xray_bin_path: /usr/local/bin/xray
|
||||
xray_config_dir: /usr/local/etc/xray
|
||||
6
roles/vhosts/vpn-overlay/xray/site/handlers/main.yml
Normal file
6
roles/vhosts/vpn-overlay/xray/site/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart Xray-client service
|
||||
systemd:
|
||||
name: xray-client.service
|
||||
state: restarted
|
||||
enabled: yes
|
||||
70
roles/vhosts/vpn-overlay/xray/site/tasks/main.yml
Normal file
70
roles/vhosts/vpn-overlay/xray/site/tasks/main.yml
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
- name: Load overlay config from file
|
||||
set_fact:
|
||||
overlay_config: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: Convert overlay_config.hubs list to dict (hubs_map)
|
||||
set_fact:
|
||||
hubs_map: "{{ dict(overlay_config.hubs | map(attribute='name') | zip(overlay_config.hubs)) }}"
|
||||
when: overlay_config.hubs is defined
|
||||
|
||||
- name: Convert overlay_config.sites list to dict (sites_map)
|
||||
set_fact:
|
||||
sites_map: "{{ dict(overlay_config.sites | map(attribute='name') | zip(overlay_config.sites)) }}"
|
||||
when: overlay_config.sites is defined
|
||||
|
||||
- name: 显示主机名
|
||||
debug:
|
||||
var: overlay_config
|
||||
when: debug | default(false)
|
||||
|
||||
- set_fact:
|
||||
xray_uuid: "{{ sites_map[inventory_hostname].xray.uuid }}"
|
||||
xray_remote_domain: "{{ sites_map[inventory_hostname].xray.remote_domain }}"
|
||||
xray_cert_path: "{{ sites_map[inventory_hostname].xray.cert_path }}"
|
||||
xray_key_path: "{{ sites_map[inventory_hostname].xray.key_path }}"
|
||||
|
||||
- name: Install Xray using official script
|
||||
shell: |
|
||||
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)"
|
||||
args:
|
||||
creates: /usr/local/bin/xray
|
||||
notify:
|
||||
- Restart xray service
|
||||
|
||||
- name: Ensure required directories exist
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ xray_bin_path | dirname }}"
|
||||
- "{{ xray_config_dir }}"
|
||||
|
||||
- name: Deploy Xray config templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "client-config.json.j2", dest: "{{ xray_config_dir }}/client-config.json" }
|
||||
|
||||
- name: Deploy systemd service templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "xray-client.service.j2", dest: "/etc/systemd/system/xray-client.service" }
|
||||
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Enable and start xray services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: yes
|
||||
state: started
|
||||
loop:
|
||||
- xray-client.service
|
||||
@ -0,0 +1,94 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"routing": {
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"domain": [
|
||||
"geosite:cn",
|
||||
"geosite:private"
|
||||
],
|
||||
"outboundTag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"geoip:cn",
|
||||
"geoip:private"
|
||||
],
|
||||
"outboundTag": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1080,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"udp": true
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 1081,
|
||||
"protocol": "http",
|
||||
"settings": {},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "{{ xray_remote_domain }}",
|
||||
"port": {{ xray_main_port }},
|
||||
"users": [
|
||||
{
|
||||
"id": "{{ xray_uuid }}",
|
||||
"encryption": "none",
|
||||
"flow": "xtls-rprx-vision"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"serverName": "{{ xray_remote_domain }}",
|
||||
"allowInsecure": false,
|
||||
"fingerprint": "chrome"
|
||||
}
|
||||
},
|
||||
"tag": "proxy"
|
||||
},
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Xray Client Service
|
||||
Documentation=https://github.com/xtls
|
||||
After=network.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
User=nobody
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
NoNewPrivileges=true
|
||||
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/client-config.json
|
||||
Restart=on-failure
|
||||
RestartPreventExitStatus=23
|
||||
LimitNPROC=10000
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
6
roles/vhosts/vpn-overlay/xray/tproxy/defaults/main.yml
Normal file
6
roles/vhosts/vpn-overlay/xray/tproxy/defaults/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
xray_main_port: 1443
|
||||
xray_tproxy_port: 51830
|
||||
xray_cert_path: "/etc/ssl/onwalk.net.pem"
|
||||
xray_key_path: "/etc/ssl/onwalk.net.key"
|
||||
xray_bin_path: /usr/local/bin/xray
|
||||
xray_config_dir: /usr/local/etc/xray
|
||||
6
roles/vhosts/vpn-overlay/xray/tproxy/handlers/main.yml
Normal file
6
roles/vhosts/vpn-overlay/xray/tproxy/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart xray-tproxy service
|
||||
systemd:
|
||||
name: xray-tproxy.service
|
||||
state: restarted
|
||||
enabled: yes
|
||||
82
roles/vhosts/vpn-overlay/xray/tproxy/tasks/main.yml
Normal file
82
roles/vhosts/vpn-overlay/xray/tproxy/tasks/main.yml
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
- name: Load overlay config from file
|
||||
set_fact:
|
||||
overlay_config: "{{ lookup('file', overlay_config_path) | from_yaml }}"
|
||||
|
||||
- name: Convert overlay_config.hubs list to dict (hubs_map)
|
||||
set_fact:
|
||||
hubs_map: "{{ dict(overlay_config.hubs | map(attribute='name') | zip(overlay_config.hubs)) }}"
|
||||
when: overlay_config.hubs is defined
|
||||
|
||||
- name: Convert overlay_config.sites list to dict (sites_map)
|
||||
set_fact:
|
||||
sites_map: "{{ dict(overlay_config.sites | map(attribute='name') | zip(overlay_config.sites)) }}"
|
||||
when: overlay_config.sites is defined
|
||||
|
||||
- name: Convert all nodes (hubs + sites) to one dict as node_map
|
||||
set_fact:
|
||||
node_map: >-
|
||||
{{ dict((overlay_config.hubs + overlay_config.sites)
|
||||
| map(attribute='name')
|
||||
| zip(overlay_config.hubs + overlay_config.sites)) }}
|
||||
|
||||
- name: 显示主机名
|
||||
debug:
|
||||
var: node_map
|
||||
when: debug | default(true)
|
||||
|
||||
- name: Show value for this node
|
||||
debug:
|
||||
msg: "{{ node_map[inventory_hostname] }}"
|
||||
when: debug | default(true)
|
||||
|
||||
- set_fact:
|
||||
xray_uuid: "{{ node_map[inventory_hostname].xray.uuid }}"
|
||||
xray_remote_domain: "{{ node_map[inventory_hostname].xray.remote_domain }}"
|
||||
xray_cert_path: "{{ node_map[inventory_hostname].xray.cert_path }}"
|
||||
xray_key_path: "{{ node_map[inventory_hostname].xray.key_path }}"
|
||||
|
||||
- name: Install Xray using official script
|
||||
shell: |
|
||||
bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)"
|
||||
args:
|
||||
creates: /usr/local/bin/xray
|
||||
notify:
|
||||
- Restart xray-tproxy service
|
||||
|
||||
- name: Ensure required directories exist
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ xray_bin_path | dirname }}"
|
||||
- "{{ xray_config_dir }}"
|
||||
|
||||
- name: Deploy Xray config templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "tproxy-config.json.j2", dest: "{{ xray_config_dir }}/tproxy-config.json" }
|
||||
|
||||
- name: Deploy systemd service templates
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "xray-tproxy.service.j2", dest: "/etc/systemd/system/xray-tproxy.service" }
|
||||
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Enable and start xray services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
enabled: yes
|
||||
state: started
|
||||
loop:
|
||||
- xray-tproxy.service
|
||||
@ -0,0 +1,58 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "info"
|
||||
},
|
||||
"routing": {
|
||||
"rules": []
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 51830,
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "{{ xray_remote_domain }}",
|
||||
"port": 51820,
|
||||
"network": "udp"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"vnext": [
|
||||
{
|
||||
"address": "{{ xray_remote_domain }}",
|
||||
"port": 1443,
|
||||
"users": [
|
||||
{
|
||||
"id": "{{ xray_uuid }}",
|
||||
"encryption": "none",
|
||||
"flow": "xtls-rprx-vision"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "tcp",
|
||||
"security": "tls",
|
||||
"tlsSettings": {
|
||||
"serverName": "{{ xray_remote_domain }}",
|
||||
"allowInsecure": false,
|
||||
"fingerprint": "chrome"
|
||||
}
|
||||
},
|
||||
"tag": "proxy"
|
||||
},
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Xray Tproxy Service
|
||||
Documentation=https://github.com/xtls
|
||||
After=network.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
User=nobody
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
||||
NoNewPrivileges=true
|
||||
ExecStart=/usr/local/bin/xray run -config /usr/local/etc/xray/tproxy-config.json
|
||||
Restart=on-failure
|
||||
RestartPreventExitStatus=23
|
||||
LimitNPROC=10000
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
8
vpn-overlay-dnat.yaml
Normal file
8
vpn-overlay-dnat.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup DNAT rules
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: no
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
roles:
|
||||
- vhosts/vpn-overlay/setup-dnat
|
||||
8
vpn-overlay-vxlan-hub.yaml
Normal file
8
vpn-overlay-vxlan-hub.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Run infrastructure setup
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
roles:
|
||||
- vhosts/vpn-overlay/vxlan/hub
|
||||
8
vpn-overlay-vxlan-site.yaml
Normal file
8
vpn-overlay-vxlan-site.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Run infrastructure setup
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
roles:
|
||||
- vhosts/vpn-overlay/vxlan/site
|
||||
8
vpn-wireguard-hub.yaml
Normal file
8
vpn-wireguard-hub.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup WireGuard for hub
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
roles:
|
||||
- role: vhosts/vpn-overlay/wireguard/hub
|
||||
8
vpn-wireguard-site.yaml
Normal file
8
vpn-wireguard-site.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup WireGuard for site
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
roles:
|
||||
- vhosts/vpn-overlay/wireguard/site
|
||||
8
vpn-xray-client.yaml
Normal file
8
vpn-xray-client.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup Xray for hub
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
roles:
|
||||
- role: vhosts/vpn-overlay/xray/site
|
||||
8
vpn-xray-hub.yaml
Normal file
8
vpn-xray-hub.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup Xray for hub
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
roles:
|
||||
- role: vhosts/vpn-overlay/xray/hub
|
||||
8
vpn-xray-tproxy.yaml
Normal file
8
vpn-xray-tproxy.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: Setup Xray for hub
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml"
|
||||
overlay_keys_path: "{{ playbook_dir }}/../../config/sit/vpn-keys.yaml"
|
||||
roles:
|
||||
- role: vhosts/vpn-overlay/xray/tproxy/
|
||||
Loading…
Reference in New Issue
Block a user