From e9ea0b1d3b696afdb3d74d96584cd0e3f6b74b98 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 5 Apr 2026 18:58:09 +0800 Subject: [PATCH] Add managed accounts.svc.plus deployment --- deploy_accounts_svc_plus.yml | 8 ++ inventory.ini | 4 +- .../vhosts/accounts_service/defaults/main.yml | 54 +++++++++ .../vhosts/accounts_service/handlers/main.yml | 5 + roles/vhosts/accounts_service/tasks/main.yml | 45 +++++++ .../vhosts/accounts_service/tasks/target.yml | 101 ++++++++++++++++ .../templates/account.yaml.j2 | 113 ++++++++++++++++++ .../templates/accounts-site.caddy.j2 | 14 +++ .../accounts_service/templates/app.env.j2 | 21 ++++ .../templates/docker-compose.yml.j2 | 25 ++++ 10 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 deploy_accounts_svc_plus.yml create mode 100644 roles/vhosts/accounts_service/defaults/main.yml create mode 100644 roles/vhosts/accounts_service/handlers/main.yml create mode 100644 roles/vhosts/accounts_service/tasks/main.yml create mode 100644 roles/vhosts/accounts_service/tasks/target.yml create mode 100644 roles/vhosts/accounts_service/templates/account.yaml.j2 create mode 100644 roles/vhosts/accounts_service/templates/accounts-site.caddy.j2 create mode 100644 roles/vhosts/accounts_service/templates/app.env.j2 create mode 100644 roles/vhosts/accounts_service/templates/docker-compose.yml.j2 diff --git a/deploy_accounts_svc_plus.yml b/deploy_accounts_svc_plus.yml new file mode 100644 index 0000000..e182cec --- /dev/null +++ b/deploy_accounts_svc_plus.yml @@ -0,0 +1,8 @@ +- name: Deploy managed accounts.svc.plus service + hosts: accounts + gather_facts: false + become: true + vars: + accounts_service_image_tag: "{{ lookup('ansible.builtin.env', 'ACCOUNTS_IMAGE_TAG') | default('70c6a3f8', true) }}" + roles: + - roles/vhosts/accounts_service diff --git a/inventory.ini b/inventory.ini index 3ac6ad1..84a25a3 100644 --- a/inventory.ini +++ b/inventory.ini @@ -4,6 +4,9 @@ cn-front.svc.plus ansible_host=47.120.61.35 ansible_user=roo [agent_proxy] jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=root +[accounts] +acp-server.svc.plus ansible_host=46.250.251.132 ansible_user=root + [k3s] jp-k3s-vultr.svc.plus ansible_host=167.179.110.129 ansible_user=root @@ -16,4 +19,3 @@ ansible_host_key_checking=False # SSH 密钥或密码(二选一) ansible_ssh_private_key_file=~/.ssh/id_rsa k3s_platform_git_private_key=~/.ssh/id_rsa - diff --git a/roles/vhosts/accounts_service/defaults/main.yml b/roles/vhosts/accounts_service/defaults/main.yml new file mode 100644 index 0000000..40a48e5 --- /dev/null +++ b/roles/vhosts/accounts_service/defaults/main.yml @@ -0,0 +1,54 @@ +--- +accounts_service_image_repo: ghcr.io/x-evor/accounts +accounts_service_image_tag: latest +accounts_service_pull_image: true +accounts_service_container_port: 8080 +accounts_service_base_dir: /opt/cloud-neutral/accounts/managed +accounts_service_shared_network: cn-toolkit-shared +accounts_service_dns_servers: + - 1.1.1.1 + - 8.8.8.8 +accounts_service_caddyfile_path: /etc/caddy/Caddyfile +accounts_service_caddy_conf_dir: /etc/caddy/conf.d +accounts_service_caddy_fragment_path: /etc/caddy/conf.d/accounts-contabo-e700175.caddy +accounts_service_caddy_sites: + - server_names: + - accounts.svc.plus + - accounts-contabo-e700175.svc.plus + default_forwarded_host: svc.plus + upstream: 127.0.0.1:18081 +accounts_service_env_defaults: + DB_HOST: stunnel-client + DB_NAME: account + DB_PASSWORD: "" + DB_PORT: "15432" + DB_USER: svcplus_vps + INTERNAL_SERVICE_TOKEN: "" + POSTGRES_PASSWORD: "" + POSTGRES_USER: svcplus_vps + SMTP_FROM: XControl Account + SMTP_HOST: smtp.qq.com + SMTP_PASSWORD: "" + SMTP_PORT: "587" + SMTP_USERNAME: "" + XWORKMATE_VAULT_ADDR: https://vault.svc.plus + XWORKMATE_VAULT_MOUNT: kv + XWORKMATE_VAULT_NAMESPACE: "" + XWORKMATE_VAULT_TOKEN: "" +accounts_service_targets: + - name: prod + compose_dir: "{{ accounts_service_base_dir }}/prod" + compose_file: "{{ accounts_service_base_dir }}/prod/docker-compose.yml" + env_file: "{{ accounts_service_base_dir }}/prod/env/app.env" + config_file: "{{ accounts_service_base_dir }}/prod/config/account.yaml" + legacy_env_file: /opt/cloud-neutral/accounts/accounts-51945b5-contabo/env/app.env + container_name: accounts-managed-prod-contabo + host_port: 18081 + - name: preview + compose_dir: "{{ accounts_service_base_dir }}/preview" + compose_file: "{{ accounts_service_base_dir }}/preview/docker-compose.yml" + env_file: "{{ accounts_service_base_dir }}/preview/env/app.env" + config_file: "{{ accounts_service_base_dir }}/preview/config/account.yaml" + legacy_env_file: /opt/cloud-neutral/accounts/accounts-preview-51945b5-contabo/env/app.env + container_name: accounts-managed-preview-contabo + host_port: 28081 diff --git a/roles/vhosts/accounts_service/handlers/main.yml b/roles/vhosts/accounts_service/handlers/main.yml new file mode 100644 index 0000000..c1525ef --- /dev/null +++ b/roles/vhosts/accounts_service/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Reload caddy + ansible.builtin.systemd: + name: caddy + state: reloaded diff --git a/roles/vhosts/accounts_service/tasks/main.yml b/roles/vhosts/accounts_service/tasks/main.yml new file mode 100644 index 0000000..66c4ea7 --- /dev/null +++ b/roles/vhosts/accounts_service/tasks/main.yml @@ -0,0 +1,45 @@ +--- +- name: Ensure accounts service base directory exists + ansible.builtin.file: + path: "{{ accounts_service_base_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure Caddy fragment directory exists for accounts service + ansible.builtin.file: + path: "{{ accounts_service_caddy_conf_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Deploy managed accounts Caddy fragment + ansible.builtin.template: + src: accounts-site.caddy.j2 + dest: "{{ accounts_service_caddy_fragment_path }}" + owner: root + group: root + mode: "0644" + notify: Reload caddy + +- name: Ensure Caddy is enabled and running for accounts service + ansible.builtin.systemd: + name: caddy + enabled: true + state: started + +- name: Ensure shared Docker network exists for accounts service + ansible.builtin.command: docker network inspect "{{ accounts_service_shared_network }}" + changed_when: false + +- name: Deploy managed accounts compose targets + ansible.builtin.include_tasks: target.yml + loop: "{{ accounts_service_targets }}" + loop_control: + loop_var: accounts_service_target + +- name: Validate Caddy configuration for accounts service + ansible.builtin.command: caddy validate --config "{{ accounts_service_caddyfile_path }}" + changed_when: false diff --git a/roles/vhosts/accounts_service/tasks/target.yml b/roles/vhosts/accounts_service/tasks/target.yml new file mode 100644 index 0000000..c7ac3c5 --- /dev/null +++ b/roles/vhosts/accounts_service/tasks/target.yml @@ -0,0 +1,101 @@ +--- +- name: Ensure compose directories exist for {{ accounts_service_target.name }} + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ accounts_service_target.compose_dir }}" + - "{{ accounts_service_target.compose_dir }}/env" + - "{{ accounts_service_target.compose_dir }}/config" + +- name: Check for managed env file for {{ accounts_service_target.name }} + ansible.builtin.stat: + path: "{{ accounts_service_target.env_file }}" + register: accounts_service_target_managed_env_stat + +- name: Check for legacy env file for {{ accounts_service_target.name }} + ansible.builtin.stat: + path: "{{ accounts_service_target.legacy_env_file }}" + register: accounts_service_target_legacy_env_stat + +- name: Seed managed env file from legacy deployment for {{ accounts_service_target.name }} + ansible.builtin.copy: + src: "{{ accounts_service_target.legacy_env_file }}" + dest: "{{ accounts_service_target.env_file }}" + remote_src: true + owner: root + group: root + mode: "0600" + when: + - not accounts_service_target_managed_env_stat.stat.exists + - accounts_service_target_legacy_env_stat.stat.exists + +- name: Render managed env file from defaults for {{ accounts_service_target.name }} + ansible.builtin.template: + src: app.env.j2 + dest: "{{ accounts_service_target.env_file }}" + owner: root + group: root + mode: "0600" + when: + - not accounts_service_target_managed_env_stat.stat.exists + - not accounts_service_target_legacy_env_stat.stat.exists + +- name: Ensure managed CONFIG_TEMPLATE is present for {{ accounts_service_target.name }} + ansible.builtin.lineinfile: + path: "{{ accounts_service_target.env_file }}" + regexp: '^CONFIG_TEMPLATE=' + line: 'CONFIG_TEMPLATE=/etc/xcontrol/account.template.yaml' + state: present + insertafter: '^CONFIG_PATH=' + +- name: Ensure managed CONFIG_PATH is present for {{ accounts_service_target.name }} + ansible.builtin.lineinfile: + path: "{{ accounts_service_target.env_file }}" + regexp: '^CONFIG_PATH=' + line: 'CONFIG_PATH=/etc/xcontrol/account.yaml' + state: present + insertbefore: BOF + +- name: Render managed account config for {{ accounts_service_target.name }} + ansible.builtin.template: + src: account.yaml.j2 + dest: "{{ accounts_service_target.config_file }}" + owner: root + group: root + mode: "0644" + +- name: Render compose file for {{ accounts_service_target.name }} + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ accounts_service_target.compose_file }}" + owner: root + group: root + mode: "0644" + +- name: Pull image for {{ accounts_service_target.name }} + ansible.builtin.command: docker compose -f "{{ accounts_service_target.compose_file }}" pull app + args: + chdir: "{{ accounts_service_target.compose_dir }}" + when: accounts_service_pull_image | bool + +- name: Remove any container currently bound to {{ accounts_service_target.host_port }} + ansible.builtin.shell: | + set -euo pipefail + ids="$(docker ps --filter publish={{ accounts_service_target.host_port }} -q)" + if [ -z "${ids}" ]; then + exit 0 + fi + docker rm -f ${ids} + args: + executable: /bin/bash + register: accounts_service_target_port_cleanup + changed_when: accounts_service_target_port_cleanup.stdout | trim != "" + +- name: Start managed compose target for {{ accounts_service_target.name }} + ansible.builtin.command: docker compose -f "{{ accounts_service_target.compose_file }}" up -d --force-recreate --remove-orphans + args: + chdir: "{{ accounts_service_target.compose_dir }}" diff --git a/roles/vhosts/accounts_service/templates/account.yaml.j2 b/roles/vhosts/accounts_service/templates/account.yaml.j2 new file mode 100644 index 0000000..8a321e9 --- /dev/null +++ b/roles/vhosts/accounts_service/templates/account.yaml.j2 @@ -0,0 +1,113 @@ +mode: "server-agent" + +log: + level: info + +auth: + enable: true + token: + publicToken: "${AUTH_TOKEN_PUBLIC_TOKEN:-xcontrol-public-token-2024}" + refreshSecret: "${AUTH_TOKEN_REFRESH_SECRET:-xcontrol-refresh-secret-2024}" + accessSecret: "${AUTH_TOKEN_ACCESS_SECRET:-xcontrol-access-secret-2024}" + accessExpiry: "1h" + refreshExpiry: "168h" + oauth: + redirectUrl: "${OAUTH_REDIRECT_URL}" + frontendUrl: "${OAUTH_FRONTEND_URL:-https://console.svc.plus}" + github: + clientId: "${GITHUB_CLIENT_ID}" + clientSecret: "${GITHUB_CLIENT_SECRET}" + google: + clientId: "${GOOGLE_CLIENT_ID}" + clientSecret: "${GOOGLE_CLIENT_SECRET}" + +server: + addr: ":8080" + readTimeout: 15s + writeTimeout: 15s + publicUrl: "https://accounts.svc.plus" + allowedOrigins: + - "https://dev.svc.plus" + - "https://dev-homepage.svc.plus" + - "https://www.svc.plus" + - "https://global-homepage.svc.plus" + - "https://accounts.svc.plus" + - "https://console.svc.plus" + - "https://localhost:8443" + - "http://localhost:8080" + - "http://127.0.0.1:8080" + - "http://localhost:3001" + - "http://127.0.0.1:3001" + - "http://localhost:3000" + - "http://127.0.0.1:3000" + tls: + enabled: false + certFile: "" + keyFile: "" + caFile: "" + clientCAFile: "" + redirectHttp: false + +store: + driver: "postgres" + dsn: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable" + maxOpenConns: 30 + maxIdleConns: 10 + +session: + ttl: 24h + cache: "redis" + redis: + addr: "127.0.0.1:6379" + password: "" + +smtp: + host: "${SMTP_HOST}" + port: ${SMTP_PORT} + username: "${SMTP_USERNAME}" + password: "${SMTP_PASSWORD}" + from: "${SMTP_FROM}" + replyTo: "" + timeout: 10s + tls: + mode: "auto" + insecureSkipVerify: false + +xray: + sync: + enabled: false + interval: 5m + outputPath: "/usr/local/etc/xray/config.json" + templatePath: "account/config/xray.config.template.json" + validateCommand: [] + restartCommand: + - "systemctl" + - "restart" + - "xray.service" + +reviewAccount: + enabled: true + email: "review@svc.plus" + name: "Review" + password: "Review123!" + groups: + - "User" + - "Beta" + - "Review" + - "ReadOnly Role" + permissions: + - "admin.settings.read" + - "admin.users.metrics.read" + - "admin.users.list.read" + - "admin.agents.status.read" + - "admin.blacklist.read" + +agent: + id: "account-primary" + controllerUrl: "http://127.0.0.1:8080" + apiToken: "replace-with-agent-token" + httpTimeout: 15s + statusInterval: 1m + syncInterval: 5m + tls: + insecureSkipVerify: false diff --git a/roles/vhosts/accounts_service/templates/accounts-site.caddy.j2 b/roles/vhosts/accounts_service/templates/accounts-site.caddy.j2 new file mode 100644 index 0000000..e8c0440 --- /dev/null +++ b/roles/vhosts/accounts_service/templates/accounts-site.caddy.j2 @@ -0,0 +1,14 @@ +{% for site in accounts_service_caddy_sites %} +{{ site.server_names | join(', ') }} { + encode zstd gzip + map {http.request.header.X-Forwarded-Host} {accounts_forwarded_host} { + "" {{ site.default_forwarded_host | default("") }} + default {http.request.header.X-Forwarded-Host} + } + reverse_proxy {{ site.upstream }} { + # Preserve caller-provided tenant host (for example console.svc.plus -> accounts proxy). + # When the request comes directly to accounts.svc.plus, fall back to the managed shared host. + header_up X-Forwarded-Host {accounts_forwarded_host} + } +} +{% endfor %} diff --git a/roles/vhosts/accounts_service/templates/app.env.j2 b/roles/vhosts/accounts_service/templates/app.env.j2 new file mode 100644 index 0000000..72e5f11 --- /dev/null +++ b/roles/vhosts/accounts_service/templates/app.env.j2 @@ -0,0 +1,21 @@ +# Managed by Ansible. Do not edit on the host. +CONFIG_PATH=/etc/xcontrol/account.yaml +CONFIG_TEMPLATE=/etc/xcontrol/account.template.yaml +DB_HOST={{ accounts_service_env_defaults.DB_HOST }} +DB_NAME={{ accounts_service_env_defaults.DB_NAME }} +DB_PASSWORD={{ accounts_service_env_defaults.DB_PASSWORD }} +DB_PORT={{ accounts_service_env_defaults.DB_PORT }} +DB_USER={{ accounts_service_env_defaults.DB_USER }} +INTERNAL_SERVICE_TOKEN={{ accounts_service_env_defaults.INTERNAL_SERVICE_TOKEN }} +POSTGRES_PASSWORD={{ accounts_service_env_defaults.POSTGRES_PASSWORD }} +POSTGRES_USER={{ accounts_service_env_defaults.POSTGRES_USER }} +SMTP_FROM={{ accounts_service_env_defaults.SMTP_FROM }} +SMTP_HOST={{ accounts_service_env_defaults.SMTP_HOST }} +SMTP_PASSWORD={{ accounts_service_env_defaults.SMTP_PASSWORD }} +SMTP_PORT={{ accounts_service_env_defaults.SMTP_PORT }} +SMTP_USERNAME={{ accounts_service_env_defaults.SMTP_USERNAME }} + +XWORKMATE_VAULT_ADDR={{ accounts_service_env_defaults.XWORKMATE_VAULT_ADDR }} +XWORKMATE_VAULT_NAMESPACE={{ accounts_service_env_defaults.XWORKMATE_VAULT_NAMESPACE }} +XWORKMATE_VAULT_TOKEN={{ accounts_service_env_defaults.XWORKMATE_VAULT_TOKEN }} +XWORKMATE_VAULT_MOUNT={{ accounts_service_env_defaults.XWORKMATE_VAULT_MOUNT }} diff --git a/roles/vhosts/accounts_service/templates/docker-compose.yml.j2 b/roles/vhosts/accounts_service/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..16c94dd --- /dev/null +++ b/roles/vhosts/accounts_service/templates/docker-compose.yml.j2 @@ -0,0 +1,25 @@ +services: + app: + image: "{{ accounts_service_image_repo }}:{{ accounts_service_image_tag }}" + container_name: {{ accounts_service_target.container_name }} + restart: unless-stopped + env_file: + - ./env/app.env + environment: + PORT: "{{ accounts_service_container_port }}" + volumes: + - ./config/account.yaml:/etc/xcontrol/account.template.yaml:ro + ports: + - "127.0.0.1:{{ accounts_service_target.host_port }}:{{ accounts_service_container_port }}" + dns: +{% for dns_server in accounts_service_dns_servers %} + - "{{ dns_server }}" +{% endfor %} + networks: + - default + - shared_stunnel + +networks: + shared_stunnel: + external: true + name: "{{ accounts_service_shared_network }}"