Refactor ACP vhosts deployment layout
This commit is contained in:
parent
9d6e59e802
commit
672ea8ba32
12
deploy_acp_codex_vhosts.yml
Normal file
12
deploy_acp_codex_vhosts.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Deploy ACP Codex vhosts
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: roles/vhosts/deploy_acp_vhosts/
|
||||
vars:
|
||||
deploy_acp_codex: true
|
||||
deploy_acp_opencode: false
|
||||
deploy_acp_gemini: false
|
||||
tags: [deploy_acp_vhosts, acp_codex]
|
||||
12
deploy_acp_gemini_vhosts.yml
Normal file
12
deploy_acp_gemini_vhosts.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Deploy ACP Gemini vhosts
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: roles/vhosts/deploy_acp_vhosts/
|
||||
vars:
|
||||
deploy_acp_codex: false
|
||||
deploy_acp_opencode: false
|
||||
deploy_acp_gemini: true
|
||||
tags: [deploy_acp_vhosts, acp_gemini]
|
||||
@ -1,6 +1,12 @@
|
||||
---
|
||||
- import_playbook: deploy_acp_vhosts.yml
|
||||
vars:
|
||||
deploy_acp_codex: false
|
||||
deploy_acp_opencode: true
|
||||
deploy_acp_unified: false
|
||||
- name: Deploy ACP OpenCode vhosts
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: roles/vhosts/deploy_acp_vhosts/
|
||||
vars:
|
||||
deploy_acp_codex: false
|
||||
deploy_acp_opencode: true
|
||||
deploy_acp_gemini: false
|
||||
tags: [deploy_acp_vhosts, acp_opencode]
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
- name: Deploy ACP vhosts
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: true
|
||||
vars:
|
||||
deploy_acp_codex: true
|
||||
deploy_acp_opencode: true
|
||||
deploy_acp_unified: true
|
||||
deploy_acp_bridge_server: true
|
||||
roles:
|
||||
- role: roles/vhosts/acp_codex/
|
||||
when: deploy_acp_codex
|
||||
tags: [acp_codex]
|
||||
- role: roles/vhosts/acp_opencode/
|
||||
when: deploy_acp_opencode
|
||||
tags: [acp_opencode]
|
||||
- role: roles/vhosts/acp_bridge_server/
|
||||
when: deploy_acp_bridge_server
|
||||
tags: [acp_bridge_server]
|
||||
- role: roles/vhosts/acp_vhosts/
|
||||
when: deploy_acp_unified
|
||||
tags: [acp_vhosts]
|
||||
83
deploy_agent_svc_plus.yml
Normal file
83
deploy_agent_svc_plus.yml
Normal file
@ -0,0 +1,83 @@
|
||||
- name: Deploy managed agent.svc.plus service
|
||||
hosts: "{{ agent_service_hosts | default('agent_svc_plus') }}"
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
agent_svc_plus_repo_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_REPO_URL')
|
||||
| default('https://github.com/x-evor/agent.svc.plus.git', true) }}
|
||||
agent_svc_plus_repo_version: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_REPO_VERSION')
|
||||
| default('main', true) }}
|
||||
agent_svc_plus_app_dir: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_APP_DIR')
|
||||
| default('/opt/agent.svc.plus', true) }}
|
||||
agent_svc_plus_go_version: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_GO_VERSION')
|
||||
| default('1.25.1', true) }}
|
||||
agent_id: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_ID')
|
||||
| default('node-xhttp.svc.plus', true) }}
|
||||
agent_controller_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_CONTROLLER_URL')
|
||||
| default('https://accounts.svc.plus', true) }}
|
||||
agent_api_token: >-
|
||||
{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN')
|
||||
| default('', true) }}
|
||||
agent_billing_enabled: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_BILLING_ENABLED')
|
||||
| default(true, true) | bool }}
|
||||
agent_billing_base_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'BILLING_SERVICE_BASE_URL')
|
||||
| default('http://127.0.0.1:8081', true) }}
|
||||
agent_billing_http_timeout: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_BILLING_HTTP_TIMEOUT')
|
||||
| default('15s', true) }}
|
||||
agent_billing_collect_interval: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_BILLING_COLLECT_INTERVAL')
|
||||
| default('1m', true) }}
|
||||
agent_billing_reconcile_interval: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_BILLING_RECONCILE_INTERVAL')
|
||||
| default('5m', true) }}
|
||||
xray_enabled: >-
|
||||
{{ lookup('ansible.builtin.env', 'AGENT_XRAY_ENABLED')
|
||||
| default(true, true) | bool }}
|
||||
xray_uuid: >-
|
||||
{{ lookup('ansible.builtin.env', 'XRAY_UUID')
|
||||
| default('00000000-0000-0000-0000-000000000000', true) }}
|
||||
pre_tasks:
|
||||
- name: Validate INTERNAL_SERVICE_TOKEN is present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- agent_api_token | length > 0
|
||||
fail_msg: "INTERNAL_SERVICE_TOKEN must be exported before running this playbook."
|
||||
success_msg: "INTERNAL_SERVICE_TOKEN found"
|
||||
|
||||
- name: Gather service facts
|
||||
ansible.builtin.service_facts:
|
||||
|
||||
- name: Assert host is bootstrapped with setup-proxy.sh services
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'xray.service' in ansible_facts.services"
|
||||
- "'xray-tcp.service' in ansible_facts.services"
|
||||
- "'caddy.service' in ansible_facts.services"
|
||||
fail_msg: "Target host must already be bootstrapped by setup-proxy.sh (missing xray.service, xray-tcp.service, or caddy.service)."
|
||||
success_msg: "Target host already has the setup-proxy.sh service layout."
|
||||
|
||||
- name: Assert setup-proxy.sh config paths exist
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
loop:
|
||||
- /etc/caddy/Caddyfile
|
||||
- /usr/local/etc/xray/templates
|
||||
register: agent_bootstrap_paths
|
||||
|
||||
- name: Validate setup-proxy.sh config paths are present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- agent_bootstrap_paths.results | map(attribute='stat.exists') | min
|
||||
fail_msg: "Target host is missing /etc/caddy/Caddyfile or /usr/local/etc/xray/templates. Run setup-proxy.sh first."
|
||||
success_msg: "setup-proxy.sh config paths exist."
|
||||
roles:
|
||||
- roles/vhosts/agent-svc-plus
|
||||
44
deploy_billing_service.yml
Normal file
44
deploy_billing_service.yml
Normal file
@ -0,0 +1,44 @@
|
||||
- name: Deploy billing-service
|
||||
hosts: "{{ billing_service_hosts | default('billing_service') }}"
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
billing_service_source_dir: >-
|
||||
{{ lookup('ansible.builtin.env', 'BILLING_SERVICE_SOURCE_DIR')
|
||||
| default(playbook_dir ~ '/../billing-service', true) }}
|
||||
billing_service_exporter_base_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'EXPORTER_BASE_URL')
|
||||
| default('http://127.0.0.1:8080', true) }}
|
||||
billing_service_database_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'DATABASE_URL')
|
||||
| default('', true) }}
|
||||
billing_service_listen_addr: >-
|
||||
{{ lookup('ansible.builtin.env', 'BILLING_SERVICE_LISTEN_ADDR')
|
||||
| default('127.0.0.1:8081', true) }}
|
||||
billing_service_collect_interval: >-
|
||||
{{ lookup('ansible.builtin.env', 'COLLECT_INTERVAL')
|
||||
| default('1m', true) }}
|
||||
billing_service_default_region: >-
|
||||
{{ lookup('ansible.builtin.env', 'DEFAULT_REGION')
|
||||
| default('', true) }}
|
||||
billing_service_source_revision: >-
|
||||
{{ lookup('ansible.builtin.env', 'SOURCE_REVISION')
|
||||
| default('billing-service-v1', true) }}
|
||||
billing_service_price_per_byte: >-
|
||||
{{ lookup('ansible.builtin.env', 'PRICE_PER_BYTE')
|
||||
| default('0', true) }}
|
||||
billing_service_initial_included_quota_bytes: >-
|
||||
{{ lookup('ansible.builtin.env', 'INITIAL_INCLUDED_QUOTA_BYTES')
|
||||
| default('0', true) }}
|
||||
billing_service_initial_balance: >-
|
||||
{{ lookup('ansible.builtin.env', 'INITIAL_BALANCE')
|
||||
| default('0', true) }}
|
||||
pre_tasks:
|
||||
- name: Validate DATABASE_URL is present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- billing_service_database_url | length > 0
|
||||
fail_msg: "DATABASE_URL must be exported before running this playbook."
|
||||
success_msg: "DATABASE_URL found"
|
||||
roles:
|
||||
- roles/vhosts/billing-service
|
||||
@ -1,6 +0,0 @@
|
||||
---
|
||||
- import_playbook: deploy_acp_vhosts.yml
|
||||
vars:
|
||||
deploy_acp_codex: true
|
||||
deploy_acp_opencode: false
|
||||
deploy_acp_unified: false
|
||||
7
deploy_console_svc_plus.yml
Normal file
7
deploy_console_svc_plus.yml
Normal file
@ -0,0 +1,7 @@
|
||||
- name: Deploy managed console.svc.plus service
|
||||
hosts: "{{ console_service_hosts | default('console') }}"
|
||||
gather_facts: true
|
||||
become: true
|
||||
roles:
|
||||
- roles/vhosts/docker
|
||||
- roles/vhosts/console_service
|
||||
41
deploy_xray_exporter.yml
Normal file
41
deploy_xray_exporter.yml
Normal file
@ -0,0 +1,41 @@
|
||||
- name: Deploy xray-exporter service
|
||||
hosts: "{{ xray_exporter_hosts | default('xray_exporter') }}"
|
||||
gather_facts: true
|
||||
become: true
|
||||
vars:
|
||||
xray_exporter_source_dir: >-
|
||||
{{ lookup('ansible.builtin.env', 'XRAY_EXPORTER_SOURCE_DIR')
|
||||
| default(playbook_dir ~ '/../xray-exporter', true) }}
|
||||
xray_exporter_node_id: >-
|
||||
{{ lookup('ansible.builtin.env', 'EXPORTER_NODE_ID')
|
||||
| default('node-xhttp.svc.plus', true) }}
|
||||
xray_exporter_env_name: >-
|
||||
{{ lookup('ansible.builtin.env', 'EXPORTER_ENV')
|
||||
| default('prod', true) }}
|
||||
xray_exporter_stats_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'XRAY_STATS_URL')
|
||||
| default('http://127.0.0.1:49227/debug/vars', true) }}
|
||||
xray_exporter_stats_token: >-
|
||||
{{ lookup('ansible.builtin.env', 'XRAY_STATS_TOKEN')
|
||||
| default('', true) }}
|
||||
xray_exporter_accounts_base_url: >-
|
||||
{{ lookup('ansible.builtin.env', 'ACCOUNTS_BASE_URL')
|
||||
| default('https://accounts.svc.plus', true) }}
|
||||
xray_exporter_internal_service_token: >-
|
||||
{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN')
|
||||
| default('', true) }}
|
||||
xray_exporter_listen_addr: >-
|
||||
{{ lookup('ansible.builtin.env', 'XRAY_EXPORTER_LISTEN_ADDR')
|
||||
| default('127.0.0.1:8080', true) }}
|
||||
xray_exporter_scrape_interval: >-
|
||||
{{ lookup('ansible.builtin.env', 'SCRAPE_INTERVAL')
|
||||
| default('1m', true) }}
|
||||
pre_tasks:
|
||||
- name: Validate INTERNAL_SERVICE_TOKEN is present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- xray_exporter_internal_service_token | length > 0
|
||||
fail_msg: "INTERNAL_SERVICE_TOKEN must be exported before running this playbook."
|
||||
success_msg: "INTERNAL_SERVICE_TOKEN found"
|
||||
roles:
|
||||
- roles/vhosts/xray-exporter
|
||||
8
deploy_xworkmate_bridge_vhosts.yml
Normal file
8
deploy_xworkmate_bridge_vhosts.yml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Deploy ACP vhosts through xworkmate bridge
|
||||
hosts: "{{ xworkmate_bridge_hosts | default('xworkmate-bridge.svc.plus') }}"
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: roles/vhosts/deploy_acp_vhosts/
|
||||
tags: [deploy_acp_vhosts]
|
||||
@ -4,14 +4,25 @@ 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
|
||||
|
||||
[agent_svc_plus]
|
||||
jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=root
|
||||
|
||||
[xray_exporter]
|
||||
jp-xhttp-contabo.svc.plus ansible_host=46.250.251.132 ansible_user=root
|
||||
|
||||
[billing_service]
|
||||
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
|
||||
xworkmate-bridge.svc.plus ansible_host=46.250.251.132 ansible_user=root
|
||||
|
||||
[apisix]
|
||||
api.svc.plus ansible_host=46.250.251.132 ansible_user=root
|
||||
|
||||
[postgresql]
|
||||
acp-server.svc.plus ansible_host=46.250.251.132 ansible_user=root
|
||||
xworkmate-bridge.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
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
# Zitadel Docker role
|
||||
|
||||
This role provisions a Zitadel stack with Postgres, optional TLS termination, login frontend, Nginx proxy, and Certbot assets. Templates from `templates/` and static assets from `files/` are rendered into `{{ zitadel_workspace }}` and the Docker Compose stack is started.
|
||||
This role provisions a Zitadel stack with Postgres and the login frontend, then exposes both services on localhost-only ports so the host Caddy instance can terminate TLS and reverse proxy traffic for `{{ zitadel_domain }}`.
|
||||
|
||||
The previous embedded `nginx/certbot` deployment mode now lives in the separate legacy role `docker/zitadel_legacy`.
|
||||
|
||||
## Layout
|
||||
```
|
||||
files/
|
||||
├── certbot/
|
||||
│ ├── conf/
|
||||
│ └── www/
|
||||
├── docker-compose.yaml
|
||||
├── nginx/
|
||||
│ ├── conf.d/
|
||||
│ │ └── default.conf
|
||||
│ └── nginx.conf
|
||||
└── run.sh
|
||||
templates/
|
||||
├── docker-compose.yaml
|
||||
└── zitadel-site.caddy.j2
|
||||
```
|
||||
|
||||
## Defaults
|
||||
@ -21,6 +18,12 @@ files/
|
||||
- `zitadel_workspace`: `{{ zitadel_deploy_dir }}`
|
||||
- `zitadel_domain`: `auth.svc.plus`
|
||||
- `zitadel_masterkey`: `MasterkeyNeedsToHave32Characters`
|
||||
- `zitadel_api_bind_host`: `127.0.0.1`
|
||||
- `zitadel_api_port`: `19080`
|
||||
- `zitadel_login_bind_host`: `127.0.0.1`
|
||||
- `zitadel_login_port`: `19081`
|
||||
- `zitadel_caddy_conf_dir`: `/etc/caddy/conf.d`
|
||||
- `zitadel_caddy_fragment_path`: `/etc/caddy/conf.d/zitadel.caddy`
|
||||
|
||||
## RUN
|
||||
|
||||
|
||||
@ -4,3 +4,10 @@ zitadel_deploy_dir: /opt/zitadel
|
||||
zitadel_workspace: "{{ zitadel_deploy_dir }}"
|
||||
zitadel_domain: auth.svc.plus
|
||||
zitadel_masterkey: MasterkeyNeedsToHave32Characters
|
||||
zitadel_api_bind_host: 127.0.0.1
|
||||
zitadel_api_port: 19080
|
||||
zitadel_login_bind_host: 127.0.0.1
|
||||
zitadel_login_port: 19081
|
||||
zitadel_caddyfile_path: /etc/caddy/Caddyfile
|
||||
zitadel_caddy_conf_dir: /etc/caddy/conf.d
|
||||
zitadel_caddy_fragment_path: /etc/caddy/conf.d/zitadel.caddy
|
||||
|
||||
@ -3,4 +3,4 @@ set -euo pipefail
|
||||
|
||||
# Helper script to start the Zitadel docker compose stack
|
||||
cd "$(dirname "$0")"
|
||||
docker-compose -f docker-compose.yaml up -d
|
||||
docker compose -f docker-compose.yaml up -d --remove-orphans
|
||||
|
||||
5
roles/docker/zitadel/handlers/main.yml
Normal file
5
roles/docker/zitadel/handlers/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Reload caddy
|
||||
ansible.builtin.service:
|
||||
name: caddy
|
||||
state: reloaded
|
||||
@ -7,11 +7,6 @@
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ zitadel_workspace }}"
|
||||
- "{{ zitadel_workspace }}/certbot"
|
||||
- "{{ zitadel_workspace }}/certbot/conf"
|
||||
- "{{ zitadel_workspace }}/certbot/www"
|
||||
- "{{ zitadel_workspace }}/nginx"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d"
|
||||
|
||||
- name: Ensure Zitadel workspace ownership
|
||||
become: true
|
||||
@ -31,8 +26,6 @@
|
||||
mode: "{{ item.mode | default('0644') }}"
|
||||
loop:
|
||||
- { src: 'docker-compose.yaml', dest: 'docker-compose.yaml' }
|
||||
- { src: 'nginx/conf.d/default.conf', dest: 'nginx/conf.d/default.conf' }
|
||||
- { src: 'nginx/conf.d/bootstrap-nginx.conf', dest: 'nginx/conf.d/bootstrap-nginx.conf' }
|
||||
|
||||
- name: Copy Zitadel static files
|
||||
become: true
|
||||
@ -42,25 +35,42 @@
|
||||
mode: "{{ item.mode | default('0644') }}"
|
||||
loop:
|
||||
- { src: 'run.sh', dest: 'run.sh', mode: '0755' }
|
||||
- { src: 'nginx/nginx.conf', dest: 'nginx/nginx.conf' }
|
||||
|
||||
- name: Bootstrap NGINX (80-only for ACME)
|
||||
- name: Check caddy CLI is present on the target node
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml up -d bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
ansible.builtin.command: caddy version
|
||||
changed_when: false
|
||||
|
||||
- name: Run certbot initial ACME challenge
|
||||
- name: Ensure Caddy fragment directory exists for Zitadel
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml run --rm certbot
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ zitadel_caddy_conf_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Destroy Bootstrap NGINX (80-only for ACME)
|
||||
- name: Deploy Zitadel Caddy site fragment
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml down bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
ansible.builtin.template:
|
||||
src: zitadel-site.caddy.j2
|
||||
dest: "{{ zitadel_caddy_fragment_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
|
||||
- name: Ensure Caddy is enabled and running for Zitadel
|
||||
become: true
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Validate Caddy configuration for Zitadel
|
||||
become: true
|
||||
ansible.builtin.command: caddy validate --config "{{ zitadel_caddyfile_path }}"
|
||||
changed_when: false
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 1. 判断 Zitadel 是否已经初始化
|
||||
@ -100,6 +110,6 @@
|
||||
# -------------------------------------------------------------------
|
||||
- name: Bring up Zitadel stack
|
||||
become: true
|
||||
command: docker compose -f {{ zitadel_workspace }}/docker-compose.yaml up -d
|
||||
command: docker compose -f {{ zitadel_workspace }}/docker-compose.yaml up -d --remove-orphans
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
services:
|
||||
|
||||
zitadel-external-tls:
|
||||
zitadel:
|
||||
extends:
|
||||
service: zitadel-init
|
||||
command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"'
|
||||
@ -11,33 +10,14 @@ services:
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
ports:
|
||||
- "{{ zitadel_api_bind_host }}:{{ zitadel_api_port }}:8080"
|
||||
depends_on:
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
zitadel-init:
|
||||
condition: 'service_completed_successfully'
|
||||
|
||||
zitadel-enabled-tls:
|
||||
extends:
|
||||
service: zitadel-init
|
||||
command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"'
|
||||
environment:
|
||||
ZITADEL_EXTERNALPORT: 443
|
||||
ZITADEL_EXTERNALSECURE: true
|
||||
ZITADEL_TLS_ENABLED: true
|
||||
ZITADEL_TLS_CERTPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/fullchain.pem
|
||||
ZITADEL_TLS_KEYPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/privkey.pem
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
depends_on:
|
||||
zitadel-init:
|
||||
condition: 'service_completed_successfully'
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
|
||||
zitadel-init:
|
||||
image: '${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}'
|
||||
command: 'init'
|
||||
@ -91,10 +71,11 @@ services:
|
||||
- 'data:/var/lib/postgresql/data:rw'
|
||||
|
||||
login-external-tls:
|
||||
container_name: login-external-tls
|
||||
restart: 'unless-stopped'
|
||||
image: 'ghcr.io/zitadel/zitadel-login:latest'
|
||||
environment:
|
||||
- ZITADEL_API_URL=http://zitadel-external-tls:8080
|
||||
- ZITADEL_API_URL=http://zitadel:8080
|
||||
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
|
||||
- CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }}
|
||||
@ -102,82 +83,11 @@ services:
|
||||
- "{{ zitadel_workspace }}:/current-dir:ro"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
zitadel-external-tls:
|
||||
condition: 'service_healthy'
|
||||
|
||||
login-enabled-tls:
|
||||
restart: 'unless-stopped'
|
||||
image: 'ghcr.io/zitadel/zitadel-login:latest'
|
||||
environment:
|
||||
- ZITADEL_API_URL=https://zitadel-enabled-tls:8080
|
||||
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
|
||||
- CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }}
|
||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}:/current-dir:ro"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
zitadel-enabled-tls:
|
||||
condition: 'service_healthy'
|
||||
|
||||
proxy-external-tls:
|
||||
image: nginx:mainline-alpine
|
||||
container_name: proxy-external-tls
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro"
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks:
|
||||
- app
|
||||
- "{{ zitadel_login_bind_host }}:{{ zitadel_login_port }}:3000"
|
||||
depends_on:
|
||||
zitadel-external-tls:
|
||||
condition: service_healthy
|
||||
|
||||
bootstrap-nginx:
|
||||
profiles: ["bootstrap"]
|
||||
image: nginx:mainline-alpine
|
||||
container_name: bootstrap-nginx
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d/bootstrap-nginx.conf:/etc/nginx/conf.d/bootstrap-nginx.conf"
|
||||
ports:
|
||||
- "80:80" # 暂时只占用80
|
||||
networks:
|
||||
- app
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost"]
|
||||
interval: 3s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
start_period: 3s
|
||||
certbot:
|
||||
profiles: ["bootstrap"]
|
||||
image: certbot/certbot
|
||||
container_name: certbot
|
||||
command: >
|
||||
certonly --webroot
|
||||
--webroot-path=/var/www/certbot
|
||||
--email manbuzhe2009@qq.com
|
||||
--agree-tos
|
||||
--no-eff-email
|
||||
--keep-until-expiring
|
||||
--non-interactive
|
||||
-d {{ zitadel_domain }}
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
networks:
|
||||
- app
|
||||
zitadel:
|
||||
condition: 'service_healthy'
|
||||
|
||||
networks:
|
||||
app:
|
||||
|
||||
24
roles/docker/zitadel/templates/zitadel-site.caddy.j2
Normal file
24
roles/docker/zitadel/templates/zitadel-site.caddy.j2
Normal file
@ -0,0 +1,24 @@
|
||||
{{ zitadel_domain }} {
|
||||
encode zstd gzip
|
||||
|
||||
@login {
|
||||
path /ui/v2/login*
|
||||
}
|
||||
|
||||
handle @login {
|
||||
reverse_proxy {{ zitadel_login_bind_host }}:{{ zitadel_login_port }} {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Proto https
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy {{ zitadel_api_bind_host }}:{{ zitadel_api_port }} {
|
||||
transport http {
|
||||
versions h2c 2
|
||||
}
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Proto https
|
||||
}
|
||||
}
|
||||
}
|
||||
30
roles/docker/zitadel_legacy/README.md
Normal file
30
roles/docker/zitadel_legacy/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Zitadel Docker legacy role
|
||||
|
||||
Legacy role that preserves the original bundled `nginx/certbot` deployment mode.
|
||||
|
||||
This role provisions a Zitadel stack with Postgres, optional TLS termination, login frontend, Nginx proxy, and Certbot assets. Templates from `templates/` and static assets from `files/` are rendered into `{{ zitadel_workspace }}` and the Docker Compose stack is started.
|
||||
|
||||
## Layout
|
||||
```
|
||||
files/
|
||||
├── certbot/
|
||||
│ ├── conf/
|
||||
│ └── www/
|
||||
├── docker-compose.yaml
|
||||
├── nginx/
|
||||
│ ├── conf.d/
|
||||
│ │ └── default.conf
|
||||
│ └── nginx.conf
|
||||
└── run.sh
|
||||
```
|
||||
|
||||
## Defaults
|
||||
- `zitadel_deploy_dir`: `/opt/zitadel`
|
||||
- `zitadel_workspace`: `{{ zitadel_deploy_dir }}`
|
||||
- `zitadel_domain`: `auth.svc.plus`
|
||||
- `zitadel_masterkey`: `MasterkeyNeedsToHave32Characters`
|
||||
|
||||
## RUN
|
||||
|
||||
ansible-playbook -i inventory.ini deploy_zitadel_docker.yaml -e "domain=auth.svc.plus" -D -C -l auth.svc.plus
|
||||
ansible-playbook -i inventory.ini deploy_zitadel_docker.yaml -e "domain=auth.svc.plus" -D -l auth.svc.plus
|
||||
6
roles/docker/zitadel_legacy/defaults/main.yml
Normal file
6
roles/docker/zitadel_legacy/defaults/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
# Default deployment directory for Zitadel Docker stack
|
||||
zitadel_deploy_dir: /opt/zitadel
|
||||
zitadel_workspace: "{{ zitadel_deploy_dir }}"
|
||||
zitadel_domain: auth.svc.plus
|
||||
zitadel_masterkey: MasterkeyNeedsToHave32Characters
|
||||
6
roles/docker/zitadel_legacy/files/run.sh
Normal file
6
roles/docker/zitadel_legacy/files/run.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Helper script to start the Zitadel docker compose stack
|
||||
cd "$(dirname "$0")"
|
||||
docker-compose -f docker-compose.yaml up -d
|
||||
105
roles/docker/zitadel_legacy/tasks/main.yml
Normal file
105
roles/docker/zitadel_legacy/tasks/main.yml
Normal file
@ -0,0 +1,105 @@
|
||||
---
|
||||
- name: Ensure Zitadel directories exist
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ zitadel_workspace }}"
|
||||
- "{{ zitadel_workspace }}/certbot"
|
||||
- "{{ zitadel_workspace }}/certbot/conf"
|
||||
- "{{ zitadel_workspace }}/certbot/www"
|
||||
- "{{ zitadel_workspace }}/nginx"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d"
|
||||
|
||||
- name: Ensure Zitadel workspace ownership
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ zitadel_workspace }}"
|
||||
state: directory
|
||||
recurse: true
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0755"
|
||||
|
||||
- name: Template Zitadel configuration files
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ zitadel_workspace }}/{{ item.dest }}"
|
||||
mode: "{{ item.mode | default('0644') }}"
|
||||
loop:
|
||||
- { src: 'docker-compose.yaml', dest: 'docker-compose.yaml' }
|
||||
- { src: 'nginx/conf.d/default.conf', dest: 'nginx/conf.d/default.conf' }
|
||||
- { src: 'nginx/conf.d/bootstrap-nginx.conf', dest: 'nginx/conf.d/bootstrap-nginx.conf' }
|
||||
|
||||
- name: Copy Zitadel static files
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ zitadel_workspace }}/{{ item.dest }}"
|
||||
mode: "{{ item.mode | default('0644') }}"
|
||||
loop:
|
||||
- { src: 'run.sh', dest: 'run.sh', mode: '0755' }
|
||||
- { src: 'nginx/nginx.conf', dest: 'nginx/nginx.conf' }
|
||||
|
||||
- name: Bootstrap NGINX (80-only for ACME)
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml up -d bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
|
||||
- name: Run certbot initial ACME challenge
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml run --rm certbot
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
|
||||
- name: Destroy Bootstrap NGINX (80-only for ACME)
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ zitadel_workspace }}/docker-compose.yaml down bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 1. 判断 Zitadel 是否已经初始化
|
||||
# (是否已经生成 login-client.pat 或其它初始化标记)
|
||||
# -------------------------------------------------------------------
|
||||
- name: Check if Zitadel initialized
|
||||
stat:
|
||||
path: "{{ zitadel_workspace }}/login-client.pat"
|
||||
register: zitadel_initialized
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 2. 如果未初始化,先清理所有可能失败的残留容器、状态
|
||||
# -------------------------------------------------------------------
|
||||
- name: Zitadel containers and Zitadel postgres volume (cleanup)
|
||||
become: true
|
||||
shell: |
|
||||
docker compose -f {{ zitadel_workspace }}/docker-compose.yaml down || true
|
||||
docker volume rm zitadel_data || true
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
when: not zitadel_initialized.stat.exists
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 3. 执行第一次初始化(init + setup)
|
||||
# -------------------------------------------------------------------
|
||||
- name: Run Zitadel init (one-time)
|
||||
become: true
|
||||
shell: |
|
||||
docker compose -f {{ zitadel_workspace }}/docker-compose.yaml run --rm zitadel-init || true
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
when: not zitadel_initialized.stat.exists
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# 4. 启动正式 Zitadel stack(start-only)
|
||||
# -------------------------------------------------------------------
|
||||
- name: Bring up Zitadel stack
|
||||
become: true
|
||||
command: docker compose -f {{ zitadel_workspace }}/docker-compose.yaml up -d
|
||||
args:
|
||||
chdir: "{{ zitadel_workspace }}"
|
||||
187
roles/docker/zitadel_legacy/templates/docker-compose.yaml
Normal file
187
roles/docker/zitadel_legacy/templates/docker-compose.yaml
Normal file
@ -0,0 +1,187 @@
|
||||
services:
|
||||
|
||||
zitadel-external-tls:
|
||||
extends:
|
||||
service: zitadel-init
|
||||
command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"'
|
||||
environment:
|
||||
ZITADEL_EXTERNALPORT: 443
|
||||
ZITADEL_EXTERNALSECURE: true
|
||||
ZITADEL_TLS_ENABLED: false
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
depends_on:
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
zitadel-init:
|
||||
condition: 'service_completed_successfully'
|
||||
|
||||
zitadel-enabled-tls:
|
||||
extends:
|
||||
service: zitadel-init
|
||||
command: 'start-from-setup --masterkey "{{ zitadel_masterkey }}"'
|
||||
environment:
|
||||
ZITADEL_EXTERNALPORT: 443
|
||||
ZITADEL_EXTERNALSECURE: true
|
||||
ZITADEL_TLS_ENABLED: true
|
||||
ZITADEL_TLS_CERTPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/fullchain.pem
|
||||
ZITADEL_TLS_KEYPATH: /etc/letsencrypt/live/{{ zitadel_domain }}/privkey.pem
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
depends_on:
|
||||
zitadel-init:
|
||||
condition: 'service_completed_successfully'
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
|
||||
zitadel-init:
|
||||
image: '${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}'
|
||||
command: 'init'
|
||||
depends_on:
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
environment:
|
||||
# Using an external domain other than localhost proofs, that the proxy configuration works.
|
||||
# If Zitadel can't resolve a requests original host to this domain,
|
||||
# it will return a 404 Instance not found error.
|
||||
ZITADEL_EXTERNALDOMAIN: {{ zitadel_domain }}
|
||||
# In case something doesn't work as expected,
|
||||
# it can be handy to be able to read the access logs.
|
||||
ZITADEL_LOGSTORE_ACCESS_STDOUT_ENABLED: true
|
||||
# For convenience, ZITADEL should not ask to change the initial admin users password.
|
||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: false
|
||||
# database configuration
|
||||
ZITADEL_DATABASE_POSTGRES_HOST: db
|
||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
||||
# Set up a service account with IAM_LOGIN_CLIENT role and write the PAT to the file ./login-client.pat
|
||||
ZITADEL_FIRSTINSTANCE_LOGINCLIENTPATPATH: /current-dir/login-client.pat
|
||||
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_USERNAME: login-client
|
||||
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_MACHINE_NAME: Automatically Initialized IAM Login Client
|
||||
ZITADEL_FIRSTINSTANCE_ORG_LOGINCLIENT_PAT_EXPIRATIONDATE: '2029-01-01T00:00:00Z'
|
||||
# The master key is used to
|
||||
networks:
|
||||
- db
|
||||
healthcheck:
|
||||
test: [ "CMD", "/app/zitadel", "ready" ]
|
||||
interval: '10s'
|
||||
timeout: '5s'
|
||||
retries: 5
|
||||
start_period: '10s'
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}:/current-dir:rw"
|
||||
|
||||
db:
|
||||
restart: 'always'
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready" ]
|
||||
interval: 5s
|
||||
timeout: 60s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
networks:
|
||||
- db
|
||||
volumes:
|
||||
- 'data:/var/lib/postgresql/data:rw'
|
||||
|
||||
login-external-tls:
|
||||
restart: 'unless-stopped'
|
||||
image: 'ghcr.io/zitadel/zitadel-login:latest'
|
||||
environment:
|
||||
- ZITADEL_API_URL=http://zitadel-external-tls:8080
|
||||
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
|
||||
- CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }}
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}:/current-dir:ro"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
zitadel-external-tls:
|
||||
condition: 'service_healthy'
|
||||
|
||||
login-enabled-tls:
|
||||
restart: 'unless-stopped'
|
||||
image: 'ghcr.io/zitadel/zitadel-login:latest'
|
||||
environment:
|
||||
- ZITADEL_API_URL=https://zitadel-enabled-tls:8080
|
||||
- NEXT_PUBLIC_BASE_PATH=/ui/v2/login
|
||||
- ZITADEL_SERVICE_USER_TOKEN_FILE=/current-dir/login-client.pat
|
||||
- CUSTOM_REQUEST_HEADERS=Host:{{ zitadel_domain }}
|
||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}:/current-dir:ro"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
zitadel-enabled-tls:
|
||||
condition: 'service_healthy'
|
||||
|
||||
proxy-external-tls:
|
||||
image: nginx:mainline-alpine
|
||||
container_name: proxy-external-tls
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro"
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
zitadel-external-tls:
|
||||
condition: service_healthy
|
||||
|
||||
bootstrap-nginx:
|
||||
profiles: ["bootstrap"]
|
||||
image: nginx:mainline-alpine
|
||||
container_name: bootstrap-nginx
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ zitadel_workspace }}/nginx/conf.d/bootstrap-nginx.conf:/etc/nginx/conf.d/bootstrap-nginx.conf"
|
||||
ports:
|
||||
- "80:80" # 暂时只占用80
|
||||
networks:
|
||||
- app
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost"]
|
||||
interval: 3s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
start_period: 3s
|
||||
certbot:
|
||||
profiles: ["bootstrap"]
|
||||
image: certbot/certbot
|
||||
container_name: certbot
|
||||
command: >
|
||||
certonly --webroot
|
||||
--webroot-path=/var/www/certbot
|
||||
--email manbuzhe2009@qq.com
|
||||
--agree-tos
|
||||
--no-eff-email
|
||||
--keep-until-expiring
|
||||
--non-interactive
|
||||
-d {{ zitadel_domain }}
|
||||
volumes:
|
||||
- "{{ zitadel_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ zitadel_workspace }}/certbot/www:/var/www/certbot"
|
||||
networks:
|
||||
- app
|
||||
|
||||
networks:
|
||||
app:
|
||||
db:
|
||||
|
||||
volumes:
|
||||
data:
|
||||
@ -24,6 +24,7 @@ acp_codex_bridge_allowed_origins:
|
||||
acp_codex_public_base_url: https://acp-server.svc.plus/codex
|
||||
acp_codex_caddyfile_path: /etc/caddy/Caddyfile
|
||||
acp_codex_caddy_conf_dir: /etc/caddy/conf.d
|
||||
acp_codex_manage_caddy: true
|
||||
acp_codex_obsolete_caddy_fragment_paths:
|
||||
- /etc/caddy/conf.d/acp-server-codex.caddy
|
||||
acp_codex_enable_ufw: true
|
||||
|
||||
@ -60,6 +60,8 @@
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
when:
|
||||
- acp_codex_manage_caddy | bool
|
||||
|
||||
- name: Deploy Codex ACP systemd service
|
||||
ansible.builtin.template:
|
||||
@ -88,6 +90,8 @@
|
||||
name: caddy
|
||||
enabled: true
|
||||
state: started
|
||||
when:
|
||||
- acp_codex_manage_caddy | bool
|
||||
|
||||
- name: Ensure Codex ACP service is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
|
||||
26
roles/vhosts/acp_gemini/defaults/main.yml
Normal file
26
roles/vhosts/acp_gemini/defaults/main.yml
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
acp_gemini_service_name: acp-gemini-adapter
|
||||
acp_gemini_service_user: root
|
||||
acp_gemini_service_group: root
|
||||
acp_gemini_home: /root
|
||||
acp_gemini_workdir: /root
|
||||
acp_gemini_binary_path: /opt/homebrew/bin/gemini
|
||||
acp_gemini_args: --experimental-acp
|
||||
|
||||
acp_gemini_bridge_binary_path: /usr/local/bin/xworkmate-go-core
|
||||
acp_gemini_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge"
|
||||
acp_gemini_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/acp_gemini"
|
||||
acp_gemini_bridge_local_binary_path: "{{ acp_gemini_bridge_local_build_dir }}/xworkmate-go-core"
|
||||
acp_gemini_bridge_build_goos: linux
|
||||
acp_gemini_bridge_build_goarch: amd64
|
||||
acp_gemini_listen_host: 127.0.0.1
|
||||
acp_gemini_listen_port: 8791
|
||||
acp_gemini_allowed_origins:
|
||||
- https://xworkmate.svc.plus
|
||||
- http://localhost:*
|
||||
- http://127.0.0.1:*
|
||||
acp_gemini_public_base_url: https://acp-server.svc.plus/gemini
|
||||
acp_gemini_manage_caddy: false
|
||||
acp_gemini_environment: {}
|
||||
acp_gemini_packages:
|
||||
- caddy
|
||||
6
roles/vhosts/acp_gemini/handlers/main.yml
Normal file
6
roles/vhosts/acp_gemini/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart gemini acp adapter
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ acp_gemini_service_name }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
48
roles/vhosts/acp_gemini/tasks/config.yml
Normal file
48
roles/vhosts/acp_gemini/tasks/config.yml
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
- name: Ensure local Gemini ACP build directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ acp_gemini_bridge_local_build_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Build XWorkmate Go ACP adapter locally for Gemini
|
||||
ansible.builtin.command:
|
||||
cmd: go build -o "{{ acp_gemini_bridge_local_binary_path }}" .
|
||||
chdir: "{{ acp_gemini_bridge_local_source_dir }}"
|
||||
environment:
|
||||
GOOS: "{{ acp_gemini_bridge_build_goos }}"
|
||||
GOARCH: "{{ acp_gemini_bridge_build_goarch }}"
|
||||
CGO_ENABLED: "0"
|
||||
GO111MODULE: "on"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Upload XWorkmate Go ACP adapter binary for Gemini
|
||||
ansible.builtin.copy:
|
||||
src: "{{ acp_gemini_bridge_local_binary_path }}"
|
||||
dest: "{{ acp_gemini_bridge_binary_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
notify: Restart gemini acp adapter
|
||||
|
||||
- name: Deploy Gemini ACP adapter service
|
||||
ansible.builtin.template:
|
||||
src: gemini-acp-adapter.service.j2
|
||||
dest: "/etc/systemd/system/{{ acp_gemini_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart gemini acp adapter
|
||||
|
||||
- name: Reload systemd manager configuration for Gemini ACP
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Ensure Gemini ACP adapter service is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ acp_gemini_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
10
roles/vhosts/acp_gemini/tasks/install.yml
Normal file
10
roles/vhosts/acp_gemini/tasks/install.yml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Install Gemini ACP packages
|
||||
ansible.builtin.apt:
|
||||
name: "{{ acp_gemini_packages }}"
|
||||
state: present
|
||||
update_cache: true
|
||||
environment:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
APT_LISTCHANGES_FRONTEND: none
|
||||
become: true
|
||||
16
roles/vhosts/acp_gemini/tasks/main.yml
Normal file
16
roles/vhosts/acp_gemini/tasks/main.yml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Install Gemini ACP prerequisites
|
||||
ansible.builtin.import_tasks: install.yml
|
||||
tags: [acp_gemini, acp_gemini_install]
|
||||
|
||||
- name: Configure Gemini ACP adapter
|
||||
ansible.builtin.import_tasks: config.yml
|
||||
tags: [acp_gemini, acp_gemini_config]
|
||||
|
||||
- name: Flush Gemini ACP handlers before validation
|
||||
ansible.builtin.meta: flush_handlers
|
||||
tags: [acp_gemini, acp_gemini_config, acp_gemini_validate]
|
||||
|
||||
- name: Validate Gemini ACP readiness
|
||||
ansible.builtin.import_tasks: validate.yml
|
||||
tags: [acp_gemini, acp_gemini_validate]
|
||||
36
roles/vhosts/acp_gemini/tasks/validate.yml
Normal file
36
roles/vhosts/acp_gemini/tasks/validate.yml
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
- name: Check Gemini ACP adapter listener
|
||||
ansible.builtin.command: ss -ltnp
|
||||
register: acp_gemini_ss
|
||||
changed_when: false
|
||||
|
||||
- name: Validate local Gemini ACP adapter HTTP endpoint
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }}/acp/rpc"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
jsonrpc: "2.0"
|
||||
id: 1
|
||||
method: acp.capabilities
|
||||
params: {}
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: acp_gemini_bridge_http
|
||||
|
||||
- name: Show Gemini ACP adapter status
|
||||
ansible.builtin.command: systemctl status "{{ acp_gemini_service_name }}" --no-pager
|
||||
register: acp_gemini_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show Gemini ACP validation summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Gemini public base URL: {{ acp_gemini_public_base_url }}"
|
||||
- "Preferred WebSocket endpoint: {{ acp_gemini_public_base_url }}/acp"
|
||||
- "Compatibility HTTP RPC endpoint: {{ acp_gemini_public_base_url }}/acp/rpc"
|
||||
- "Gemini adapter listener: {{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }}"
|
||||
- "Service: {{ acp_gemini_status.stdout | default('N/A') }}"
|
||||
- "Socket: {{ acp_gemini_ss.stdout | default('N/A') }}"
|
||||
- "Bridge capabilities HTTP: {{ acp_gemini_bridge_http.content | default('N/A') }}"
|
||||
@ -0,0 +1,25 @@
|
||||
[Unit]
|
||||
Description=XWorkmate Gemini ACP adapter
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ acp_gemini_service_user }}
|
||||
Group={{ acp_gemini_service_group }}
|
||||
WorkingDirectory={{ acp_gemini_workdir }}
|
||||
Environment=HOME={{ acp_gemini_home }}
|
||||
Environment=TERM=xterm-256color
|
||||
Environment=GEMINI_ADAPTER_LISTEN_ADDR={{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }}
|
||||
Environment=GEMINI_ADAPTER_BIN={{ acp_gemini_binary_path }}
|
||||
Environment=GEMINI_ADAPTER_ARGS={{ acp_gemini_args }}
|
||||
Environment=GEMINI_ADAPTER_ALLOWED_ORIGINS={{ acp_gemini_allowed_origins | join(',') }}
|
||||
{% for key, value in acp_gemini_environment | dictsort %}
|
||||
Environment={{ key }}={{ value }}
|
||||
{% endfor %}
|
||||
ExecStart={{ acp_gemini_bridge_binary_path }} gemini-acp-adapter --listen {{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }} --gemini-bin {{ acp_gemini_binary_path }} --gemini-args "{{ acp_gemini_args }}"
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -26,6 +26,7 @@ acp_opencode_expected_content_type: text/html
|
||||
acp_opencode_expected_body_marker: opencode-theme-id
|
||||
acp_opencode_caddyfile_path: /etc/caddy/Caddyfile
|
||||
acp_opencode_caddy_conf_dir: /etc/caddy/conf.d
|
||||
acp_opencode_manage_caddy: true
|
||||
acp_opencode_obsolete_caddy_fragment_paths:
|
||||
- /etc/caddy/conf.d/acp-server-opencode.caddy
|
||||
acp_opencode_enable_ufw: true
|
||||
|
||||
@ -47,6 +47,8 @@
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
when:
|
||||
- acp_opencode_manage_caddy | bool
|
||||
|
||||
- name: Remove deprecated standalone OpenCode Caddy fragments
|
||||
ansible.builtin.file:
|
||||
@ -82,6 +84,8 @@
|
||||
name: caddy
|
||||
enabled: true
|
||||
state: started
|
||||
when:
|
||||
- acp_opencode_manage_caddy | bool
|
||||
|
||||
- name: Ensure OpenCode ACP service is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
|
||||
47
roles/vhosts/agent-svc-plus/defaults/main.yml
Normal file
47
roles/vhosts/agent-svc-plus/defaults/main.yml
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
# Agent-svc-plus deployment defaults
|
||||
|
||||
agent_svc_plus_repo_url: "https://github.com/x-evor/agent.svc.plus.git"
|
||||
agent_svc_plus_repo_version: "main"
|
||||
agent_svc_plus_app_dir: "/opt/agent.svc.plus"
|
||||
|
||||
agent_svc_plus_go_version: "1.25.1"
|
||||
agent_svc_plus_go_root: "/usr/local/go"
|
||||
agent_svc_plus_go_bin: "{{ agent_svc_plus_go_root }}/bin/go"
|
||||
|
||||
agent_svc_plus_binary_name: "agent-svc-plus"
|
||||
agent_svc_plus_binary_path: "/usr/local/bin/{{ agent_svc_plus_binary_name }}"
|
||||
agent_svc_plus_build_target: "./cmd/agent"
|
||||
|
||||
agent_svc_plus_service_name: "agent-svc-plus"
|
||||
agent_svc_plus_service_description: "agent.svc.plus service"
|
||||
agent_svc_plus_user: "root"
|
||||
agent_svc_plus_group: "root"
|
||||
|
||||
agent_svc_plus_config_dir: "/etc/agent"
|
||||
agent_svc_plus_config_file: "account-agent.yaml"
|
||||
agent_svc_plus_config_path: "{{ agent_svc_plus_config_dir }}/{{ agent_svc_plus_config_file }}"
|
||||
agent_svc_plus_data_dir: "/var/lib/agent-svc-plus"
|
||||
|
||||
agent_id: "node-xhttp.svc.plus"
|
||||
agent_controller_url: "https://accounts.svc.plus"
|
||||
agent_api_token: ""
|
||||
agent_http_timeout: "15s"
|
||||
agent_status_interval: "1m"
|
||||
agent_sync_interval: "5m"
|
||||
agent_tls_insecure_skip_verify: false
|
||||
|
||||
agent_billing_enabled: true
|
||||
agent_billing_base_url: "http://127.0.0.1:8081"
|
||||
agent_billing_http_timeout: "15s"
|
||||
agent_billing_collect_interval: "1m"
|
||||
agent_billing_reconcile_interval: "5m"
|
||||
|
||||
xray_enabled: true
|
||||
xray_sync_interval: "5m"
|
||||
xray_uuid: "00000000-0000-0000-0000-000000000000"
|
||||
xray_config_path: "/usr/local/etc/xray/config.json"
|
||||
xray_tcp_config_path: "/usr/local/etc/xray/tcp-config.json"
|
||||
xray_template_dir: "/usr/local/etc/xray/templates"
|
||||
|
||||
agent_log_level: "info"
|
||||
6
roles/vhosts/agent-svc-plus/handlers/main.yml
Normal file
6
roles/vhosts/agent-svc-plus/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart agent-svc-plus
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ agent_svc_plus_service_name }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
123
roles/vhosts/agent-svc-plus/tasks/main.yml
Normal file
123
roles/vhosts/agent-svc-plus/tasks/main.yml
Normal file
@ -0,0 +1,123 @@
|
||||
---
|
||||
- name: Map Go architecture
|
||||
ansible.builtin.set_fact:
|
||||
agent_svc_plus_go_arch: >-
|
||||
{{ 'arm64' if ansible_architecture in ['aarch64', 'arm64'] else 'amd64' }}
|
||||
|
||||
- name: Ensure agent build prerequisites are installed
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- git
|
||||
- curl
|
||||
- ca-certificates
|
||||
- tar
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Check installed Go version
|
||||
ansible.builtin.command: "{{ agent_svc_plus_go_bin }} version"
|
||||
register: agent_svc_plus_go_version_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Download Go toolchain archive
|
||||
ansible.builtin.get_url:
|
||||
url: "https://go.dev/dl/go{{ agent_svc_plus_go_version }}.linux-{{ agent_svc_plus_go_arch }}.tar.gz"
|
||||
dest: "/tmp/go{{ agent_svc_plus_go_version }}.linux-{{ agent_svc_plus_go_arch }}.tar.gz"
|
||||
mode: "0644"
|
||||
when: agent_svc_plus_go_version_check.rc != 0 or ('go' ~ agent_svc_plus_go_version) not in agent_svc_plus_go_version_check.stdout
|
||||
|
||||
- name: Remove previous Go installation
|
||||
ansible.builtin.file:
|
||||
path: "{{ agent_svc_plus_go_root }}"
|
||||
state: absent
|
||||
when: agent_svc_plus_go_version_check.rc != 0 or ('go' ~ agent_svc_plus_go_version) not in agent_svc_plus_go_version_check.stdout
|
||||
|
||||
- name: Install Go toolchain
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/go{{ agent_svc_plus_go_version }}.linux-{{ agent_svc_plus_go_arch }}.tar.gz"
|
||||
dest: "/usr/local"
|
||||
remote_src: true
|
||||
when: agent_svc_plus_go_version_check.rc != 0 or ('go' ~ agent_svc_plus_go_version) not in agent_svc_plus_go_version_check.stdout
|
||||
|
||||
- name: Create agent directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ agent_svc_plus_user }}"
|
||||
group: "{{ agent_svc_plus_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ agent_svc_plus_app_dir }}"
|
||||
- "{{ agent_svc_plus_config_dir }}"
|
||||
- "{{ agent_svc_plus_data_dir }}"
|
||||
- "{{ xray_template_dir }}"
|
||||
|
||||
- name: Clone agent.svc.plus repository
|
||||
ansible.builtin.git:
|
||||
repo: "{{ agent_svc_plus_repo_url }}"
|
||||
dest: "{{ agent_svc_plus_app_dir }}"
|
||||
version: "{{ agent_svc_plus_repo_version }}"
|
||||
update: true
|
||||
notify: Restart agent-svc-plus
|
||||
|
||||
- name: Build agent.svc.plus binary
|
||||
ansible.builtin.command: >-
|
||||
{{ agent_svc_plus_go_bin }} build -o {{ agent_svc_plus_binary_path }} {{ agent_svc_plus_build_target }}
|
||||
args:
|
||||
chdir: "{{ agent_svc_plus_app_dir }}"
|
||||
environment:
|
||||
GOROOT: "{{ agent_svc_plus_go_root }}"
|
||||
PATH: "{{ agent_svc_plus_go_root }}/bin:{{ ansible_env.PATH }}"
|
||||
GOTOOLCHAIN: local
|
||||
notify: Restart agent-svc-plus
|
||||
|
||||
- name: Ensure agent binary permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ agent_svc_plus_binary_path }}"
|
||||
owner: "{{ agent_svc_plus_user }}"
|
||||
group: "{{ agent_svc_plus_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy agent configuration
|
||||
ansible.builtin.template:
|
||||
src: agent-config.yaml.j2
|
||||
dest: "{{ agent_svc_plus_config_path }}"
|
||||
owner: "{{ agent_svc_plus_user }}"
|
||||
group: "{{ agent_svc_plus_group }}"
|
||||
mode: "0600"
|
||||
notify: Restart agent-svc-plus
|
||||
|
||||
- name: Deploy XHTTP template
|
||||
ansible.builtin.template:
|
||||
src: xray.xhttp.template.json.j2
|
||||
dest: "{{ xray_template_dir }}/xray.xhttp.template.json"
|
||||
owner: "{{ agent_svc_plus_user }}"
|
||||
group: "{{ agent_svc_plus_group }}"
|
||||
mode: "0644"
|
||||
when: xray_enabled | bool
|
||||
|
||||
- name: Deploy TCP template
|
||||
ansible.builtin.template:
|
||||
src: xray.tcp.template.json.j2
|
||||
dest: "{{ xray_template_dir }}/xray.tcp.template.json"
|
||||
owner: "{{ agent_svc_plus_user }}"
|
||||
group: "{{ agent_svc_plus_group }}"
|
||||
mode: "0644"
|
||||
when: xray_enabled | bool
|
||||
|
||||
- name: Install agent systemd service
|
||||
ansible.builtin.template:
|
||||
src: agent-svc-plus.service.j2
|
||||
dest: "/etc/systemd/system/{{ agent_svc_plus_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart agent-svc-plus
|
||||
|
||||
- name: Enable and start agent-svc-plus
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ agent_svc_plus_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
45
roles/vhosts/agent-svc-plus/templates/agent-config.yaml.j2
Normal file
45
roles/vhosts/agent-svc-plus/templates/agent-config.yaml.j2
Normal file
@ -0,0 +1,45 @@
|
||||
mode: "agent"
|
||||
|
||||
log:
|
||||
level: {{ agent_log_level }}
|
||||
|
||||
agent:
|
||||
id: "{{ agent_id }}"
|
||||
controllerUrl: "{{ agent_controller_url }}"
|
||||
apiToken: "{{ agent_api_token }}"
|
||||
httpTimeout: {{ agent_http_timeout }}
|
||||
statusInterval: {{ agent_status_interval }}
|
||||
syncInterval: {{ agent_sync_interval }}
|
||||
tls:
|
||||
insecureSkipVerify: {{ agent_tls_insecure_skip_verify | lower }}
|
||||
|
||||
billing:
|
||||
enabled: {{ agent_billing_enabled | lower }}
|
||||
baseURL: "{{ agent_billing_base_url }}"
|
||||
httpTimeout: {{ agent_billing_http_timeout }}
|
||||
collectInterval: {{ agent_billing_collect_interval }}
|
||||
reconcileInterval: {{ agent_billing_reconcile_interval }}
|
||||
|
||||
{% if xray_enabled %}
|
||||
xray:
|
||||
sync:
|
||||
enabled: true
|
||||
interval: {{ xray_sync_interval }}
|
||||
targets:
|
||||
- name: "xhttp"
|
||||
outputPath: "{{ xray_config_path }}"
|
||||
templatePath: "{{ xray_template_dir }}/xray.xhttp.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
- "restart"
|
||||
- "xray.service"
|
||||
- name: "tcp"
|
||||
outputPath: "{{ xray_tcp_config_path }}"
|
||||
templatePath: "{{ xray_template_dir }}/xray.tcp.template.json"
|
||||
validateCommand: []
|
||||
restartCommand:
|
||||
- "systemctl"
|
||||
- "restart"
|
||||
- "xray-tcp.service"
|
||||
{% endif %}
|
||||
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description={{ agent_svc_plus_service_description }}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory={{ agent_svc_plus_data_dir }}
|
||||
ExecStart={{ agent_svc_plus_binary_path }} -config {{ agent_svc_plus_config_path }}
|
||||
Restart=always
|
||||
User={{ agent_svc_plus_user }}
|
||||
Group={{ agent_svc_plus_group }}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -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": 1443,
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "{% raw %}{{ UUID }}{% endraw %}",
|
||||
"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": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{{ agent_id }}/{{ agent_id }}.crt",
|
||||
"keyFile": "/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/{{ agent_id }}/{{ agent_id }}.key"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sniffing": {
|
||||
"enabled": true,
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"protocol": "freedom",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"protocol": "blackhole",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"handshake": 2,
|
||||
"connIdle": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "debug"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "/dev/shm/xray.sock,0666",
|
||||
"protocol": "vless",
|
||||
"settings": {
|
||||
"clients": [
|
||||
{
|
||||
"id": "{% raw %}{{ UUID }}{% endraw %}"
|
||||
}
|
||||
],
|
||||
"decryption": "none"
|
||||
},
|
||||
"streamSettings": {
|
||||
"network": "xhttp",
|
||||
"xhttpSettings": {
|
||||
"mode": "auto",
|
||||
"path": "/split"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {}
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {}
|
||||
}
|
||||
],
|
||||
"routing": {
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"outboundTag": "blocked"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -149,6 +149,8 @@
|
||||
do
|
||||
grep -q "^${key}=" "{{ apisix_service_env_file }}"
|
||||
done
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
when: apisix_service_validate_env | bool
|
||||
|
||||
|
||||
24
roles/vhosts/billing-service/defaults/main.yml
Normal file
24
roles/vhosts/billing-service/defaults/main.yml
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
billing_service_source_dir: "{{ playbook_dir }}/../billing-service"
|
||||
billing_service_app_dir: "/opt/billing-service"
|
||||
billing_service_go_version: "1.25.1"
|
||||
billing_service_go_root: "/usr/local/go"
|
||||
billing_service_go_bin: "{{ billing_service_go_root }}/bin/go"
|
||||
billing_service_binary_name: "billing-service"
|
||||
billing_service_binary_path: "/usr/local/bin/{{ billing_service_binary_name }}"
|
||||
billing_service_service_name: "billing-service"
|
||||
billing_service_service_description: "billing-service service"
|
||||
billing_service_user: "root"
|
||||
billing_service_group: "root"
|
||||
billing_service_env_dir: "/etc/default"
|
||||
billing_service_env_path: "{{ billing_service_env_dir }}/billing-service"
|
||||
|
||||
billing_service_listen_addr: "127.0.0.1:8081"
|
||||
billing_service_exporter_base_url: "http://127.0.0.1:8080"
|
||||
billing_service_database_url: ""
|
||||
billing_service_collect_interval: "1m"
|
||||
billing_service_default_region: ""
|
||||
billing_service_source_revision: "billing-service-v1"
|
||||
billing_service_price_per_byte: "0"
|
||||
billing_service_initial_included_quota_bytes: "0"
|
||||
billing_service_initial_balance: "0"
|
||||
6
roles/vhosts/billing-service/handlers/main.yml
Normal file
6
roles/vhosts/billing-service/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart billing-service
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ billing_service_service_name }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
102
roles/vhosts/billing-service/tasks/main.yml
Normal file
102
roles/vhosts/billing-service/tasks/main.yml
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
- name: Map Go architecture
|
||||
ansible.builtin.set_fact:
|
||||
billing_service_go_arch: >-
|
||||
{{ 'arm64' if ansible_architecture in ['aarch64', 'arm64'] else 'amd64' }}
|
||||
|
||||
- name: Ensure billing-service build prerequisites are installed
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- curl
|
||||
- ca-certificates
|
||||
- tar
|
||||
- rsync
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Check installed Go version
|
||||
ansible.builtin.command: "{{ billing_service_go_bin }} version"
|
||||
register: billing_service_go_version_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Download Go toolchain archive
|
||||
ansible.builtin.get_url:
|
||||
url: "https://go.dev/dl/go{{ billing_service_go_version }}.linux-{{ billing_service_go_arch }}.tar.gz"
|
||||
dest: "/tmp/go{{ billing_service_go_version }}.linux-{{ billing_service_go_arch }}.tar.gz"
|
||||
mode: "0644"
|
||||
when: billing_service_go_version_check.rc != 0 or ('go' ~ billing_service_go_version) not in billing_service_go_version_check.stdout
|
||||
|
||||
- name: Remove previous Go installation
|
||||
ansible.builtin.file:
|
||||
path: "{{ billing_service_go_root }}"
|
||||
state: absent
|
||||
when: billing_service_go_version_check.rc != 0 or ('go' ~ billing_service_go_version) not in billing_service_go_version_check.stdout
|
||||
|
||||
- name: Install Go toolchain
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/go{{ billing_service_go_version }}.linux-{{ billing_service_go_arch }}.tar.gz"
|
||||
dest: "/usr/local"
|
||||
remote_src: true
|
||||
when: billing_service_go_version_check.rc != 0 or ('go' ~ billing_service_go_version) not in billing_service_go_version_check.stdout
|
||||
|
||||
- name: Create billing-service directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ billing_service_user }}"
|
||||
group: "{{ billing_service_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ billing_service_app_dir }}"
|
||||
- "{{ billing_service_env_dir }}"
|
||||
|
||||
- name: Sync billing-service source tree
|
||||
ansible.posix.synchronize:
|
||||
src: "{{ billing_service_source_dir }}/"
|
||||
dest: "{{ billing_service_app_dir }}/"
|
||||
delete: true
|
||||
notify: Restart billing-service
|
||||
|
||||
- name: Build billing-service binary
|
||||
ansible.builtin.command: >-
|
||||
{{ billing_service_go_bin }} build -buildvcs=false -o {{ billing_service_binary_path }} ./cmd/billing-service
|
||||
args:
|
||||
chdir: "{{ billing_service_app_dir }}"
|
||||
environment:
|
||||
GOROOT: "{{ billing_service_go_root }}"
|
||||
PATH: "{{ billing_service_go_root }}/bin:{{ ansible_env.PATH }}"
|
||||
GOTOOLCHAIN: local
|
||||
notify: Restart billing-service
|
||||
|
||||
- name: Ensure billing-service binary permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ billing_service_binary_path }}"
|
||||
owner: "{{ billing_service_user }}"
|
||||
group: "{{ billing_service_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy billing-service environment file
|
||||
ansible.builtin.template:
|
||||
src: billing-service.env.j2
|
||||
dest: "{{ billing_service_env_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: Restart billing-service
|
||||
|
||||
- name: Install billing-service systemd service
|
||||
ansible.builtin.template:
|
||||
src: billing-service.service.j2
|
||||
dest: "/etc/systemd/system/{{ billing_service_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart billing-service
|
||||
|
||||
- name: Enable and start billing-service
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ billing_service_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
@ -0,0 +1,9 @@
|
||||
EXPORTER_BASE_URL={{ billing_service_exporter_base_url }}
|
||||
DATABASE_URL={{ billing_service_database_url }}
|
||||
LISTEN_ADDR={{ billing_service_listen_addr }}
|
||||
COLLECT_INTERVAL={{ billing_service_collect_interval }}
|
||||
DEFAULT_REGION={{ billing_service_default_region }}
|
||||
SOURCE_REVISION={{ billing_service_source_revision }}
|
||||
PRICE_PER_BYTE={{ billing_service_price_per_byte }}
|
||||
INITIAL_INCLUDED_QUOTA_BYTES={{ billing_service_initial_included_quota_bytes }}
|
||||
INITIAL_BALANCE={{ billing_service_initial_balance }}
|
||||
@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description={{ billing_service_service_description }}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory={{ billing_service_app_dir }}
|
||||
EnvironmentFile={{ billing_service_env_path }}
|
||||
ExecStart={{ billing_service_binary_path }}
|
||||
Restart=always
|
||||
User={{ billing_service_user }}
|
||||
Group={{ billing_service_group }}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
67
roles/vhosts/console_service/defaults/main.yml
Normal file
67
roles/vhosts/console_service/defaults/main.yml
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
console_service_base_dir: "{{ lookup('ansible.builtin.env', 'CONSOLE_BASE_DIR') | default('/opt/console-svc-plus', true) }}"
|
||||
console_service_compose_file: "{{ console_service_base_dir }}/docker-compose.yml"
|
||||
console_service_caddyfile: "{{ console_service_base_dir }}/Caddyfile"
|
||||
console_service_runtime_env_file: "{{ console_service_base_dir }}/.env.runtime"
|
||||
console_service_project_name: "{{ lookup('ansible.builtin.env', 'CONSOLE_PROJECT_NAME') | default('console-svc-plus', true) }}"
|
||||
|
||||
console_service_image_repo: "{{ lookup('ansible.builtin.env', 'CONSOLE_IMAGE_REPO') | default('ghcr.io/x-evor/dashboard', true) }}"
|
||||
console_service_image_tag: "{{ lookup('ansible.builtin.env', 'CONSOLE_IMAGE_TAG') | default('latest', true) }}"
|
||||
console_service_frontend_image: "{{ lookup('ansible.builtin.env', 'FRONTEND_IMAGE') | default('', true) }}"
|
||||
|
||||
console_service_registry: "{{ lookup('ansible.builtin.env', 'CONSOLE_REGISTRY') | default('ghcr.io', true) }}"
|
||||
console_service_registry_username: "{{ lookup('ansible.builtin.env', 'GHCR_USERNAME') | default('', true) }}"
|
||||
console_service_registry_password: "{{ lookup('ansible.builtin.env', 'GHCR_PASSWORD') | default('', true) }}"
|
||||
|
||||
console_service_primary_domain: "{{ lookup('ansible.builtin.env', 'PRIMARY_DOMAIN') | default('cn-console.svc.plus', true) }}"
|
||||
console_service_secondary_domain: "{{ lookup('ansible.builtin.env', 'SECONDARY_DOMAIN') | default('cn-console.onwalk.net', true) }}"
|
||||
|
||||
console_service_port: "{{ lookup('ansible.builtin.env', 'PORT') | default('3000', true) }}"
|
||||
console_service_node_env: production
|
||||
console_service_runtime_env: "{{ lookup('ansible.builtin.env', 'RUNTIME_ENV') | default('prod', true) }}"
|
||||
console_service_region: "{{ lookup('ansible.builtin.env', 'REGION') | default('cn', true) }}"
|
||||
console_service_app_base_url: "{{ lookup('ansible.builtin.env', 'APP_BASE_URL') | default('https://' ~ console_service_primary_domain, true) }}"
|
||||
console_service_next_public_app_base_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_APP_BASE_URL') | default(console_service_app_base_url, true) }}"
|
||||
console_service_next_public_site_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_SITE_URL') | default(console_service_app_base_url, true) }}"
|
||||
console_service_next_public_login_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_LOGIN_URL') | default(console_service_app_base_url ~ '/login', true) }}"
|
||||
console_service_next_public_docs_base_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_DOCS_BASE_URL') | default(console_service_app_base_url ~ '/docs', true) }}"
|
||||
console_service_session_cookie_secure: "{{ lookup('ansible.builtin.env', 'SESSION_COOKIE_SECURE') | default('true', true) }}"
|
||||
console_service_next_public_session_cookie_secure: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_SESSION_COOKIE_SECURE') | default('true', true) }}"
|
||||
console_service_runtime_hostname: "{{ lookup('ansible.builtin.env', 'RUNTIME_HOSTNAME') | default(console_service_primary_domain, true) }}"
|
||||
console_service_next_runtime_hostname: "{{ lookup('ansible.builtin.env', 'NEXT_RUNTIME_HOSTNAME') | default(console_service_primary_domain, true) }}"
|
||||
console_service_deployment_hostname: "{{ lookup('ansible.builtin.env', 'DEPLOYMENT_HOSTNAME') | default(console_service_primary_domain, true) }}"
|
||||
console_service_next_public_runtime_environment: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_RUNTIME_ENVIRONMENT') | default('prod', true) }}"
|
||||
console_service_next_public_runtime_region: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_RUNTIME_REGION') | default('cn', true) }}"
|
||||
console_service_account_service_url: "{{ lookup('ansible.builtin.env', 'ACCOUNT_SERVICE_URL') | default('https://accounts.svc.plus', true) }}"
|
||||
console_service_next_public_account_service_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_ACCOUNT_SERVICE_URL') | default(console_service_account_service_url, true) }}"
|
||||
console_service_server_service_url: "{{ lookup('ansible.builtin.env', 'SERVER_SERVICE_URL') | default('https://api.svc.plus', true) }}"
|
||||
console_service_next_public_server_service_url: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_SERVER_SERVICE_URL') | default(console_service_server_service_url, true) }}"
|
||||
console_service_server_service_internal_url: "{{ lookup('ansible.builtin.env', 'SERVER_SERVICE_INTERNAL_URL') | default('', true) }}"
|
||||
console_service_docs_service_url: "{{ lookup('ansible.builtin.env', 'DOCS_SERVICE_URL') | default('https://docs.svc.plus', true) }}"
|
||||
console_service_docs_service_internal_url: "{{ lookup('ansible.builtin.env', 'DOCS_SERVICE_INTERNAL_URL') | default('', true) }}"
|
||||
console_service_openclaw_gateway_remote_url: "{{ lookup('ansible.builtin.env', 'OPENCLAW_GATEWAY_REMOTE_URL') | default('', true) }}"
|
||||
console_service_openclaw_gateway_token: "{{ lookup('ansible.builtin.env', 'OPENCLAW_GATEWAY_TOKEN') | default('', true) }}"
|
||||
console_service_vault_server_url: "{{ lookup('ansible.builtin.env', 'VAULT_SERVER_URL') | default('', true) }}"
|
||||
console_service_vault_namespace: "{{ lookup('ansible.builtin.env', 'VAULT_NAMESPACE') | default('', true) }}"
|
||||
console_service_vault_token: "{{ lookup('ansible.builtin.env', 'VAULT_TOKEN') | default('', true) }}"
|
||||
console_service_apisix_ai_gateway_url: "{{ lookup('ansible.builtin.env', 'APISIX_AI_GATEWAY_URL') | default('', true) }}"
|
||||
console_service_ai_gateway_access_token: "{{ lookup('ansible.builtin.env', 'AI_GATEWAY_ACCESS_TOKEN') | default('', true) }}"
|
||||
console_service_internal_service_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}"
|
||||
console_service_cloudflare_api_token: "{{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') | default('', true) }}"
|
||||
console_service_cloudflare_account_id: "{{ lookup('ansible.builtin.env', 'CLOUDFLARE_ACCOUNT_ID') | default('', true) }}"
|
||||
console_service_cloudflare_web_analytics_site_tag: "{{ lookup('ansible.builtin.env', 'CLOUDFLARE_WEB_ANALYTICS_SITE_TAG') | default('', true) }}"
|
||||
console_service_cloudflare_zone_tag: "{{ lookup('ansible.builtin.env', 'CLOUDFLARE_ZONE_TAG') | default('', true) }}"
|
||||
console_service_root_email_whitelist: "{{ lookup('ansible.builtin.env', 'ROOT_EMAIL_WHITELIST') | default('admin@svc.plus', true) }}"
|
||||
console_service_next_public_paypal_client_id: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_PAYPAL_CLIENT_ID') | default('', true) }}"
|
||||
console_service_next_public_giscus_repo: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_GISCUS_REPO') | default('x-evor/console.svc.plus', true) }}"
|
||||
console_service_next_public_giscus_repo_id: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_GISCUS_REPO_ID') | default('', true) }}"
|
||||
console_service_next_public_giscus_category: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_GISCUS_CATEGORY') | default('General', true) }}"
|
||||
console_service_next_public_giscus_category_id: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_GISCUS_CATEGORY_ID') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xstream_paygo: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xstream_subscription: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xscopehub_paygo: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xscopehub_subscription: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xcloudflow_paygo: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO') | default('', true) }}"
|
||||
console_service_next_public_stripe_price_xcloudflow_subscription: "{{ lookup('ansible.builtin.env', 'NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION') | default('', true) }}"
|
||||
|
||||
console_service_pull_images: true
|
||||
108
roles/vhosts/console_service/tasks/main.yml
Normal file
108
roles/vhosts/console_service/tasks/main.yml
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
- name: Ensure console service base directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ console_service_base_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Assert console deploy uses prebuilt registry image
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- console_service_frontend_image | trim | length > 0
|
||||
- "'/' in (console_service_frontend_image | trim)"
|
||||
- "':' in (console_service_frontend_image | trim)"
|
||||
fail_msg: >-
|
||||
CONSOLE deploy requires a prebuilt image reference via FRONTEND_IMAGE.
|
||||
This playbook only supports pull-only Docker Compose deployment on the target host
|
||||
and must never build images remotely.
|
||||
|
||||
- name: Log into container registry for console service
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
printf '%s' '{{ console_service_registry_password }}' | docker login {{ console_service_registry }} -u '{{ console_service_registry_username }}' --password-stdin
|
||||
args:
|
||||
executable: /bin/bash
|
||||
no_log: true
|
||||
when:
|
||||
- console_service_registry_username | length > 0
|
||||
- console_service_registry_password | length > 0
|
||||
|
||||
- name: Render console compose file
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ console_service_compose_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Render console Caddyfile
|
||||
ansible.builtin.template:
|
||||
src: Caddyfile.j2
|
||||
dest: "{{ console_service_caddyfile }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Render console runtime env file
|
||||
ansible.builtin.template:
|
||||
src: env.runtime.j2
|
||||
dest: "{{ console_service_runtime_env_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
no_log: true
|
||||
|
||||
- name: Validate console compose file
|
||||
ansible.builtin.command: >-
|
||||
docker compose
|
||||
--project-name {{ console_service_project_name }}
|
||||
-f {{ console_service_compose_file }}
|
||||
--env-file {{ console_service_runtime_env_file }}
|
||||
config
|
||||
args:
|
||||
chdir: "{{ console_service_base_dir }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Pull console service images
|
||||
ansible.builtin.command: >-
|
||||
docker compose
|
||||
--project-name {{ console_service_project_name }}
|
||||
-f {{ console_service_compose_file }}
|
||||
--env-file {{ console_service_runtime_env_file }}
|
||||
pull dashboard caddy
|
||||
args:
|
||||
chdir: "{{ console_service_base_dir }}"
|
||||
when: console_service_pull_images | bool
|
||||
|
||||
- name: Refresh console static assets volume
|
||||
ansible.builtin.command: >-
|
||||
docker compose
|
||||
--project-name {{ console_service_project_name }}
|
||||
-f {{ console_service_compose_file }}
|
||||
--env-file {{ console_service_runtime_env_file }}
|
||||
run --rm frontend-assets
|
||||
args:
|
||||
chdir: "{{ console_service_base_dir }}"
|
||||
|
||||
- name: Start console dashboard and caddy containers
|
||||
ansible.builtin.command: >-
|
||||
docker compose
|
||||
--project-name {{ console_service_project_name }}
|
||||
-f {{ console_service_compose_file }}
|
||||
--env-file {{ console_service_runtime_env_file }}
|
||||
up -d --remove-orphans dashboard caddy
|
||||
args:
|
||||
chdir: "{{ console_service_base_dir }}"
|
||||
|
||||
- name: Show console compose status
|
||||
ansible.builtin.command: >-
|
||||
docker compose
|
||||
--project-name {{ console_service_project_name }}
|
||||
-f {{ console_service_compose_file }}
|
||||
--env-file {{ console_service_runtime_env_file }}
|
||||
ps
|
||||
args:
|
||||
chdir: "{{ console_service_base_dir }}"
|
||||
changed_when: false
|
||||
31
roles/vhosts/console_service/templates/Caddyfile.j2
Normal file
31
roles/vhosts/console_service/templates/Caddyfile.j2
Normal file
@ -0,0 +1,31 @@
|
||||
{$PRIMARY_DOMAIN}, {$SECONDARY_DOMAIN} {
|
||||
encode zstd gzip
|
||||
|
||||
@secondary host {$SECONDARY_DOMAIN}
|
||||
redir @secondary https://{$PRIMARY_DOMAIN}{uri} permanent
|
||||
|
||||
handle_path /_next/static/* {
|
||||
root * /srv
|
||||
header Cache-Control "public, max-age=31536000, immutable"
|
||||
file_server
|
||||
}
|
||||
|
||||
@public_assets {
|
||||
file {
|
||||
root /srv/public
|
||||
try_files {path}
|
||||
}
|
||||
}
|
||||
handle @public_assets {
|
||||
root * /srv/public
|
||||
header Cache-Control "public, max-age=3600"
|
||||
file_server
|
||||
}
|
||||
|
||||
reverse_proxy dashboard:3000 {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Host {host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
}
|
||||
}
|
||||
56
roles/vhosts/console_service/templates/docker-compose.yml.j2
Normal file
56
roles/vhosts/console_service/templates/docker-compose.yml.j2
Normal file
@ -0,0 +1,56 @@
|
||||
services:
|
||||
frontend-assets:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: "no"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
rm -rf /assets/_next /assets/chunks /assets/public
|
||||
mkdir -p /assets /assets/public
|
||||
cp -R /app/dashboard/static/. /assets/
|
||||
cp -R /app/dashboard/public/. /assets/public
|
||||
volumes:
|
||||
- frontend_static:/assets
|
||||
|
||||
dashboard:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.runtime
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: {{ console_service_port | quote }}
|
||||
volumes:
|
||||
- frontend_static:/app/dashboard/.next/static:ro
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
caddy:
|
||||
image: caddy:2.10-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dashboard
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
PRIMARY_DOMAIN: ${PRIMARY_DOMAIN:?set PRIMARY_DOMAIN in .env.runtime}
|
||||
SECONDARY_DOMAIN: ${SECONDARY_DOMAIN:?set SECONDARY_DOMAIN in .env.runtime}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- frontend_static:/srv:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
frontend_static:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
50
roles/vhosts/console_service/templates/env.runtime.j2
Normal file
50
roles/vhosts/console_service/templates/env.runtime.j2
Normal file
@ -0,0 +1,50 @@
|
||||
FRONTEND_IMAGE={{ console_service_frontend_image }}
|
||||
PRIMARY_DOMAIN={{ console_service_primary_domain }}
|
||||
SECONDARY_DOMAIN={{ console_service_secondary_domain }}
|
||||
NODE_ENV={{ console_service_node_env }}
|
||||
PORT={{ console_service_port }}
|
||||
RUNTIME_ENV={{ console_service_runtime_env }}
|
||||
REGION={{ console_service_region }}
|
||||
APP_BASE_URL={{ console_service_app_base_url }}
|
||||
NEXT_PUBLIC_APP_BASE_URL={{ console_service_next_public_app_base_url }}
|
||||
NEXT_PUBLIC_SITE_URL={{ console_service_next_public_site_url }}
|
||||
NEXT_PUBLIC_LOGIN_URL={{ console_service_next_public_login_url }}
|
||||
NEXT_PUBLIC_DOCS_BASE_URL={{ console_service_next_public_docs_base_url }}
|
||||
SESSION_COOKIE_SECURE={{ console_service_session_cookie_secure }}
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE={{ console_service_next_public_session_cookie_secure }}
|
||||
RUNTIME_HOSTNAME={{ console_service_runtime_hostname }}
|
||||
NEXT_RUNTIME_HOSTNAME={{ console_service_next_runtime_hostname }}
|
||||
DEPLOYMENT_HOSTNAME={{ console_service_deployment_hostname }}
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT={{ console_service_next_public_runtime_environment }}
|
||||
NEXT_PUBLIC_RUNTIME_REGION={{ console_service_next_public_runtime_region }}
|
||||
ACCOUNT_SERVICE_URL={{ console_service_account_service_url }}
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL={{ console_service_next_public_account_service_url }}
|
||||
SERVER_SERVICE_URL={{ console_service_server_service_url }}
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL={{ console_service_next_public_server_service_url }}
|
||||
SERVER_SERVICE_INTERNAL_URL={{ console_service_server_service_internal_url }}
|
||||
DOCS_SERVICE_URL={{ console_service_docs_service_url }}
|
||||
DOCS_SERVICE_INTERNAL_URL={{ console_service_docs_service_internal_url }}
|
||||
OPENCLAW_GATEWAY_REMOTE_URL={{ console_service_openclaw_gateway_remote_url }}
|
||||
OPENCLAW_GATEWAY_TOKEN={{ console_service_openclaw_gateway_token }}
|
||||
VAULT_SERVER_URL={{ console_service_vault_server_url }}
|
||||
VAULT_NAMESPACE={{ console_service_vault_namespace }}
|
||||
VAULT_TOKEN={{ console_service_vault_token }}
|
||||
APISIX_AI_GATEWAY_URL={{ console_service_apisix_ai_gateway_url }}
|
||||
AI_GATEWAY_ACCESS_TOKEN={{ console_service_ai_gateway_access_token }}
|
||||
INTERNAL_SERVICE_TOKEN={{ console_service_internal_service_token }}
|
||||
CLOUDFLARE_API_TOKEN={{ console_service_cloudflare_api_token }}
|
||||
CLOUDFLARE_ACCOUNT_ID={{ console_service_cloudflare_account_id }}
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG={{ console_service_cloudflare_web_analytics_site_tag }}
|
||||
CLOUDFLARE_ZONE_TAG={{ console_service_cloudflare_zone_tag }}
|
||||
ROOT_EMAIL_WHITELIST={{ console_service_root_email_whitelist }}
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID={{ console_service_next_public_paypal_client_id }}
|
||||
NEXT_PUBLIC_GISCUS_REPO={{ console_service_next_public_giscus_repo }}
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID={{ console_service_next_public_giscus_repo_id }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY={{ console_service_next_public_giscus_category }}
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID={{ console_service_next_public_giscus_category_id }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO={{ console_service_next_public_stripe_price_xstream_paygo }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION={{ console_service_next_public_stripe_price_xstream_subscription }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO={{ console_service_next_public_stripe_price_xscopehub_paygo }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION={{ console_service_next_public_stripe_price_xscopehub_subscription }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO={{ console_service_next_public_stripe_price_xcloudflow_paygo }}
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION={{ console_service_next_public_stripe_price_xcloudflow_subscription }}
|
||||
38
roles/vhosts/deploy_acp_vhosts/defaults/main.yml
Normal file
38
roles/vhosts/deploy_acp_vhosts/defaults/main.yml
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
deploy_acp_codex: true
|
||||
deploy_acp_opencode: true
|
||||
deploy_acp_gemini: true
|
||||
|
||||
xworkmate_bridge_service_name: xworkmate-bridge
|
||||
xworkmate_bridge_service_user: root
|
||||
xworkmate_bridge_service_group: root
|
||||
xworkmate_bridge_workdir: /root
|
||||
xworkmate_bridge_listen_host: 127.0.0.1
|
||||
xworkmate_bridge_listen_port: 8787
|
||||
xworkmate_bridge_binary_path: /usr/local/bin/xworkmate-go-core
|
||||
xworkmate_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge"
|
||||
xworkmate_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/xworkmate_bridge"
|
||||
xworkmate_bridge_local_binary_path: "{{ xworkmate_bridge_local_build_dir }}/xworkmate-go-core"
|
||||
xworkmate_bridge_build_goos: linux
|
||||
xworkmate_bridge_build_goarch: amd64
|
||||
|
||||
xworkmate_bridge_domain: acp-server.svc.plus
|
||||
xworkmate_bridge_public_base_url: https://acp-server.svc.plus
|
||||
xworkmate_bridge_caddyfile_path: /etc/caddy/Caddyfile
|
||||
xworkmate_bridge_caddy_conf_dir: /etc/caddy/conf.d
|
||||
xworkmate_bridge_caddy_fragment_path: /etc/caddy/conf.d/acp-server.caddy
|
||||
xworkmate_bridge_codex_upstream_host: 127.0.0.1
|
||||
xworkmate_bridge_codex_upstream_port: 9010
|
||||
xworkmate_bridge_opencode_upstream_host: 127.0.0.1
|
||||
xworkmate_bridge_opencode_upstream_port: 3910
|
||||
xworkmate_bridge_gemini_upstream_host: 127.0.0.1
|
||||
xworkmate_bridge_gemini_upstream_port: 8791
|
||||
xworkmate_bridge_obsolete_caddy_fragment_paths:
|
||||
- /etc/caddy/conf.d/acp-server-codex.caddy
|
||||
- /etc/caddy/conf.d/acp-server-opencode.caddy
|
||||
- /etc/caddy/conf.d/acp-server-gemini.caddy
|
||||
- /etc/caddy/conf.d/acp-server-bridge.caddy
|
||||
- /etc/caddy/conf.d/acp-server-bridge-server.caddy
|
||||
|
||||
xworkmate_bridge_packages:
|
||||
- caddy
|
||||
11
roles/vhosts/deploy_acp_vhosts/handlers/main.yml
Normal file
11
roles/vhosts/deploy_acp_vhosts/handlers/main.yml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Restart xworkmate bridge
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ xworkmate_bridge_service_name }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
|
||||
- name: Reload caddy
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
state: reloaded
|
||||
115
roles/vhosts/deploy_acp_vhosts/tasks/main.yml
Normal file
115
roles/vhosts/deploy_acp_vhosts/tasks/main.yml
Normal file
@ -0,0 +1,115 @@
|
||||
---
|
||||
- name: Install xworkmate-bridge prerequisites
|
||||
ansible.builtin.package:
|
||||
name: "{{ xworkmate_bridge_packages }}"
|
||||
state: present
|
||||
|
||||
- name: Ensure local xworkmate-bridge build directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ xworkmate_bridge_local_build_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Build xworkmate-bridge locally
|
||||
ansible.builtin.command:
|
||||
cmd: go build -o "{{ xworkmate_bridge_local_binary_path }}" .
|
||||
chdir: "{{ xworkmate_bridge_local_source_dir }}"
|
||||
environment:
|
||||
GOOS: "{{ xworkmate_bridge_build_goos }}"
|
||||
GOARCH: "{{ xworkmate_bridge_build_goarch }}"
|
||||
CGO_ENABLED: "0"
|
||||
GO111MODULE: "on"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Upload xworkmate-bridge binary
|
||||
ansible.builtin.copy:
|
||||
src: "{{ xworkmate_bridge_local_binary_path }}"
|
||||
dest: "{{ xworkmate_bridge_binary_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
notify: Restart xworkmate bridge
|
||||
|
||||
- name: Include Codex ACP provider role
|
||||
ansible.builtin.import_role:
|
||||
name: roles/vhosts/acp_codex
|
||||
vars:
|
||||
acp_codex_manage_caddy: false
|
||||
when:
|
||||
- deploy_acp_codex | bool
|
||||
|
||||
- name: Include OpenCode ACP provider role
|
||||
ansible.builtin.import_role:
|
||||
name: roles/vhosts/acp_opencode
|
||||
vars:
|
||||
acp_opencode_manage_caddy: false
|
||||
when:
|
||||
- deploy_acp_opencode | bool
|
||||
|
||||
- name: Include Gemini ACP provider role
|
||||
ansible.builtin.import_role:
|
||||
name: roles/vhosts/acp_gemini
|
||||
when:
|
||||
- deploy_acp_gemini | bool
|
||||
|
||||
- name: Ensure Caddy fragment directory exists for ACP ingress
|
||||
ansible.builtin.file:
|
||||
path: "{{ xworkmate_bridge_caddy_conf_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy ACP Caddy main file
|
||||
ansible.builtin.copy:
|
||||
content: "import {{ xworkmate_bridge_caddy_conf_dir }}/*.caddy\n"
|
||||
dest: "{{ xworkmate_bridge_caddyfile_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
|
||||
- name: Deploy unified ACP Caddy site
|
||||
ansible.builtin.template:
|
||||
src: acp-site.caddy.j2
|
||||
dest: "{{ xworkmate_bridge_caddy_fragment_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
|
||||
- name: Remove deprecated ACP Caddy fragments
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop: "{{ xworkmate_bridge_obsolete_caddy_fragment_paths }}"
|
||||
notify: Reload caddy
|
||||
|
||||
- name: Deploy xworkmate-bridge systemd service
|
||||
ansible.builtin.template:
|
||||
src: xworkmate-bridge.service.j2
|
||||
dest: "/etc/systemd/system/{{ xworkmate_bridge_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart xworkmate bridge
|
||||
|
||||
- name: Ensure xworkmate-bridge is enabled and running
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ xworkmate_bridge_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
|
||||
- name: Ensure Caddy is enabled and running for ACP ingress
|
||||
ansible.builtin.systemd:
|
||||
name: caddy
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Include ACP ingress validation tasks
|
||||
ansible.builtin.import_tasks: validate.yml
|
||||
tags: [deploy_acp_vhosts, deploy_acp_vhosts_validate]
|
||||
108
roles/vhosts/deploy_acp_vhosts/tasks/validate.yml
Normal file
108
roles/vhosts/deploy_acp_vhosts/tasks/validate.yml
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
- name: Validate Caddy config for unified ACP ingress
|
||||
ansible.builtin.command: caddy validate --config "{{ xworkmate_bridge_caddyfile_path }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Read deployed ACP Caddy fragment
|
||||
ansible.builtin.command:
|
||||
cmd: cat "{{ xworkmate_bridge_caddy_fragment_path }}"
|
||||
changed_when: false
|
||||
register: xworkmate_bridge_fragment
|
||||
|
||||
- name: Assert Codex route exists in deployed ACP fragment
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'handle_path /codex*' in xworkmate_bridge_fragment.stdout"
|
||||
fail_msg: "Missing /codex route in {{ xworkmate_bridge_caddy_fragment_path }}"
|
||||
when:
|
||||
- deploy_acp_codex | bool
|
||||
|
||||
- name: Assert OpenCode route exists in deployed ACP fragment
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'handle_path /opencode*' in xworkmate_bridge_fragment.stdout"
|
||||
fail_msg: "Missing /opencode route in {{ xworkmate_bridge_caddy_fragment_path }}"
|
||||
when:
|
||||
- deploy_acp_opencode | bool
|
||||
|
||||
- name: Assert Gemini route exists in deployed ACP fragment
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'handle_path /gemini*' in xworkmate_bridge_fragment.stdout"
|
||||
fail_msg: "Missing /gemini route in {{ xworkmate_bridge_caddy_fragment_path }}"
|
||||
when:
|
||||
- deploy_acp_gemini | bool
|
||||
|
||||
- name: Check Codex route through unified ACP ingress
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1/codex"
|
||||
method: GET
|
||||
headers:
|
||||
Host: "{{ xworkmate_bridge_domain }}"
|
||||
follow_redirects: none
|
||||
status_code: [200, 301, 302, 307, 308, 401, 403, 404, 502]
|
||||
changed_when: false
|
||||
register: xworkmate_bridge_codex_redirect
|
||||
when:
|
||||
- deploy_acp_codex | bool
|
||||
|
||||
- name: Check OpenCode route through unified ACP ingress
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1/opencode"
|
||||
method: GET
|
||||
headers:
|
||||
Host: "{{ xworkmate_bridge_domain }}"
|
||||
follow_redirects: none
|
||||
status_code: [200, 301, 302, 307, 308, 401, 403, 404, 502]
|
||||
changed_when: false
|
||||
register: xworkmate_bridge_opencode_redirect
|
||||
when:
|
||||
- deploy_acp_opencode | bool
|
||||
|
||||
- name: Check Gemini route through unified ACP ingress
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1/gemini"
|
||||
method: GET
|
||||
headers:
|
||||
Host: "{{ xworkmate_bridge_domain }}"
|
||||
follow_redirects: none
|
||||
status_code: [200, 301, 302, 307, 308, 401, 403, 404, 502]
|
||||
changed_when: false
|
||||
register: xworkmate_bridge_gemini_redirect
|
||||
when:
|
||||
- deploy_acp_gemini | bool
|
||||
|
||||
- name: Check deprecated ACP fragments are absent
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
changed_when: false
|
||||
loop: "{{ xworkmate_bridge_obsolete_caddy_fragment_paths }}"
|
||||
register: xworkmate_bridge_obsolete_fragments
|
||||
|
||||
- name: Assert deprecated ACP fragments were removed
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not item.stat.exists
|
||||
fail_msg: "Deprecated ACP fragment still exists: {{ item.stat.path }}"
|
||||
loop: "{{ xworkmate_bridge_obsolete_fragments.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.stat.path }}"
|
||||
|
||||
- name: Show xworkmate-bridge service status
|
||||
ansible.builtin.command: systemctl status "{{ xworkmate_bridge_service_name }}" --no-pager
|
||||
register: xworkmate_bridge_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Summarize unified ACP ingress state
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Unified domain: {{ xworkmate_bridge_domain }}"
|
||||
- "Codex public base URL: https://{{ xworkmate_bridge_domain }}/codex"
|
||||
- "OpenCode public base URL: https://{{ xworkmate_bridge_domain }}/opencode"
|
||||
- "Gemini public base URL: https://{{ xworkmate_bridge_domain }}/gemini"
|
||||
- "Bridge service: {{ xworkmate_bridge_status.stdout | default('N/A') }}"
|
||||
- "Codex route: /codex -> {{ xworkmate_bridge_codex_upstream_host }}:{{ xworkmate_bridge_codex_upstream_port }}"
|
||||
- "OpenCode route: /opencode -> {{ xworkmate_bridge_opencode_upstream_host }}:{{ xworkmate_bridge_opencode_upstream_port }}"
|
||||
- "Gemini route: /gemini -> {{ xworkmate_bridge_gemini_upstream_host }}:{{ xworkmate_bridge_gemini_upstream_port }}"
|
||||
- "Deployed fragment: {{ xworkmate_bridge_fragment.stdout | default('N/A') }}"
|
||||
19
roles/vhosts/deploy_acp_vhosts/templates/acp-site.caddy.j2
Normal file
19
roles/vhosts/deploy_acp_vhosts/templates/acp-site.caddy.j2
Normal file
@ -0,0 +1,19 @@
|
||||
{{ xworkmate_bridge_domain }} {
|
||||
{% if deploy_acp_codex | bool %}
|
||||
handle_path /codex* {
|
||||
reverse_proxy {{ xworkmate_bridge_codex_upstream_host }}:{{ xworkmate_bridge_codex_upstream_port }}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if deploy_acp_opencode | bool %}
|
||||
handle_path /opencode* {
|
||||
reverse_proxy {{ xworkmate_bridge_opencode_upstream_host }}:{{ xworkmate_bridge_opencode_upstream_port }}
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
{% if deploy_acp_gemini | bool %}
|
||||
handle_path /gemini* {
|
||||
reverse_proxy {{ xworkmate_bridge_gemini_upstream_host }}:{{ xworkmate_bridge_gemini_upstream_port }}
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=XWorkmate Bridge ACP Service
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ xworkmate_bridge_service_user }}
|
||||
Group={{ xworkmate_bridge_service_group }}
|
||||
WorkingDirectory={{ xworkmate_bridge_workdir }}
|
||||
ExecStart={{ xworkmate_bridge_binary_path }} serve --listen {{ xworkmate_bridge_listen_host }}:{{ xworkmate_bridge_listen_port }}
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -25,3 +25,6 @@ nextjs_private_tmp: true
|
||||
|
||||
nextjs_install_deps: true
|
||||
nextjs_build: "{{ nextjs_run_mode == 'prod' }}"
|
||||
nextjs_env_file_path: "{{ nextjs_app_dir }}/.env"
|
||||
nextjs_env_src: ""
|
||||
nextjs_env_content: ""
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
---
|
||||
- name: Ensure git is installed
|
||||
ansible.builtin.apt:
|
||||
name: git
|
||||
name:
|
||||
- git
|
||||
- rsync
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
@ -21,6 +23,28 @@
|
||||
update: true
|
||||
notify: Restart nextjs service
|
||||
|
||||
- name: Copy Next.js environment file from source
|
||||
ansible.builtin.copy:
|
||||
src: "{{ nextjs_env_src }}"
|
||||
dest: "{{ nextjs_env_file_path }}"
|
||||
owner: "{{ nextjs_service_user }}"
|
||||
group: "{{ nextjs_service_group }}"
|
||||
mode: "0600"
|
||||
when: nextjs_env_src | length > 0
|
||||
notify: Restart nextjs service
|
||||
|
||||
- name: Render inline Next.js environment file
|
||||
ansible.builtin.copy:
|
||||
content: "{{ nextjs_env_content }}"
|
||||
dest: "{{ nextjs_env_file_path }}"
|
||||
owner: "{{ nextjs_service_user }}"
|
||||
group: "{{ nextjs_service_group }}"
|
||||
mode: "0600"
|
||||
when:
|
||||
- nextjs_env_src | length == 0
|
||||
- nextjs_env_content | length > 0
|
||||
notify: Restart nextjs service
|
||||
|
||||
- name: Install Next.js dependencies
|
||||
ansible.builtin.command: yarn install
|
||||
args:
|
||||
|
||||
@ -6,6 +6,7 @@ After=network.target
|
||||
Type=simple
|
||||
WorkingDirectory={{ nextjs_app_dir }}
|
||||
|
||||
EnvironmentFile=-{{ nextjs_env_file_path }}
|
||||
Environment=NODE_ENV={{ nextjs_node_env }}
|
||||
Environment=PORT={{ nextjs_port }}
|
||||
|
||||
|
||||
23
roles/vhosts/xray-exporter/defaults/main.yml
Normal file
23
roles/vhosts/xray-exporter/defaults/main.yml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
xray_exporter_source_dir: "{{ playbook_dir }}/../xray-exporter"
|
||||
xray_exporter_app_dir: "/opt/xray-exporter"
|
||||
xray_exporter_go_version: "1.25.1"
|
||||
xray_exporter_go_root: "/usr/local/go"
|
||||
xray_exporter_go_bin: "{{ xray_exporter_go_root }}/bin/go"
|
||||
xray_exporter_binary_name: "xray-exporter"
|
||||
xray_exporter_binary_path: "/usr/local/bin/{{ xray_exporter_binary_name }}"
|
||||
xray_exporter_service_name: "xray-exporter"
|
||||
xray_exporter_service_description: "xray-exporter service"
|
||||
xray_exporter_user: "root"
|
||||
xray_exporter_group: "root"
|
||||
xray_exporter_env_dir: "/etc/default"
|
||||
xray_exporter_env_path: "{{ xray_exporter_env_dir }}/xray-exporter"
|
||||
|
||||
xray_exporter_listen_addr: "127.0.0.1:8080"
|
||||
xray_exporter_scrape_interval: "1m"
|
||||
xray_exporter_node_id: "node-xhttp.svc.plus"
|
||||
xray_exporter_env_name: "prod"
|
||||
xray_exporter_stats_url: "http://127.0.0.1:49227/debug/vars"
|
||||
xray_exporter_stats_token: ""
|
||||
xray_exporter_accounts_base_url: "https://accounts.svc.plus"
|
||||
xray_exporter_internal_service_token: ""
|
||||
6
roles/vhosts/xray-exporter/handlers/main.yml
Normal file
6
roles/vhosts/xray-exporter/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart xray-exporter
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ xray_exporter_service_name }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
102
roles/vhosts/xray-exporter/tasks/main.yml
Normal file
102
roles/vhosts/xray-exporter/tasks/main.yml
Normal file
@ -0,0 +1,102 @@
|
||||
---
|
||||
- name: Map Go architecture
|
||||
ansible.builtin.set_fact:
|
||||
xray_exporter_go_arch: >-
|
||||
{{ 'arm64' if ansible_architecture in ['aarch64', 'arm64'] else 'amd64' }}
|
||||
|
||||
- name: Ensure xray-exporter build prerequisites are installed
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- curl
|
||||
- ca-certificates
|
||||
- tar
|
||||
- rsync
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Check installed Go version
|
||||
ansible.builtin.command: "{{ xray_exporter_go_bin }} version"
|
||||
register: xray_exporter_go_version_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Download Go toolchain archive
|
||||
ansible.builtin.get_url:
|
||||
url: "https://go.dev/dl/go{{ xray_exporter_go_version }}.linux-{{ xray_exporter_go_arch }}.tar.gz"
|
||||
dest: "/tmp/go{{ xray_exporter_go_version }}.linux-{{ xray_exporter_go_arch }}.tar.gz"
|
||||
mode: "0644"
|
||||
when: xray_exporter_go_version_check.rc != 0 or ('go' ~ xray_exporter_go_version) not in xray_exporter_go_version_check.stdout
|
||||
|
||||
- name: Remove previous Go installation
|
||||
ansible.builtin.file:
|
||||
path: "{{ xray_exporter_go_root }}"
|
||||
state: absent
|
||||
when: xray_exporter_go_version_check.rc != 0 or ('go' ~ xray_exporter_go_version) not in xray_exporter_go_version_check.stdout
|
||||
|
||||
- name: Install Go toolchain
|
||||
ansible.builtin.unarchive:
|
||||
src: "/tmp/go{{ xray_exporter_go_version }}.linux-{{ xray_exporter_go_arch }}.tar.gz"
|
||||
dest: "/usr/local"
|
||||
remote_src: true
|
||||
when: xray_exporter_go_version_check.rc != 0 or ('go' ~ xray_exporter_go_version) not in xray_exporter_go_version_check.stdout
|
||||
|
||||
- name: Create xray-exporter directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ xray_exporter_user }}"
|
||||
group: "{{ xray_exporter_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ xray_exporter_app_dir }}"
|
||||
- "{{ xray_exporter_env_dir }}"
|
||||
|
||||
- name: Sync xray-exporter source tree
|
||||
ansible.posix.synchronize:
|
||||
src: "{{ xray_exporter_source_dir }}/"
|
||||
dest: "{{ xray_exporter_app_dir }}/"
|
||||
delete: true
|
||||
notify: Restart xray-exporter
|
||||
|
||||
- name: Build xray-exporter binary
|
||||
ansible.builtin.command: >-
|
||||
{{ xray_exporter_go_bin }} build -buildvcs=false -o {{ xray_exporter_binary_path }} ./cmd/xray-exporter
|
||||
args:
|
||||
chdir: "{{ xray_exporter_app_dir }}"
|
||||
environment:
|
||||
GOROOT: "{{ xray_exporter_go_root }}"
|
||||
PATH: "{{ xray_exporter_go_root }}/bin:{{ ansible_env.PATH }}"
|
||||
GOTOOLCHAIN: local
|
||||
notify: Restart xray-exporter
|
||||
|
||||
- name: Ensure xray-exporter binary permissions
|
||||
ansible.builtin.file:
|
||||
path: "{{ xray_exporter_binary_path }}"
|
||||
owner: "{{ xray_exporter_user }}"
|
||||
group: "{{ xray_exporter_group }}"
|
||||
mode: "0755"
|
||||
|
||||
- name: Deploy xray-exporter environment file
|
||||
ansible.builtin.template:
|
||||
src: xray-exporter.env.j2
|
||||
dest: "{{ xray_exporter_env_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
notify: Restart xray-exporter
|
||||
|
||||
- name: Install xray-exporter systemd service
|
||||
ansible.builtin.template:
|
||||
src: xray-exporter.service.j2
|
||||
dest: "/etc/systemd/system/{{ xray_exporter_service_name }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Restart xray-exporter
|
||||
|
||||
- name: Enable and start xray-exporter
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ xray_exporter_service_name }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
@ -0,0 +1,8 @@
|
||||
XRAY_STATS_URL={{ xray_exporter_stats_url }}
|
||||
XRAY_STATS_TOKEN={{ xray_exporter_stats_token }}
|
||||
ACCOUNTS_BASE_URL={{ xray_exporter_accounts_base_url }}
|
||||
INTERNAL_SERVICE_TOKEN={{ xray_exporter_internal_service_token }}
|
||||
EXPORTER_NODE_ID={{ xray_exporter_node_id }}
|
||||
EXPORTER_ENV={{ xray_exporter_env_name }}
|
||||
SCRAPE_INTERVAL={{ xray_exporter_scrape_interval }}
|
||||
LISTEN_ADDR={{ xray_exporter_listen_addr }}
|
||||
@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description={{ xray_exporter_service_description }}
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory={{ xray_exporter_app_dir }}
|
||||
EnvironmentFile={{ xray_exporter_env_path }}
|
||||
ExecStart={{ xray_exporter_binary_path }}
|
||||
Restart=always
|
||||
User={{ xray_exporter_user }}
|
||||
Group={{ xray_exporter_group }}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@ -45,6 +45,11 @@ cloudflare_dns_records:
|
||||
content: 46.250.251.132
|
||||
ttl: 1
|
||||
proxied: false
|
||||
- type: A
|
||||
name: xworkmate-bridge.svc.plus
|
||||
content: 46.250.251.132
|
||||
ttl: 1
|
||||
proxied: false
|
||||
- type: CNAME
|
||||
name: console-8fa9cd3-contabo.svc.plus
|
||||
content: jp-xhttp-contabo.svc.plus
|
||||
@ -85,3 +90,8 @@ cloudflare_dns_records:
|
||||
content: vps-preview-accounts.svc.plus
|
||||
ttl: 1
|
||||
proxied: false
|
||||
- type: CNAME
|
||||
name: zitadel.svc.plus
|
||||
content: jp-xhttp-contabo.svc.plus
|
||||
ttl: 1
|
||||
proxied: false
|
||||
|
||||
Loading…
Reference in New Issue
Block a user