feat(vhosts): add Alicloud DNS record module and batch sync role
This commit is contained in:
parent
fae2d7b4d7
commit
e4cf88d3c3
12
playbooks/alicloud_dns_record.yml
Normal file
12
playbooks/alicloud_dns_record.yml
Normal file
@ -0,0 +1,12 @@
|
||||
- name: setup OpenResty server
|
||||
hosts: global-homepage.svc.plus
|
||||
become: true
|
||||
vars:
|
||||
alicloud_dns_domain: "svc.plus"
|
||||
alicloud_dns_rr: "www"
|
||||
alicloud_dns_type: "A"
|
||||
alicloud_dns_value: "1.2.3.4"
|
||||
alicloud_access_key: "{{ aliyun_ak }}"
|
||||
alicloud_secret_key: "{{ aliyun_sk }}"
|
||||
roles:
|
||||
- role: vhosts/alicloud_dns_record
|
||||
16
playbooks/alicloud_dns_sync.yml
Normal file
16
playbooks/alicloud_dns_sync.yml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
|
||||
# 动态加载 DNS 配置文件
|
||||
vars_files:
|
||||
- vars/dns_records_svc_plus.yaml # ← 可以切换成不同环境
|
||||
|
||||
# 如果你想在命令行覆盖 AK/SK,则可以使用 --extra-vars
|
||||
vars:
|
||||
alicloud_access_key: "{{ aliyun_ak | default('') }}"
|
||||
alicloud_secret_key: "{{ aliyun_sk | default('') }}"
|
||||
|
||||
roles:
|
||||
- role: vhosts/alicloud_dns_sync
|
||||
|
||||
8
playbooks/deploy_xcontrol_web.yml
Normal file
8
playbooks/deploy_xcontrol_web.yml
Normal file
@ -0,0 +1,8 @@
|
||||
- name: setup xcontrol web
|
||||
hosts: all
|
||||
become: true
|
||||
vars:
|
||||
group: mail
|
||||
roles:
|
||||
#- roles/vhosts/common/
|
||||
- roles/vhosts/nodejs/
|
||||
1
playbooks/docs/alicloud_dns_sync.md
Normal file
1
playbooks/docs/alicloud_dns_sync.md
Normal file
@ -0,0 +1 @@
|
||||
ansible-playbook batch_dns_sync.yml --extra-vars "aliyun_ak=XXXX aliyun_sk=YYYY"
|
||||
17
playbooks/roles/vhosts/alicloud_dns_record/defaults/main.yml
Normal file
17
playbooks/roles/vhosts/alicloud_dns_record/defaults/main.yml
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Ensure Alicloud DNS Record
|
||||
alicloud_dns_record:
|
||||
state: present
|
||||
domain: "{{ alicloud_dns_domain }}"
|
||||
rr: "{{ alicloud_dns_rr }}"
|
||||
type: "{{ alicloud_dns_type }}"
|
||||
value: "{{ alicloud_dns_value }}"
|
||||
ttl: "{{ alicloud_dns_ttl }}"
|
||||
priority: "{{ alicloud_dns_priority }}"
|
||||
access_key_id: "{{ alicloud_access_key }}"
|
||||
access_key_secret: "{{ alicloud_secret_key }}"
|
||||
security_token: "{{ alicloud_security_token }}"
|
||||
register: dns_result
|
||||
|
||||
- debug:
|
||||
var: dns_result
|
||||
@ -0,0 +1,167 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from alibabacloud_alidns20150109.client import Client as Alidns20150109Client
|
||||
from alibabacloud_credentials.client import Client as CredentialClient
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
from alibabacloud_alidns20150109 import models as alidns_models
|
||||
|
||||
|
||||
# Build Client (AK/SK 优先 → STS → Credential Chain)
|
||||
def create_client(access_key_id=None, access_key_secret=None, security_token=None):
|
||||
if access_key_id and access_key_secret:
|
||||
config = open_api_models.Config(
|
||||
access_key_id=access_key_id,
|
||||
access_key_secret=access_key_secret,
|
||||
security_token=security_token
|
||||
)
|
||||
config.endpoint = "alidns.aliyuncs.com"
|
||||
return Alidns20150109Client(config)
|
||||
|
||||
credential = CredentialClient()
|
||||
config = open_api_models.Config(credential=credential)
|
||||
config.endpoint = "alidns.aliyuncs.com"
|
||||
return Alidns20150109Client(config)
|
||||
|
||||
|
||||
# Helper: find existing record
|
||||
def find_record(client, domain, rr, record_type):
|
||||
req = alidns_models.DescribeDomainRecordsRequest(
|
||||
domain_name=domain,
|
||||
rr_key_word=rr,
|
||||
type_key_word=record_type,
|
||||
page_size=100
|
||||
)
|
||||
resp = client.describe_domain_records_with_options(
|
||||
req, util_models.RuntimeOptions()
|
||||
)
|
||||
records = resp.body.domain_records.record or []
|
||||
|
||||
for r in records:
|
||||
if r.rr == rr and r.type == record_type:
|
||||
return r
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||
domain=dict(type='str', required=True),
|
||||
rr=dict(type='str', required=True),
|
||||
type=dict(type='str', required=True),
|
||||
value=dict(type='str'),
|
||||
ttl=dict(type='int', default=600),
|
||||
priority=dict(type='int'),
|
||||
|
||||
# 支持 AK/SK
|
||||
access_key_id=dict(type='str', no_log=True),
|
||||
access_key_secret=dict(type='str', no_log=True),
|
||||
security_token=dict(type='str', no_log=True),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
state = module.params["state"]
|
||||
domain = module.params["domain"]
|
||||
rr = module.params["rr"]
|
||||
record_type = module.params["type"]
|
||||
value = module.params["value"]
|
||||
ttl = module.params["ttl"]
|
||||
priority = module.params["priority"]
|
||||
|
||||
access_key_id = module.params["access_key_id"]
|
||||
access_key_secret = module.params["access_key_secret"]
|
||||
security_token = module.params["security_token"]
|
||||
|
||||
client = create_client(access_key_id, access_key_secret, security_token)
|
||||
|
||||
# Find record
|
||||
try:
|
||||
existing = find_record(client, domain, rr, record_type)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=f"Failed to query DNS records: {e}")
|
||||
|
||||
# ----------------------------
|
||||
# ABSENT (delete)
|
||||
# ----------------------------
|
||||
if state == "absent":
|
||||
if not existing:
|
||||
module.exit_json(changed=False, msg="Record already absent")
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
try:
|
||||
req = alidns_models.DeleteDomainRecordRequest(
|
||||
record_id=existing.record_id
|
||||
)
|
||||
client.delete_domain_record_with_options(req, util_models.RuntimeOptions())
|
||||
except Exception as e:
|
||||
module.fail_json(msg=f"Failed to delete record: {e}")
|
||||
|
||||
module.exit_json(changed=True, msg="Record deleted", record_id=existing.record_id)
|
||||
|
||||
# ----------------------------
|
||||
# PRESENT (create / update)
|
||||
# ----------------------------
|
||||
if not value:
|
||||
module.fail_json(msg="value is required when state=present")
|
||||
|
||||
if existing:
|
||||
need_update = (
|
||||
existing.value != value or
|
||||
existing.ttl != ttl or
|
||||
(priority is not None and existing.priority != priority)
|
||||
)
|
||||
|
||||
if not need_update:
|
||||
module.exit_json(changed=False, msg="Record already up to date", record_id=existing.record_id)
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
try:
|
||||
req = alidns_models.UpdateDomainRecordRequest(
|
||||
record_id=existing.record_id,
|
||||
rr=rr,
|
||||
type=record_type,
|
||||
value=value,
|
||||
ttl=ttl,
|
||||
priority=priority,
|
||||
)
|
||||
client.update_domain_record_with_options(req, util_models.RuntimeOptions())
|
||||
except Exception as e:
|
||||
module.fail_json(msg=f"Failed to update record: {e}")
|
||||
|
||||
module.exit_json(changed=True, msg="Record updated", record_id=existing.record_id)
|
||||
|
||||
# ----------------------------
|
||||
# CREATE
|
||||
# ----------------------------
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
|
||||
try:
|
||||
req = alidns_models.AddDomainRecordRequest(
|
||||
domain_name=domain,
|
||||
rr=rr,
|
||||
type=record_type,
|
||||
value=value,
|
||||
ttl=ttl,
|
||||
priority=priority,
|
||||
)
|
||||
resp = client.add_domain_record_with_options(req, util_models.RuntimeOptions())
|
||||
record_id = resp.body.record_id
|
||||
except Exception as e:
|
||||
module.fail_json(msg=f"Failed to create record: {e}")
|
||||
|
||||
module.exit_json(changed=True, msg="Record created", record_id=record_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
18
playbooks/roles/vhosts/alicloud_dns_record/tasks/main.yml
Normal file
18
playbooks/roles/vhosts/alicloud_dns_record/tasks/main.yml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Ensure Alicloud DNS Record
|
||||
alicloud_dns_record:
|
||||
state: present
|
||||
domain: "{{ alicloud_dns_domain }}"
|
||||
rr: "{{ alicloud_dns_rr }}"
|
||||
type: "{{ alicloud_dns_type }}"
|
||||
value: "{{ alicloud_dns_value }}"
|
||||
ttl: "{{ alicloud_dns_ttl }}"
|
||||
priority: "{{ alicloud_dns_priority }}"
|
||||
access_key_id: "{{ alicloud_access_key }}"
|
||||
access_key_secret: "{{ alicloud_secret_key }}"
|
||||
security_token: "{{ alicloud_security_token }}"
|
||||
register: dns_result
|
||||
|
||||
- debug:
|
||||
var: dns_result
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
---
|
||||
alicloud_dns_sync_domain: ""
|
||||
alicloud_dns_sync_records: []
|
||||
alicloud_dns_sync_output: "/tmp/dns_records.yaml"
|
||||
|
||||
alicloud_access_key: ""
|
||||
alicloud_secret_key: ""
|
||||
59
playbooks/roles/vhosts/alicloud_dns_sync/files/dns_sync.py
Normal file
59
playbooks/roles/vhosts/alicloud_dns_sync/files/dns_sync.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import yaml
|
||||
from alibabacloud_alidns20150109.client import Client
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
|
||||
|
||||
def client(ak, sk):
|
||||
config = open_api_models.Config(
|
||||
access_key_id=ak,
|
||||
access_key_secret=sk,
|
||||
endpoint="alidns.aliyuncs.com",
|
||||
)
|
||||
return Client(config)
|
||||
|
||||
|
||||
def sync(domain, records, ak, sk):
|
||||
c = client(ak, sk)
|
||||
|
||||
# get all existing records
|
||||
resp = c.describe_domain_records(
|
||||
open_api_models.Config(domain_name=domain)
|
||||
)
|
||||
existing = { (i.rr, i.type): i for i in resp.body.domain_records.record }
|
||||
|
||||
for rec in records:
|
||||
key = (rec["rr"], rec["type"])
|
||||
ttl = rec.get("ttl", 600)
|
||||
|
||||
if key not in existing:
|
||||
print("CREATE:", rec)
|
||||
c.add_domain_record({
|
||||
"DomainName": domain,
|
||||
"RR": rec["rr"],
|
||||
"Type": rec["type"],
|
||||
"Value": rec["value"],
|
||||
"TTL": ttl,
|
||||
})
|
||||
else:
|
||||
cur = existing[key]
|
||||
if cur.value != rec["value"] or cur.ttl != ttl:
|
||||
print("UPDATE:", rec)
|
||||
c.update_domain_record({
|
||||
"RecordId": cur.record_id,
|
||||
"RR": rec["rr"],
|
||||
"Type": rec["type"],
|
||||
"Value": rec["value"],
|
||||
"TTL": ttl,
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fn = sys.argv[1]
|
||||
ak = sys.argv[2]
|
||||
sk = sys.argv[3]
|
||||
|
||||
cfg = yaml.safe_load(open(fn))
|
||||
for domain, recs in cfg.items():
|
||||
sync(domain, recs, ak, sk)
|
||||
18
playbooks/roles/vhosts/alicloud_dns_sync/tasks/main.yaml
Normal file
18
playbooks/roles/vhosts/alicloud_dns_sync/tasks/main.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Generate DNS records file from template
|
||||
template:
|
||||
src: dns_records.yaml.j2
|
||||
dest: "{{ alicloud_dns_sync_output }}"
|
||||
|
||||
- name: Upload dns_sync.py
|
||||
copy:
|
||||
src: dns_sync.py
|
||||
dest: /tmp/dns_sync.py
|
||||
mode: '0755'
|
||||
|
||||
- name: Sync DNS records
|
||||
command: >
|
||||
python3 /tmp/dns_sync.py
|
||||
{{ alicloud_dns_sync_output }}
|
||||
{{ alicloud_access_key }}
|
||||
{{ alicloud_secret_key }}
|
||||
@ -0,0 +1,7 @@
|
||||
{{ alicloud_dns_sync_domain }}:
|
||||
{% for rec in alicloud_dns_sync_records %}
|
||||
- rr: "{{ rec.rr }}"
|
||||
type: "{{ rec.type }}"
|
||||
value: "{{ rec.value }}"
|
||||
ttl: {{ rec.ttl | default(600) }}
|
||||
{% endfor %}
|
||||
@ -41,6 +41,20 @@ packages:
|
||||
- uidmap
|
||||
- fuse-overlayfs
|
||||
|
||||
# S3FS 挂载配置(可选)
|
||||
s3fs_enable: false
|
||||
s3fs_config:
|
||||
bucket: "" # S3 存储桶名称
|
||||
mount_point: "" # 挂载点路径,例如:/data/update-server/
|
||||
access_key: "" # AWS Access Key ID
|
||||
secret_key: "" # AWS Secret Access Key
|
||||
url: "https://s3.amazonaws.com" # S3 端点 URL
|
||||
region: "us-east-1" # S3 区域
|
||||
passwd_file: "~/.passwd-s3fs" # 密码文件路径
|
||||
use_path_request_style: true # 是否使用路径请求样式
|
||||
allow_other: true # 是否允许其他用户访问
|
||||
nonempty: false # 是否允许挂载到非空目录
|
||||
|
||||
|
||||
#config_temp:
|
||||
# k8s-node:
|
||||
|
||||
85
playbooks/roles/vhosts/common/tasks/configure_s3fs.yml
Normal file
85
playbooks/roles/vhosts/common/tasks/configure_s3fs.yml
Normal file
@ -0,0 +1,85 @@
|
||||
---
|
||||
- name: "S3FS | 检查 s3fs 配置"
|
||||
fail:
|
||||
msg: "S3FS 需要配置 s3fs_config.bucket 和 s3fs_config.mount_point"
|
||||
when:
|
||||
- s3fs_config.bucket | length == 0
|
||||
- s3fs_config.mount_point | length == 0
|
||||
|
||||
- name: "S3FS | 检查 AWS 凭证"
|
||||
fail:
|
||||
msg: "S3FS 需要配置 s3fs_config.access_key 和 s3fs_config.secret_key"
|
||||
when:
|
||||
- s3fs_config.access_key | length == 0
|
||||
- s3fs_config.secret_key | length == 0
|
||||
|
||||
- name: "S3FS | 安装 s3fs 软件包"
|
||||
apt:
|
||||
name: s3fs
|
||||
state: present
|
||||
become: yes
|
||||
when: ansible_facts.os_family == 'Debian'
|
||||
|
||||
- name: "S3FS | 安装 s3fs 软件包 (CentOS/RHEL)"
|
||||
yum:
|
||||
name: s3fs-fuse
|
||||
state: present
|
||||
become: yes
|
||||
when: ansible_facts.os_family == 'RedHat'
|
||||
|
||||
- name: "S3FS | 创建密码文件"
|
||||
copy:
|
||||
content: "{{ s3fs_config.access_key }}:{{ s3fs_config.secret_key }}"
|
||||
dest: "{{ s3fs_config.passwd_file | expanduser }}"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
when: s3fs_config.access_key | length > 0 and s3fs_config.secret_key | length > 0
|
||||
|
||||
- name: "S3FS | 创建挂载点目录"
|
||||
file:
|
||||
path: "{{ s3fs_config.mount_point }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
- name: "S3FS | 检查是否已挂载"
|
||||
shell: "mount | grep -q '{{ s3fs_config.mount_point }}' && echo 'mounted' || echo 'not mounted'"
|
||||
register: s3fs_mount_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "S3FS | 挂载 S3 存储桶"
|
||||
command: >
|
||||
s3fs {{ s3fs_config.bucket }} {{ s3fs_config.mount_point }}
|
||||
-o passwd_file={{ s3fs_config.passwd_file | expanduser }}
|
||||
{% if s3fs_config.allow_other %}-o allow_other{% endif %}
|
||||
-o url={{ s3fs_config.url }}
|
||||
{% if s3fs_config.use_path_request_style %}-o use_path_request_style{% endif %}
|
||||
args:
|
||||
creates: "{{ s3fs_config.mount_point }}/.s3fs_configured"
|
||||
when: s3fs_mount_check.stdout == 'not mounted'
|
||||
|
||||
- name: "S3FS | 创建挂载标记文件"
|
||||
copy:
|
||||
content: "S3FS mounted at {{ ansible_date_time.iso8601 }}"
|
||||
dest: "{{ s3fs_config.mount_point }}/.s3fs_configured"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
when: s3fs_mount_check.stdout == 'not mounted'
|
||||
|
||||
- name: "S3FS | 验证挂载"
|
||||
shell: "mount | grep '{{ s3fs_config.mount_point }}'"
|
||||
register: s3fs_verify_mount
|
||||
changed_when: false
|
||||
failed_when: true
|
||||
|
||||
- name: "S3FS | 显示挂载信息"
|
||||
debug:
|
||||
msg: |
|
||||
S3 存储桶已成功挂载!
|
||||
存储桶: {{ s3fs_config.bucket }}
|
||||
挂载点: {{ s3fs_config.mount_point }}
|
||||
状态: {{ s3fs_verify_mount.stdout }}
|
||||
@ -28,6 +28,11 @@
|
||||
when: install_packages | bool
|
||||
tags: [pkgs, baseline]
|
||||
|
||||
- name: "Common | S3FS 挂载"
|
||||
ansible.builtin.include_tasks: configure_s3fs.yml
|
||||
when: s3fs_enable | bool
|
||||
tags: [s3fs, mount]
|
||||
|
||||
#- name: Include GPU Configuration
|
||||
# include_tasks: include_gpu.yaml
|
||||
# when: (ansible_facts['distribution'] == "Ubuntu") or (ansible_facts['distribution'] == "Debian")
|
||||
|
||||
15
playbooks/vars/dns_records_svc_plus.yaml
Normal file
15
playbooks/vars/dns_records_svc_plus.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
alicloud_dns_sync_domain: "svc.plus"
|
||||
alicloud_dns_sync_records:
|
||||
- rr: www
|
||||
type: A
|
||||
value: 1.1.1.1
|
||||
ttl: 600
|
||||
|
||||
- rr: api
|
||||
type: A
|
||||
value: 2.2.2.2
|
||||
|
||||
- rr: mx
|
||||
type: MX
|
||||
value: mail.svc.plus
|
||||
ttl: 300
|
||||
32
scripts/init-remote-xray.sh
Normal file
32
scripts/init-remote-xray.sh
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
NODE_IP="$1"
|
||||
USER="ubuntu"
|
||||
|
||||
ssh $USER@$NODE_IP "
|
||||
sudo apt purge curl unzip -y
|
||||
curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh -o /tmp/install-release.sh
|
||||
sudo bash /tmp/install-release.sh
|
||||
"
|
||||
|
||||
scp /etc/ssl/svc.* $USER@$NODE_IP:/tmp/
|
||||
scp config.json $USER@$NODE_IP:/tmp/
|
||||
|
||||
ssh $USER@$NODE_IP "
|
||||
sudo cp /tmp/svc.* /etc/ssl/
|
||||
sudo cp /tmp/config.json /usr/local/etc/xray/config.json
|
||||
|
||||
sudo chown root:root /etc/ssl/svc.plus.pem
|
||||
sudo chmod 644 /etc/ssl/svc.plus.pem
|
||||
|
||||
sudo chown root:nogroup /etc/ssl/svc.plus.key
|
||||
sudo chmod 640 /etc/ssl/svc.plus.key
|
||||
|
||||
sudo chown root:root /usr/local/etc/xray/config.json
|
||||
sudo chmod 644 /usr/local/etc/xray/config.json
|
||||
|
||||
sudo systemctl restart xray
|
||||
sudo systemctl status xray
|
||||
sudo journalctl -fu xray
|
||||
"
|
||||
Loading…
Reference in New Issue
Block a user