From 90b819e5ad8d707782041a66268e954a95d26e02 Mon Sep 17 00:00:00 2001 From: cloudneutral Date: Wed, 10 Dec 2025 15:21:13 +0800 Subject: [PATCH] Template rag server configuration --- playbooks/roles/docker/XControl/README.md | 113 ++++++++++++++ .../roles/docker/XControl/defaults/main.yml | 112 +++++++++++++ .../docker/XControl/files/nginx/nginx.conf | 5 + playbooks/roles/docker/XControl/files/run.sh | 6 + .../roles/docker/XControl/tasks/main.yml | 76 +++++++++ .../XControl/templates/config/account.yaml | 63 ++++++++ .../XControl/templates/config/server.yaml | 54 +++++++ .../XControl/templates/docker-compose.yaml | 147 ++++++++++++++++++ .../templates/nginx/conf.d/accounts.conf | 40 +++++ .../templates/nginx/conf.d/artifact.conf | 47 ++++++ .../nginx/conf.d/bootstrap-nginx.conf | 12 ++ .../templates/nginx/conf.d/default.conf | 35 +++++ .../templates/nginx/conf.d/homepage.conf | 136 ++++++++++++++++ .../templates/nginx/conf.d/rag-server.conf | 69 ++++++++ 14 files changed, 915 insertions(+) create mode 100644 playbooks/roles/docker/XControl/README.md create mode 100644 playbooks/roles/docker/XControl/defaults/main.yml create mode 100644 playbooks/roles/docker/XControl/files/nginx/nginx.conf create mode 100644 playbooks/roles/docker/XControl/files/run.sh create mode 100644 playbooks/roles/docker/XControl/tasks/main.yml create mode 100644 playbooks/roles/docker/XControl/templates/config/account.yaml create mode 100644 playbooks/roles/docker/XControl/templates/config/server.yaml create mode 100644 playbooks/roles/docker/XControl/templates/docker-compose.yaml create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/accounts.conf create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/artifact.conf create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/bootstrap-nginx.conf create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/default.conf create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/homepage.conf create mode 100644 playbooks/roles/docker/XControl/templates/nginx/conf.d/rag-server.conf diff --git a/playbooks/roles/docker/XControl/README.md b/playbooks/roles/docker/XControl/README.md new file mode 100644 index 0000000..e930488 --- /dev/null +++ b/playbooks/roles/docker/XControl/README.md @@ -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 ` +- `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 +``` diff --git a/playbooks/roles/docker/XControl/defaults/main.yml b/playbooks/roles/docker/XControl/defaults/main.yml new file mode 100644 index 0000000..719ff61 --- /dev/null +++ b/playbooks/roles/docker/XControl/defaults/main.yml @@ -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 " +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 diff --git a/playbooks/roles/docker/XControl/files/nginx/nginx.conf b/playbooks/roles/docker/XControl/files/nginx/nginx.conf new file mode 100644 index 0000000..9fe4ac3 --- /dev/null +++ b/playbooks/roles/docker/XControl/files/nginx/nginx.conf @@ -0,0 +1,5 @@ +events {} + +http { + include /etc/nginx/conf.d/*.conf; +} diff --git a/playbooks/roles/docker/XControl/files/run.sh b/playbooks/roles/docker/XControl/files/run.sh new file mode 100644 index 0000000..369e383 --- /dev/null +++ b/playbooks/roles/docker/XControl/files/run.sh @@ -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 diff --git a/playbooks/roles/docker/XControl/tasks/main.yml b/playbooks/roles/docker/XControl/tasks/main.yml new file mode 100644 index 0000000..8c10436 --- /dev/null +++ b/playbooks/roles/docker/XControl/tasks/main.yml @@ -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 }}" diff --git a/playbooks/roles/docker/XControl/templates/config/account.yaml b/playbooks/roles/docker/XControl/templates/config/account.yaml new file mode 100644 index 0000000..b028a0a --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/config/account.yaml @@ -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 }}" diff --git a/playbooks/roles/docker/XControl/templates/config/server.yaml b/playbooks/roles/docker/XControl/templates/config/server.yaml new file mode 100644 index 0000000..68a7325 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/config/server.yaml @@ -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 }} diff --git a/playbooks/roles/docker/XControl/templates/docker-compose.yaml b/playbooks/roles/docker/XControl/templates/docker-compose.yaml new file mode 100644 index 0000000..35509d7 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/docker-compose.yaml @@ -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: diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/accounts.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/accounts.conf new file mode 100644 index 0000000..0d8edbe --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/accounts.conf @@ -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"; + } +} diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/artifact.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/artifact.conf new file mode 100644 index 0000000..547c4b2 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/artifact.conf @@ -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; +} diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/bootstrap-nginx.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/bootstrap-nginx.conf new file mode 100644 index 0000000..e802d50 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/bootstrap-nginx.conf @@ -0,0 +1,12 @@ +server { + listen 80; + server_name _; + + location ^~ /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 200 "bootstrap"; + } +} diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/default.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/default.conf new file mode 100644 index 0000000..fb6a9e2 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/default.conf @@ -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; + } +} diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/homepage.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/homepage.conf new file mode 100644 index 0000000..9c1474e --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/homepage.conf @@ -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; +} diff --git a/playbooks/roles/docker/XControl/templates/nginx/conf.d/rag-server.conf b/playbooks/roles/docker/XControl/templates/nginx/conf.d/rag-server.conf new file mode 100644 index 0000000..0fee182 --- /dev/null +++ b/playbooks/roles/docker/XControl/templates/nginx/conf.d/rag-server.conf @@ -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; + } +}