Merge pull request #99 from cloud-neutral/codex/implement-xcontrol-docker-playbook
Template rag server configuration
This commit is contained in:
commit
ff8c0d2c3d
113
playbooks/roles/docker/XControl/README.md
Normal file
113
playbooks/roles/docker/XControl/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
# XControl Docker role
|
||||
|
||||
This role provisions the XControl stack (Postgres, account service, RAG server, dashboard, Redis, and Nginx proxy with Certbot assets). Templates from `templates/` and static assets from `files/` are rendered into `{{ xcontrol_workspace }}` and the Docker Compose stack is started.
|
||||
|
||||
## Layout
|
||||
```
|
||||
files/
|
||||
├── docker-compose.yaml
|
||||
├── config/
|
||||
│ ├── account.yaml
|
||||
│ └── server.yaml
|
||||
├── certbot/
|
||||
│ ├── conf/
|
||||
│ └── www/
|
||||
├── nginx/
|
||||
│ ├── conf.d/
|
||||
│ │ ├── accounts.conf
|
||||
│ │ ├── artifact.conf
|
||||
│ │ ├── bootstrap-nginx.conf
|
||||
│ │ ├── default.conf
|
||||
│ │ ├── homepage.conf
|
||||
│ │ └── rag-server.conf
|
||||
│ └── nginx.conf
|
||||
└── run.sh
|
||||
```
|
||||
|
||||
## Defaults
|
||||
- `xcontrol_deploy_dir`: `/opt/xcontrol`
|
||||
- `xcontrol_workspace`: `{{ xcontrol_deploy_dir }}`
|
||||
- `xcontrol_certbot_domains`: `svc.plus` (comma-separated)
|
||||
- `xcontrol_certbot_email`: `manbuzhe2009@qq.com`
|
||||
- `xcontrol_homepage_domain`: `{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_homepage_alias_domain`: `www.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_homepage_cn_domain`: `cn-homepage.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_accounts_domain`: `accounts.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_rag_domain`: `rag-server.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_rag_api_domain`: `api.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_artifact_domain`: `dl.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_artifact_cn_domain`: `cn-dl.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_db_host`: `db`
|
||||
- `xcontrol_db_port`: `5432`
|
||||
- `xcontrol_db_name`: `xcontrol`
|
||||
- `xcontrol_db_user`: `xcontrol`
|
||||
- `xcontrol_db_password`: `xcontrol`
|
||||
- `xcontrol_account_mode`: `server-agent`
|
||||
- `xcontrol_account_log_level`: `info`
|
||||
- `xcontrol_account_auth_enable`: `true`
|
||||
- `xcontrol_account_public_token`: `xcontrol-public-token-2024`
|
||||
- `xcontrol_account_refresh_secret`: `xcontrol-refresh-secret-2024`
|
||||
- `xcontrol_account_access_secret`: `xcontrol-access-secret-2024`
|
||||
- `xcontrol_account_access_expiry`: `1h`
|
||||
- `xcontrol_account_refresh_expiry`: `168h`
|
||||
- `xcontrol_account_server_addr`: `:8080`
|
||||
- `xcontrol_account_read_timeout`: `15s`
|
||||
- `xcontrol_account_write_timeout`: `15s`
|
||||
- `xcontrol_account_public_url`: `https://accounts.{{ xcontrol_primary_domain }}`
|
||||
- `xcontrol_account_tls_enabled`: `false`
|
||||
- `xcontrol_account_tls_redirect_http`: `false`
|
||||
- `xcontrol_account_store_driver`: `postgres`
|
||||
- `xcontrol_account_db_name`: `{{ xcontrol_db_name }}`
|
||||
- `xcontrol_account_db_sslmode`: `disable`
|
||||
- `xcontrol_account_db_max_open_conns`: `30`
|
||||
- `xcontrol_account_db_max_idle_conns`: `10`
|
||||
- `xcontrol_account_session_ttl`: `24h`
|
||||
- `xcontrol_account_session_cache`: `memory`
|
||||
- `xcontrol_account_smtp_host`: `smtp.example.com`
|
||||
- `xcontrol_account_smtp_port`: `587`
|
||||
- `xcontrol_account_smtp_username`: `apikey`
|
||||
- `xcontrol_account_smtp_password`: `change-me`
|
||||
- `xcontrol_account_smtp_from`: `XControl Account <no-reply@example.com>`
|
||||
- `xcontrol_account_smtp_timeout`: `10s`
|
||||
- `xcontrol_account_smtp_tls_mode`: `auto`
|
||||
- `xcontrol_account_smtp_tls_insecure_skip_verify`: `false`
|
||||
- `xcontrol_account_xray_sync_enabled`: `false`
|
||||
- `xcontrol_account_xray_sync_interval`: `5m`
|
||||
- `xcontrol_account_xray_output_path`: `/usr/local/etc/xray/config.json`
|
||||
- `xcontrol_account_xray_template_path`: `account/config/xray.config.template.json`
|
||||
- `xcontrol_account_xray_validate_command`: `[]`
|
||||
- `xcontrol_account_xray_restart_command`: `["systemctl", "restart", "xray.service"]`
|
||||
- `xcontrol_account_agent_id`: `account-primary`
|
||||
- `xcontrol_rag_server_addr`: `:8090`
|
||||
- `xcontrol_rag_read_timeout`: `120s`
|
||||
- `xcontrol_rag_write_timeout`: `120s`
|
||||
- `xcontrol_rag_public_url`: `https://{{ xcontrol_rag_api_domain }}`
|
||||
- `xcontrol_rag_allowed_origins`: `["https://{{ xcontrol_rag_api_domain }}", "https://{{ xcontrol_homepage_alias_domain }}", "https://{{ xcontrol_homepage_domain }}", "https://{{ xcontrol_accounts_domain }}", "http://localhost:3000", "http://127.0.0.1:3000"]`
|
||||
- `xcontrol_rag_auth_enable`: `false`
|
||||
- `xcontrol_rag_auth_url`: `https://{{ xcontrol_accounts_domain }}`
|
||||
- `xcontrol_rag_api_base_url`: `https://{{ xcontrol_rag_api_domain }}`
|
||||
- `xcontrol_rag_public_token`: `xcontrol-public-token-2025`
|
||||
- `xcontrol_rag_redis_addr`: `""`
|
||||
- `xcontrol_rag_redis_password`: `""`
|
||||
- `xcontrol_rag_vectordb_db_name`: `rag`
|
||||
- `xcontrol_rag_vectordb_sslmode`: `disable`
|
||||
- `xcontrol_rag_vectordb_pgurl`: `postgres://{{ xcontrol_db_user }}:{{ xcontrol_db_password }}@{{ xcontrol_db_host }}:{{ xcontrol_db_port }}/{{ xcontrol_rag_vectordb_db_name }}?sslmode={{ xcontrol_rag_vectordb_sslmode }}`
|
||||
- `xcontrol_rag_datasources`: `[{"name": "XControl", "repo": "https://github.com/svc-design/XControl", "path": "docs"}]`
|
||||
- `xcontrol_rag_sync_repo_proxy`: `""`
|
||||
- `xcontrol_rag_embedder_provider`: `chutes`
|
||||
- `xcontrol_rag_embedder_models`: `["bge-m3"]`
|
||||
- `xcontrol_rag_embedder_baseurl`: `http://127.0.0.1:9000`
|
||||
- `xcontrol_rag_embedder_endpoint`: `http://127.0.0.1:9000/v1/embeddings`
|
||||
- `xcontrol_rag_generator_provider`: `chutes`
|
||||
- `xcontrol_rag_generator_models`: `["deepseek-r1:8b"]`
|
||||
- `xcontrol_rag_generator_baseurl`: `http://127.0.0.1:11434`
|
||||
- `xcontrol_rag_generator_endpoint`: `http://127.0.0.1:11434/v1/chat/completions`
|
||||
- `xcontrol_rag_embedding_max_batch`: `64`
|
||||
- `xcontrol_rag_embedding_dimension`: `1024`
|
||||
|
||||
## RUN
|
||||
|
||||
```
|
||||
ansible-playbook -i inventory.ini deploy_XControl_docker.yaml -e "domain=svc.plus" -D -C -l svc.plus
|
||||
ansible-playbook -i inventory.ini deploy_XControl_docker.yaml -e "domain=svc.plus" -D -l svc.plus
|
||||
```
|
||||
112
playbooks/roles/docker/XControl/defaults/main.yml
Normal file
112
playbooks/roles/docker/XControl/defaults/main.yml
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
# Default deployment directory for XControl Docker stack
|
||||
xcontrol_deploy_dir: /opt/xcontrol
|
||||
xcontrol_workspace: "{{ xcontrol_deploy_dir }}"
|
||||
|
||||
# Primary domain (first in the comma-separated domain list)
|
||||
xcontrol_certbot_domains: svc.plus
|
||||
xcontrol_primary_domain: "{{ xcontrol_certbot_domains.split(',')[0] | trim }}"
|
||||
xcontrol_certbot_email: manbuzhe2009@qq.com
|
||||
|
||||
# Subdomains for individual services
|
||||
xcontrol_homepage_domain: "{{ xcontrol_primary_domain }}"
|
||||
xcontrol_homepage_alias_domain: "www.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_homepage_cn_domain: "cn-homepage.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_accounts_domain: "accounts.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_rag_domain: "rag-server.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_rag_api_domain: "api.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_artifact_domain: "dl.{{ xcontrol_primary_domain }}"
|
||||
xcontrol_artifact_cn_domain: "cn-dl.{{ xcontrol_primary_domain }}"
|
||||
|
||||
# Database defaults
|
||||
xcontrol_db_host: db
|
||||
xcontrol_db_port: 5432
|
||||
xcontrol_db_name: xcontrol
|
||||
xcontrol_db_user: xcontrol
|
||||
xcontrol_db_password: xcontrol
|
||||
|
||||
# Account service configuration defaults
|
||||
xcontrol_account_mode: server-agent
|
||||
xcontrol_account_log_level: info
|
||||
xcontrol_account_auth_enable: true
|
||||
xcontrol_account_public_token: xcontrol-public-token-2024
|
||||
xcontrol_account_refresh_secret: xcontrol-refresh-secret-2024
|
||||
xcontrol_account_access_secret: xcontrol-access-secret-2024
|
||||
xcontrol_account_access_expiry: 1h
|
||||
xcontrol_account_refresh_expiry: 168h
|
||||
xcontrol_account_server_addr: ":8080"
|
||||
xcontrol_account_read_timeout: 15s
|
||||
xcontrol_account_write_timeout: 15s
|
||||
xcontrol_account_public_url: "https://{{ xcontrol_accounts_domain }}"
|
||||
xcontrol_account_tls_enabled: false
|
||||
xcontrol_account_tls_redirect_http: false
|
||||
xcontrol_account_store_driver: postgres
|
||||
xcontrol_account_db_name: "{{ xcontrol_db_name }}"
|
||||
xcontrol_account_db_sslmode: disable
|
||||
xcontrol_account_db_max_open_conns: 30
|
||||
xcontrol_account_db_max_idle_conns: 10
|
||||
xcontrol_account_session_ttl: 24h
|
||||
xcontrol_account_session_cache: memory
|
||||
xcontrol_account_smtp_host: smtp.example.com
|
||||
xcontrol_account_smtp_port: 587
|
||||
xcontrol_account_smtp_username: apikey
|
||||
xcontrol_account_smtp_password: change-me
|
||||
xcontrol_account_smtp_from: "XControl Account <no-reply@example.com>"
|
||||
xcontrol_account_smtp_timeout: 10s
|
||||
xcontrol_account_smtp_tls_mode: auto
|
||||
xcontrol_account_smtp_tls_insecure_skip_verify: false
|
||||
xcontrol_account_xray_sync_enabled: false
|
||||
xcontrol_account_xray_sync_interval: 5m
|
||||
xcontrol_account_xray_output_path: /usr/local/etc/xray/config.json
|
||||
xcontrol_account_xray_template_path: account/config/xray.config.template.json
|
||||
xcontrol_account_xray_validate_command: []
|
||||
xcontrol_account_xray_restart_command:
|
||||
- systemctl
|
||||
- restart
|
||||
- xray.service
|
||||
xcontrol_account_agent_id: account-primary
|
||||
|
||||
# Image overrides (optional)
|
||||
xcontrol_account_image: ghcr.io/cloud-neutral-toolkit/account:latest
|
||||
xcontrol_rag_image: manbuzhe2009/rag-server:latest
|
||||
xcontrol_dashboard_image: manbuzhe2009/dashboard:latest
|
||||
xcontrol_db_image: manbuzhe2009/postgres-runtime:latest
|
||||
|
||||
# RAG server configuration defaults
|
||||
xcontrol_rag_server_addr: ":8090"
|
||||
xcontrol_rag_read_timeout: 120s
|
||||
xcontrol_rag_write_timeout: 120s
|
||||
xcontrol_rag_public_url: "https://{{ xcontrol_rag_api_domain }}"
|
||||
xcontrol_rag_allowed_origins:
|
||||
- "https://{{ xcontrol_rag_api_domain }}"
|
||||
- "https://{{ xcontrol_homepage_alias_domain }}"
|
||||
- "https://{{ xcontrol_homepage_domain }}"
|
||||
- "https://{{ xcontrol_accounts_domain }}"
|
||||
- "http://localhost:3000"
|
||||
- "http://127.0.0.1:3000"
|
||||
xcontrol_rag_auth_enable: false
|
||||
xcontrol_rag_auth_url: "https://{{ xcontrol_accounts_domain }}"
|
||||
xcontrol_rag_api_base_url: "https://{{ xcontrol_rag_api_domain }}"
|
||||
xcontrol_rag_public_token: xcontrol-public-token-2025
|
||||
xcontrol_rag_redis_addr: ""
|
||||
xcontrol_rag_redis_password: ""
|
||||
xcontrol_rag_vectordb_db_name: rag
|
||||
xcontrol_rag_vectordb_sslmode: disable
|
||||
xcontrol_rag_vectordb_pgurl: "postgres://{{ xcontrol_db_user }}:{{ xcontrol_db_password }}@{{ xcontrol_db_host }}:{{ xcontrol_db_port }}/{{ xcontrol_rag_vectordb_db_name }}?sslmode={{ xcontrol_rag_vectordb_sslmode }}"
|
||||
xcontrol_rag_datasources:
|
||||
- name: XControl
|
||||
repo: https://github.com/svc-design/XControl
|
||||
path: docs
|
||||
xcontrol_rag_sync_repo_proxy: ""
|
||||
xcontrol_rag_embedder_provider: chutes
|
||||
xcontrol_rag_embedder_models:
|
||||
- bge-m3
|
||||
xcontrol_rag_embedder_baseurl: http://127.0.0.1:9000
|
||||
xcontrol_rag_embedder_endpoint: http://127.0.0.1:9000/v1/embeddings
|
||||
xcontrol_rag_generator_provider: chutes
|
||||
xcontrol_rag_generator_models:
|
||||
- deepseek-r1:8b
|
||||
xcontrol_rag_generator_baseurl: http://127.0.0.1:11434
|
||||
xcontrol_rag_generator_endpoint: http://127.0.0.1:11434/v1/chat/completions
|
||||
xcontrol_rag_embedding_max_batch: 64
|
||||
xcontrol_rag_embedding_dimension: 1024
|
||||
5
playbooks/roles/docker/XControl/files/nginx/nginx.conf
Normal file
5
playbooks/roles/docker/XControl/files/nginx/nginx.conf
Normal file
@ -0,0 +1,5 @@
|
||||
events {}
|
||||
|
||||
http {
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
6
playbooks/roles/docker/XControl/files/run.sh
Normal file
6
playbooks/roles/docker/XControl/files/run.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Helper script to start the XControl docker compose stack
|
||||
cd "$(dirname "$0")"
|
||||
docker compose -f docker-compose.yaml up -d
|
||||
76
playbooks/roles/docker/XControl/tasks/main.yml
Normal file
76
playbooks/roles/docker/XControl/tasks/main.yml
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
- name: Ensure XControl directories exist
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ xcontrol_workspace }}"
|
||||
- "{{ xcontrol_workspace }}/certbot"
|
||||
- "{{ xcontrol_workspace }}/certbot/conf"
|
||||
- "{{ xcontrol_workspace }}/certbot/www"
|
||||
- "{{ xcontrol_workspace }}/config"
|
||||
- "{{ xcontrol_workspace }}/nginx"
|
||||
- "{{ xcontrol_workspace }}/nginx/conf.d"
|
||||
|
||||
- name: Ensure XControl workspace ownership
|
||||
become: true
|
||||
ansible.builtin.file:
|
||||
path: "{{ xcontrol_workspace }}"
|
||||
state: directory
|
||||
recurse: true
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
mode: "0755"
|
||||
|
||||
- name: Template XControl configuration files
|
||||
become: true
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ xcontrol_workspace }}/{{ item.dest }}"
|
||||
mode: "{{ item.mode | default('0644') }}"
|
||||
loop:
|
||||
- { src: 'docker-compose.yaml', dest: 'docker-compose.yaml' }
|
||||
- { src: 'config/account.yaml', dest: 'config/account.yaml' }
|
||||
- { src: 'config/server.yaml', dest: 'config/server.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' }
|
||||
- { src: 'nginx/conf.d/accounts.conf', dest: 'nginx/conf.d/accounts.conf' }
|
||||
- { src: 'nginx/conf.d/homepage.conf', dest: 'nginx/conf.d/homepage.conf' }
|
||||
- { src: 'nginx/conf.d/rag-server.conf', dest: 'nginx/conf.d/rag-server.conf' }
|
||||
- { src: 'nginx/conf.d/artifact.conf', dest: 'nginx/conf.d/artifact.conf' }
|
||||
|
||||
- name: Copy XControl static files
|
||||
become: true
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ xcontrol_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 {{ xcontrol_workspace }}/docker-compose.yaml up -d bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ xcontrol_workspace }}"
|
||||
|
||||
- name: Run certbot initial ACME challenge
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ xcontrol_workspace }}/docker-compose.yaml run --rm certbot
|
||||
args:
|
||||
chdir: "{{ xcontrol_workspace }}"
|
||||
|
||||
- name: Destroy Bootstrap NGINX (80-only for ACME)
|
||||
become: true
|
||||
command: docker compose --profile bootstrap -f {{ xcontrol_workspace }}/docker-compose.yaml down bootstrap-nginx
|
||||
args:
|
||||
chdir: "{{ xcontrol_workspace }}"
|
||||
|
||||
- name: Bring up XControl stack
|
||||
become: true
|
||||
command: docker compose -f {{ xcontrol_workspace }}/docker-compose.yaml up -d
|
||||
args:
|
||||
chdir: "{{ xcontrol_workspace }}"
|
||||
@ -0,0 +1,63 @@
|
||||
mode: "{{ xcontrol_account_mode }}"
|
||||
|
||||
log:
|
||||
level: {{ xcontrol_account_log_level | to_nice_yaml(indent=0) | trim }}
|
||||
|
||||
auth:
|
||||
enable: {{ xcontrol_account_auth_enable | bool | lower }}
|
||||
token:
|
||||
publicToken: "{{ xcontrol_account_public_token }}"
|
||||
refreshSecret: "{{ xcontrol_account_refresh_secret }}"
|
||||
accessSecret: "{{ xcontrol_account_access_secret }}"
|
||||
accessExpiry: "{{ xcontrol_account_access_expiry }}"
|
||||
refreshExpiry: "{{ xcontrol_account_refresh_expiry }}"
|
||||
|
||||
server:
|
||||
addr: "{{ xcontrol_account_server_addr }}"
|
||||
readTimeout: {{ xcontrol_account_read_timeout | to_nice_yaml(indent=0) | trim }}
|
||||
writeTimeout: {{ xcontrol_account_write_timeout | to_nice_yaml(indent=0) | trim }}
|
||||
publicUrl: "{{ xcontrol_account_public_url }}"
|
||||
allowedOrigins:
|
||||
- "https://{{ xcontrol_accounts_domain }}"
|
||||
- "https://{{ xcontrol_rag_api_domain }}"
|
||||
- "https://{{ xcontrol_homepage_alias_domain }}"
|
||||
- "http://localhost:3000"
|
||||
- "http://127.0.0.1:3000"
|
||||
- "http://localhost:8080"
|
||||
- "http://127.0.0.1:8080"
|
||||
tls:
|
||||
enabled: {{ xcontrol_account_tls_enabled | bool | lower }}
|
||||
redirectHttp: {{ xcontrol_account_tls_redirect_http | bool | lower }}
|
||||
|
||||
store:
|
||||
driver: "{{ xcontrol_account_store_driver }}"
|
||||
dsn: "postgres://{{ xcontrol_db_user }}:{{ xcontrol_db_password }}@{{ xcontrol_db_host }}:{{ xcontrol_db_port }}/{{ xcontrol_account_db_name }}?sslmode={{ xcontrol_account_db_sslmode }}"
|
||||
maxOpenConns: {{ xcontrol_account_db_max_open_conns }}
|
||||
maxIdleConns: {{ xcontrol_account_db_max_idle_conns }}
|
||||
|
||||
session:
|
||||
ttl: {{ xcontrol_account_session_ttl | to_nice_yaml(indent=0) | trim }}
|
||||
cache: "{{ xcontrol_account_session_cache }}"
|
||||
|
||||
smtp:
|
||||
host: "{{ xcontrol_account_smtp_host }}"
|
||||
port: {{ xcontrol_account_smtp_port }}
|
||||
username: "{{ xcontrol_account_smtp_username }}"
|
||||
password: "{{ xcontrol_account_smtp_password }}"
|
||||
from: "{{ xcontrol_account_smtp_from }}"
|
||||
timeout: {{ xcontrol_account_smtp_timeout | to_nice_yaml(indent=0) | trim }}
|
||||
tls:
|
||||
mode: "{{ xcontrol_account_smtp_tls_mode }}"
|
||||
insecureSkipVerify: {{ xcontrol_account_smtp_tls_insecure_skip_verify | bool | lower }}
|
||||
|
||||
xray:
|
||||
sync:
|
||||
enabled: {{ xcontrol_account_xray_sync_enabled | bool | lower }}
|
||||
interval: {{ xcontrol_account_xray_sync_interval | to_nice_yaml(indent=0) | trim }}
|
||||
outputPath: "{{ xcontrol_account_xray_output_path }}"
|
||||
templatePath: "{{ xcontrol_account_xray_template_path }}"
|
||||
validateCommand: {{ xcontrol_account_xray_validate_command | to_nice_yaml(indent=2) | trim }}
|
||||
restartCommand: {{ xcontrol_account_xray_restart_command | to_nice_yaml(indent=2) | trim }}
|
||||
|
||||
agent:
|
||||
id: "{{ xcontrol_account_agent_id }}"
|
||||
54
playbooks/roles/docker/XControl/templates/config/server.yaml
Normal file
54
playbooks/roles/docker/XControl/templates/config/server.yaml
Normal file
@ -0,0 +1,54 @@
|
||||
server:
|
||||
addr: "{{ xcontrol_rag_server_addr }}"
|
||||
readTimeout: "{{ xcontrol_rag_read_timeout }}"
|
||||
writeTimeout: "{{ xcontrol_rag_write_timeout }}"
|
||||
publicUrl: "{{ xcontrol_rag_public_url }}"
|
||||
allowedOrigins:
|
||||
{% for origin in xcontrol_rag_allowed_origins %}
|
||||
- "{{ origin }}"
|
||||
{% endfor %}
|
||||
|
||||
auth:
|
||||
enable: {{ xcontrol_rag_auth_enable | bool }}
|
||||
authUrl: "{{ xcontrol_rag_auth_url }}"
|
||||
apiBaseUrl: "{{ xcontrol_rag_api_base_url }}"
|
||||
publicToken: "{{ xcontrol_rag_public_token }}"
|
||||
|
||||
global:
|
||||
redis:
|
||||
addr: "{{ xcontrol_rag_redis_addr }}"
|
||||
password: "{{ xcontrol_rag_redis_password }}"
|
||||
vectordb:
|
||||
pgurl: "{{ xcontrol_rag_vectordb_pgurl }}"
|
||||
datasources:
|
||||
{% for datasource in xcontrol_rag_datasources %}
|
||||
- name: "{{ datasource.name }}"
|
||||
repo: "{{ datasource.repo }}"
|
||||
path: "{{ datasource.path }}"
|
||||
{% endfor %}
|
||||
|
||||
sync:
|
||||
repo:
|
||||
proxy: "{{ xcontrol_rag_sync_repo_proxy }}"
|
||||
|
||||
models:
|
||||
embedder:
|
||||
provider: "{{ xcontrol_rag_embedder_provider }}"
|
||||
models:
|
||||
{% for model in xcontrol_rag_embedder_models %}
|
||||
- "{{ model }}"
|
||||
{% endfor %}
|
||||
baseurl: "{{ xcontrol_rag_embedder_baseurl }}"
|
||||
endpoint: "{{ xcontrol_rag_embedder_endpoint }}"
|
||||
generator:
|
||||
provider: "{{ xcontrol_rag_generator_provider }}"
|
||||
models:
|
||||
{% for model in xcontrol_rag_generator_models %}
|
||||
- "{{ model }}"
|
||||
{% endfor %}
|
||||
baseurl: "{{ xcontrol_rag_generator_baseurl }}"
|
||||
endpoint: "{{ xcontrol_rag_generator_endpoint }}"
|
||||
|
||||
embedding:
|
||||
max_batch: {{ xcontrol_rag_embedding_max_batch }}
|
||||
dimension: {{ xcontrol_rag_embedding_dimension }}
|
||||
147
playbooks/roles/docker/XControl/templates/docker-compose.yaml
Normal file
147
playbooks/roles/docker/XControl/templates/docker-compose.yaml
Normal file
@ -0,0 +1,147 @@
|
||||
services:
|
||||
db:
|
||||
image: "{{ xcontrol_db_image }}"
|
||||
container_name: xcontrol-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: "{{ xcontrol_db_name }}"
|
||||
POSTGRES_USER: "{{ xcontrol_db_user }}"
|
||||
POSTGRES_PASSWORD: "{{ xcontrol_db_password }}"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U {{ xcontrol_db_user }}"]
|
||||
interval: 5s
|
||||
timeout: 60s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
volumes:
|
||||
- "data:/var/lib/postgresql/data:rw"
|
||||
networks:
|
||||
- db
|
||||
|
||||
account:
|
||||
image: "{{ xcontrol_account_image }}"
|
||||
container_name: account
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 8080
|
||||
CONFIG_PATH: /etc/xcontrol/account-compose.yaml
|
||||
volumes:
|
||||
- "{{ xcontrol_workspace }}/config/account.yaml:/etc/xcontrol/account-compose.yaml:ro"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8080:8080"
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
|
||||
rag-server:
|
||||
image: "{{ xcontrol_rag_image }}"
|
||||
container_name: rag-server
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 8090
|
||||
CONFIG_PATH: /etc/rag-server/server-compose.yaml
|
||||
volumes:
|
||||
- "{{ xcontrol_workspace }}/config/server.yaml:/etc/rag-server/server-compose.yaml:ro"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8090:8090"
|
||||
networks:
|
||||
- app
|
||||
- db
|
||||
|
||||
dashboard:
|
||||
image: "{{ xcontrol_dashboard_image }}"
|
||||
container_name: dashboard
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 3000
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
account:
|
||||
condition: service_started
|
||||
rag-server:
|
||||
condition: service_started
|
||||
networks:
|
||||
- app
|
||||
|
||||
proxy-external-tls:
|
||||
image: nginx:mainline-alpine
|
||||
container_name: proxy-external-tls
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- "{{ xcontrol_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ xcontrol_workspace }}/nginx/conf.d:/etc/nginx/conf.d:ro"
|
||||
- "{{ xcontrol_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ xcontrol_workspace }}/certbot/www:/var/www/certbot"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
networks:
|
||||
- app
|
||||
depends_on:
|
||||
account:
|
||||
condition: service_started
|
||||
rag-server:
|
||||
condition: service_started
|
||||
dashboard:
|
||||
condition: service_started
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
networks:
|
||||
- app
|
||||
|
||||
bootstrap-nginx:
|
||||
profiles: ["bootstrap"]
|
||||
image: nginx:mainline-alpine
|
||||
container_name: bootstrap-nginx
|
||||
volumes:
|
||||
- "{{ xcontrol_workspace }}/certbot/www:/var/www/certbot"
|
||||
- "{{ xcontrol_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ xcontrol_workspace }}/nginx/nginx.conf:/etc/nginx/nginx.conf"
|
||||
- "{{ xcontrol_workspace }}/nginx/conf.d/bootstrap-nginx.conf:/etc/nginx/conf.d/default.conf"
|
||||
ports:
|
||||
- "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 {{ xcontrol_certbot_email }}
|
||||
--agree-tos
|
||||
--no-eff-email
|
||||
--keep-until-expiring
|
||||
--non-interactive
|
||||
-d {{ xcontrol_certbot_domains }}
|
||||
volumes:
|
||||
- "{{ xcontrol_workspace }}/certbot/conf:/etc/letsencrypt"
|
||||
- "{{ xcontrol_workspace }}/certbot/www:/var/www/certbot"
|
||||
networks:
|
||||
- app
|
||||
|
||||
networks:
|
||||
app:
|
||||
db:
|
||||
|
||||
volumes:
|
||||
data:
|
||||
@ -0,0 +1,40 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ xcontrol_accounts_domain }};
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ xcontrol_accounts_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location ^~ /api/auth/ {
|
||||
proxy_pass http://account:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Cookie" always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires "0";
|
||||
|
||||
proxy_cookie_path / "/; Secure; HttpOnly; SameSite=None";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ xcontrol_artifact_domain }} {{ xcontrol_artifact_cn_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
root /data/update-server;
|
||||
index index.html;
|
||||
|
||||
location ^~ /.well-known/ { allow all; }
|
||||
|
||||
# ✅ JSON 专用——放在 / 之前
|
||||
location ~* \.json$ {
|
||||
try_files $uri =404;
|
||||
add_header Cache-Control "public, max-age=60, s-maxage=60, stale-while-revalidate=300";
|
||||
default_type application/json;
|
||||
}
|
||||
|
||||
# 目录浏览
|
||||
location / {
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
add_header Accept-Ranges bytes;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# 大包直出
|
||||
location ~* \.(?:dmg|zip|tar\.gz|deb|rpm|exe|pkg|appimage|apk|ipa)$ {
|
||||
expires 7d;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
add_header Accept-Ranges bytes;
|
||||
}
|
||||
|
||||
# 隐藏 dotfiles(不拦 /.well-known/)
|
||||
location ~ /\.(?!well-known/)[^/]+ { deny all; }
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ xcontrol_artifact_domain }} {{ xcontrol_artifact_cn_domain }};
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 200 "bootstrap";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
# ----------------------------------------------------
|
||||
# 80 - ACME Challenge + Redirect to HTTPS for homepage
|
||||
# ----------------------------------------------------
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ xcontrol_homepage_domain }};
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 443 - TLS Termination for homepage
|
||||
# ----------------------------------------------------
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name {{ xcontrol_homepage_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
location / {
|
||||
proxy_pass http://dashboard:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ xcontrol_homepage_alias_domain }} {{ xcontrol_homepage_cn_domain }};
|
||||
|
||||
# Certbot HTTP-01 challenge
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# All HTTP → HTTPS
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ xcontrol_homepage_alias_domain }} {{ xcontrol_homepage_cn_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
# ====== 静态根目录(Next.js export 产物)======
|
||||
root /dashboard/;
|
||||
index index.html;
|
||||
|
||||
# (可选)放行 ACME/健康检查等
|
||||
location ^~ /.well-known/ { allow all; }
|
||||
|
||||
# =======================
|
||||
# API 反向代理(保持原样)
|
||||
# =======================
|
||||
location /api/ {
|
||||
proxy_pass http://account:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# /api/askai 接口限流(保持原样)
|
||||
location = /api/askai {
|
||||
access_by_lua_block {
|
||||
local redis = require "resty.redis"
|
||||
local r = redis:new()
|
||||
r:set_timeout(200)
|
||||
local ok, err = r:connect("redis", 6379)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Redis connect error: ", err)
|
||||
return ngx.exit(500)
|
||||
end
|
||||
|
||||
local user = ngx.var.arg_user or ngx.var.remote_addr
|
||||
local today = os.date("%Y%m%d")
|
||||
local key = "limit:user:" .. user .. ":" .. today
|
||||
|
||||
local count, err = r:incr(key)
|
||||
if count == 1 then r:expire(key, 86400) end
|
||||
if count > 200 then
|
||||
ngx.status = 429
|
||||
ngx.header["Content-Type"] = "text/plain; charset=utf-8"
|
||||
ngx.say("Too Many Requests: daily limit reached")
|
||||
return ngx.exit(429)
|
||||
end
|
||||
}
|
||||
|
||||
proxy_pass http://account:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# =======================
|
||||
# 静态文件直出(替换原先的 Next.js 动态代理)
|
||||
# =======================
|
||||
|
||||
# Next 导出的静态资源(hash 不变 -> 长缓存)
|
||||
location ^~ /_next/static/ {
|
||||
try_files $uri =404;
|
||||
access_log off;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable, max-age=31536000";
|
||||
}
|
||||
|
||||
# 其他常见静态资源:中等缓存
|
||||
location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|webp|ico|woff2?|ttf)$ {
|
||||
try_files $uri =404;
|
||||
access_log off;
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
}
|
||||
|
||||
# 主页与已导出的所有路由:按文件/目录匹配
|
||||
# 未命中的交给 404.html(保持静态站语义)
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
# 显式处理 404/500 路由目录(Next export 会生成 404/、500/ 与同名 .html)
|
||||
location = /404.html { internal; }
|
||||
error_page 404 /404.html;
|
||||
|
||||
# 如果有 /favicon.ico,则直接给文件
|
||||
location = /favicon.ico {
|
||||
try_files /favicon.ico =204;
|
||||
access_log off;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, max-age=2592000";
|
||||
}
|
||||
|
||||
# (可选)为某些目录开启目录索引(你有 dl-index、docs、download)
|
||||
# 若需要列表页可以这样做;不需要则删除本段
|
||||
location ^~ /dl-index/ {
|
||||
autoindex on;
|
||||
autoindex_exact_size off;
|
||||
autoindex_localtime on;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# 拒绝访问隐藏文件(如 .env)
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# (可选)开启 gzip(如启用 ngx_brotli,也可再加 br)
|
||||
gzip on;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 1k;
|
||||
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
|
||||
gzip_vary on;
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ xcontrol_rag_domain }} {{ xcontrol_rag_api_domain }};
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name {{ xcontrol_rag_domain }} {{ xcontrol_rag_api_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ xcontrol_primary_domain }}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
location ^~ /api/ {
|
||||
proxy_pass http://rag-server:8090;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
add_header Access-Control-Allow-Origin $cors_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Cookie" always;
|
||||
add_header Access-Control-Allow-Credentials "true" always;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
|
||||
location = /api/askai {
|
||||
access_by_lua_block {
|
||||
local redis = require "resty.redis"
|
||||
local r = redis:new()
|
||||
r:set_timeout(200)
|
||||
local ok, err = r:connect("redis", 6379)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "Redis connect error: ", err)
|
||||
return ngx.exit(500)
|
||||
end
|
||||
|
||||
local user = ngx.var.arg_user or ngx.var.remote_addr
|
||||
local today = os.date("%Y%m%d")
|
||||
local key = "limit:user:" .. user .. ":" .. today
|
||||
|
||||
local count, err = r:incr(key)
|
||||
if count == 1 then r:expire(key, 86400) end
|
||||
if count > 200 then
|
||||
ngx.status = 429
|
||||
ngx.header["Content-Type"] = "text/plain; charset=utf-8"
|
||||
ngx.say("Too Many Requests: daily limit reached")
|
||||
return ngx.exit(429)
|
||||
end
|
||||
}
|
||||
|
||||
proxy_pass http://rag-server:8090;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user