diff --git a/playbooks/alicloud_dns_record.yml b/playbooks/alicloud_dns_record.yml new file mode 100644 index 0000000..6c84aff --- /dev/null +++ b/playbooks/alicloud_dns_record.yml @@ -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 diff --git a/playbooks/alicloud_dns_sync.yml b/playbooks/alicloud_dns_sync.yml new file mode 100644 index 0000000..ab407ba --- /dev/null +++ b/playbooks/alicloud_dns_sync.yml @@ -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 + diff --git a/playbooks/deploy_xcontrol_web.yml b/playbooks/deploy_xcontrol_web.yml new file mode 100644 index 0000000..09639ce --- /dev/null +++ b/playbooks/deploy_xcontrol_web.yml @@ -0,0 +1,8 @@ +- name: setup xcontrol web + hosts: all + become: true + vars: + group: mail + roles: + #- roles/vhosts/common/ + - roles/vhosts/nodejs/ diff --git a/playbooks/docs/alicloud_dns_sync.md b/playbooks/docs/alicloud_dns_sync.md new file mode 100644 index 0000000..11a0a66 --- /dev/null +++ b/playbooks/docs/alicloud_dns_sync.md @@ -0,0 +1 @@ +ansible-playbook batch_dns_sync.yml --extra-vars "aliyun_ak=XXXX aliyun_sk=YYYY" diff --git a/playbooks/roles/vhosts/alicloud_dns_record/defaults/main.yml b/playbooks/roles/vhosts/alicloud_dns_record/defaults/main.yml new file mode 100644 index 0000000..22aacd1 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_record/defaults/main.yml @@ -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 diff --git a/playbooks/roles/vhosts/alicloud_dns_record/library/alicloud_dns_record.py b/playbooks/roles/vhosts/alicloud_dns_record/library/alicloud_dns_record.py new file mode 100644 index 0000000..0f48968 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_record/library/alicloud_dns_record.py @@ -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() diff --git a/playbooks/roles/vhosts/alicloud_dns_record/tasks/main.yml b/playbooks/roles/vhosts/alicloud_dns_record/tasks/main.yml new file mode 100644 index 0000000..777570e --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_record/tasks/main.yml @@ -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 + diff --git a/playbooks/roles/vhosts/alicloud_dns_sync/defaults/main.yml b/playbooks/roles/vhosts/alicloud_dns_sync/defaults/main.yml new file mode 100644 index 0000000..b018f88 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_sync/defaults/main.yml @@ -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: "" diff --git a/playbooks/roles/vhosts/alicloud_dns_sync/files/dns_sync.py b/playbooks/roles/vhosts/alicloud_dns_sync/files/dns_sync.py new file mode 100644 index 0000000..0e9c3c1 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_sync/files/dns_sync.py @@ -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) diff --git a/playbooks/roles/vhosts/alicloud_dns_sync/tasks/main.yaml b/playbooks/roles/vhosts/alicloud_dns_sync/tasks/main.yaml new file mode 100644 index 0000000..88b8658 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_sync/tasks/main.yaml @@ -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 }} diff --git a/playbooks/roles/vhosts/alicloud_dns_sync/templates/dns_records.yaml.j2 b/playbooks/roles/vhosts/alicloud_dns_sync/templates/dns_records.yaml.j2 new file mode 100644 index 0000000..f6abbf4 --- /dev/null +++ b/playbooks/roles/vhosts/alicloud_dns_sync/templates/dns_records.yaml.j2 @@ -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 %} diff --git a/playbooks/roles/vhosts/common/defaults/main.yml b/playbooks/roles/vhosts/common/defaults/main.yml index efac9a1..bddd51d 100644 --- a/playbooks/roles/vhosts/common/defaults/main.yml +++ b/playbooks/roles/vhosts/common/defaults/main.yml @@ -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: diff --git a/playbooks/roles/vhosts/common/tasks/configure_s3fs.yml b/playbooks/roles/vhosts/common/tasks/configure_s3fs.yml new file mode 100644 index 0000000..7655ec9 --- /dev/null +++ b/playbooks/roles/vhosts/common/tasks/configure_s3fs.yml @@ -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 }} diff --git a/playbooks/roles/vhosts/common/tasks/main.yml b/playbooks/roles/vhosts/common/tasks/main.yml index 6e572b9..0f0e19d 100644 --- a/playbooks/roles/vhosts/common/tasks/main.yml +++ b/playbooks/roles/vhosts/common/tasks/main.yml @@ -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") diff --git a/playbooks/vars/dns_records_svc_plus.yaml b/playbooks/vars/dns_records_svc_plus.yaml new file mode 100644 index 0000000..8ca0e66 --- /dev/null +++ b/playbooks/vars/dns_records_svc_plus.yaml @@ -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 diff --git a/scripts/init-remote-xray.sh b/scripts/init-remote-xray.sh new file mode 100644 index 0000000..95fdd96 --- /dev/null +++ b/scripts/init-remote-xray.sh @@ -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 +"