Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions autobot-slm-backend/ansible/playbooks/update-all-nodes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,22 @@
changed_when: false
tags: [backend, env]

# #10400: align AUTOBOT_JWT_SECRET with SLM_SECRET_KEY so the backend→SLM
# WebSocket service-JWT verifies (SLM verifies with SLM_SECRET_KEY). The key
# is written by the slm_manager role into /etc/autobot/slm-secrets.env.
- name: "[PLAY 2] Backend | Read SLM_SECRET_KEY for backend_jwt_secret (#10400)"
ansible.builtin.shell:
cmd: >-
grep -oP '^SLM_SECRET_KEY=\K.*'
/etc/autobot/slm-secrets.env 2>/dev/null || true
register: existing_slm_secret_key
become: true
when: "'backend' in group_names"
failed_when: false
changed_when: false
no_log: true
tags: [backend, env]

- name: "[PLAY 2] Backend | Load shared backend env vars (#2727)"
ansible.builtin.include_vars:
file: vars/backend-env.yml
Expand All @@ -576,6 +592,8 @@
vars:
# backend_secret_key uses a register var specific to this playbook
backend_secret_key: "{{ existing_secret_key.stdout | default(lookup('env', 'AUTOBOT_SECRET_KEY') | default('autobot-dev-secret-' + (9999999999 | random | string), true), true) }}"
# #10400: prefer SLM_SECRET_KEY when present, else keep the role default.
backend_jwt_secret: "{{ (existing_slm_secret_key.stdout | default('') | trim) if (existing_slm_secret_key.stdout | default('') | trim) | length > 0 else (existing_secret_key.stdout | default('') | trim) }}"
tags: [backend, env]

- name: "[PLAY 2] Backend | Read backend user mode (#10001)"
Expand Down
8 changes: 6 additions & 2 deletions autobot-slm-backend/ansible/playbooks/update-node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,14 @@
and (cache_stat.results | selectattr('item', 'equalto', 'autobot-frontend') | map(attribute='stat.exists') | first | default(false))
changed_when: frontend_build.rc == 0

- name: "Rebuild autobot-slm-frontend production dist"
# #10435: SLM UI is served under /slm; build:slm exports VITE_API_URL=/slm so
# the API base is baked correctly (plain build leaves it unset → guard fails).
- name: "Rebuild autobot-slm-frontend production dist (#10435)"
ansible.builtin.command:
cmd: npm run build
cmd: npm run build:slm
chdir: "{{ install_base }}/autobot-slm-frontend"
environment:
VITE_API_URL: "/slm"
register: slm_frontend_build
when: >
'autobot-slm-frontend' in deploy_roles
Expand Down
23 changes: 23 additions & 0 deletions autobot-slm-backend/ansible/roles/backend/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,29 @@
backend_slm_auth_tok: "{{ _backend_slm_auth_tok.stdout | default('') | trim }}"
tags: ['backend', 'config']

# #10400: Backend signs the service JWT with AUTOBOT_JWT_SECRET, but SLM verifies
# it with SLM_SECRET_KEY. Provisioning never distributed the SLM key to the backend,
# so the backend→SLM WebSocket handshake was rejected with 4001. Read SLM_SECRET_KEY
# from /etc/autobot/slm-secrets.env (written by the slm_manager role) and surface it
# as backend_jwt_secret so backend.env.j2 renders AUTOBOT_JWT_SECRET to the same value.
- name: "Backend | Read SLM_SECRET_KEY from slm-secrets.env (#10400)"
ansible.builtin.shell:
cmd: >-
grep -oP '^SLM_SECRET_KEY=\K.*'
/etc/autobot/slm-secrets.env 2>/dev/null || true
register: _backend_slm_secret_key
changed_when: false
failed_when: false
no_log: true
tags: ['backend', 'config']

- name: "Backend | Align backend_jwt_secret with SLM_SECRET_KEY when present (#10400)"
ansible.builtin.set_fact:
backend_jwt_secret: "{{ _backend_slm_secret_key.stdout | trim }}"
when: (_backend_slm_secret_key.stdout | default('') | trim) | length > 0
no_log: true
tags: ['backend', 'config']

# #9914: Deploy the systemd unit BEFORE any stop/start so a fresh VM provisions
# out-of-box. Previously the unit was deployed after the MVA-1633 start task, so
# first-run on a clean node failed: "Could not find the requested service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ AUTOBOT_REDIS_URL=redis://{{ backend_redis_host }}:{{ backend_redis_port }}

# Security
SECRET_KEY={{ backend_secret_key }}
AUTOBOT_JWT_SECRET={{ backend_jwt_secret | default(backend_secret_key) }}
# #10400: set from SLM_SECRET_KEY at deploy time so the backend->SLM service JWT verifies.
AUTOBOT_JWT_SECRET={{ backend_jwt_secret | default(backend_secret_key, true) }}
CORS_ORIGINS={{ backend_cors_origins }}
# Internal API key for SLM→backend proxy authentication (#1779)
AUTOBOT_INTERNAL_API_KEY={{ autobot_internal_api_key }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ slm_auth_token: ""
# Injected into nginx proxy headers AND into backend/SLM process environments.
autobot_internal_api_key: ""

# Host and port for the user-facing AutoBot backend (HTTPS).
# Host, port and protocol for the user-facing AutoBot backend.
# Defaults to the inventory-derived backend host so no IP is hardcoded here.
# #10378: the co-located backend is plain HTTP on uvicorn :8001 (nginx holds
# 443); nothing listens on https :8443. The /autobot-api/ proxy uses the
# autobot_backend upstream in co-located mode and these vars in standalone mode.
autobot_backend_host: "{{ infrastructure.hosts.backend if infrastructure is defined else '127.0.0.1' }}"
autobot_backend_port: 8443
autobot_backend_port: 8001
autobot_backend_protocol: http

# noVNC proxy port (used by /tools/novnc/ location)
novnc_port: 6080
Expand Down
10 changes: 7 additions & 3 deletions autobot-slm-backend/ansible/roles/slm_manager/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -456,16 +456,20 @@
when: slm_frontend_build and slm_package_json.stat.exists
tags: ['slm', 'frontend']

- name: "SLM | Build frontend for production"
# #10435: SLM UI is always served under /slm (nginx `location /slm/`) and
# vite.config sets base /slm/. The API base must always be /slm; an unset/empty
# VITE_API_URL trips the co-located build guard and bakes the wrong base. Use
# build:slm which exports VITE_API_URL=/slm for every deploy (co-located + standalone).
- name: "SLM | Build frontend for production (#10435)"
ansible.builtin.command:
cmd: npm run build
cmd: npm run build:slm
chdir: "{{ slm_frontend_dir }}"
become_user: "{{ slm_user }}"
when: slm_frontend_build and slm_package_json.stat.exists
register: slm_frontend_build_result
changed_when: slm_frontend_build_result.rc == 0
environment:
VITE_API_URL: "{{ '/slm' if (slm_colocated_frontend | default(false) | bool) else '' }}"
VITE_API_URL: "/slm"
tags: ['slm', 'frontend']

# ==========================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# AutoBot SLM Nginx Configuration
# Managed by Ansible - slm_manager role

# WebSocket Upgrade/Connection map (http context).
# Sends "Connection: upgrade" only for real WS requests and "Connection: close"
# for plain REST GETs, so proxied HTTP/1.1 keepalive REST calls are not broken
# by a hardcoded upgrade header (#10378).
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

{% if slm_colocated_frontend | default(false) | bool %}
# Co-located user backend upstream (#2829)
# Proxies directly to uvicorn (HTTP) — no intermediate backend nginx.
Expand Down Expand Up @@ -348,22 +357,32 @@ server {
}

# Main AutoBot backend proxy (user backend skills, cache, etc.)
# /autobot-api/* → https://main_backend/api/* (strips /autobot-api prefix)
# /autobot-api/* → http://main_backend/api/* (strips /autobot-api prefix) (#10378)
# Issue #1791: inject X-Internal-API-Key for service auth + WebSocket support
location /autobot-api/ {
proxy_pass https://{{ autobot_backend_host }}:{{ autobot_backend_port }}/api/;
proxy_http_version 1.1;
# #10378: main backend is plain HTTP on its real port. Co-located deploys
# use the autobot_backend upstream (uvicorn :8001); standalone proxies to
# the inventory backend host/port over its configured protocol.
{% if slm_colocated_frontend | default(false) | bool %}
proxy_pass http://autobot_backend/api/;
{% else %}
proxy_pass {{ autobot_backend_protocol | default('http') }}://{{ autobot_backend_host }}:{{ autobot_backend_port }}/api/;
{% if (autobot_backend_protocol | default('http')) == 'https' %}
proxy_ssl_verify off;
{% endif %}
{% endif %}
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;
{% if autobot_internal_api_key %}
proxy_set_header X-Internal-API-Key "{{ autobot_internal_api_key }}";
{% endif %}
# WebSocket support for /processes/{id}/stream (#1777)
# WebSocket support for /processes/{id}/stream (#1777). Use the
# $connection_upgrade map so plain REST GETs are not forced to upgrade.
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Connection $connection_upgrade;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"ansible_playbook": "deploy-slm-manager.yml",
"build_steps": [
"pip install -r requirements.txt",
"cd {{ slm_base_dir }}/autobot-slm-frontend && npm install && npm run build"
"cd {{ slm_base_dir }}/autobot-slm-frontend && npm install && npm run build:slm"
],
"depends_on": [],
"required": true,
Expand Down
7 changes: 7 additions & 0 deletions autobot-slm-backend/services/ansible_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
# Internal API key shared between SLM backend and main backend (#1779,
# #3512). Stored via the SLM secrets UI; injected as an Ansible extra_var
# and rendered into slm-secrets.env and backend.env.
#
# NOTE (#10400): SLM_SECRET_KEY is NOT distributed here. It is not a SystemSecret
# DB row — it is generated by the slm_manager role into /etc/autobot/slm-secrets.env
# (and read by SLM config.py from env / the persisted keys file). The backend role
# reads SLM_SECRET_KEY from that file at deploy time and surfaces it as
# backend_jwt_secret so AUTOBOT_JWT_SECRET == SLM_SECRET_KEY (same #7689 pattern as
# SLM_AUTH_TOKEN). Adding it to this DB-keyed map would never resolve.
_SECRET_TO_ANSIBLE_VAR: dict[str, str] = {
"hf_token": "tts_hf_token", # nosec B105 - dict maps secret DB key names to Ansible variable names, not credential
"autobot_internal_api_key": "autobot_internal_api_key",
Expand Down
4 changes: 3 additions & 1 deletion autobot-slm-backend/services/role_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@
"systemd_service": "nginx",
"auto_restart": False,
"health_check_port": 443,
"post_sync_cmd": (f"cd {_BASE_DIR}/autobot-slm-frontend && npm install && npm run build"),
"post_sync_cmd": (
f"cd {_BASE_DIR}/autobot-slm-frontend && npm install && npm run build:slm"
), # #10435: VITE_API_URL=/slm
"required": True,
"degraded_without": [],
"ansible_playbook": "deploy-slm-manager.yml",
Expand Down
Loading