Compare commits
29 Commits
d996d179eb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ffcf00570 | |||
| cac18dc61f | |||
| 58b7f4139f | |||
| eea1359414 | |||
| 56d7fdb9cf | |||
| 45db26040e | |||
| 6f5f610297 | |||
| bb0b12ad0f | |||
| 7dab63b83c | |||
| 2d8ae617c6 | |||
| bc1cf0e9dc | |||
| f6aae9a6ea | |||
| 6f48b38868 | |||
| e21c91e73e | |||
| b17cdada7c | |||
| 0a7d528844 | |||
| 83170bf6ce | |||
| 8fddef6050 | |||
| c32c3471e0 | |||
| c1391e3dbc | |||
| d768edea99 | |||
| e472d83372 | |||
| 0a053c1cd6 | |||
| 856d7e2ef2 | |||
| a068483330 | |||
| cdd61bd916 | |||
| 27fab11f78 | |||
| 808a775ebe | |||
| 06118fbd40 |
@@ -13,7 +13,7 @@ Containers are named after moons of Uranus and resolved via the `.incus` DNS dom
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
ℹ️ The Ansible virtual environment is expected at `~/env/agathos/bin/activate`.
|
ℹ️ The Ansible virtual environment is expected at `~/env/ouranos/bin/activate`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Provision containers
|
# Provision containers
|
||||||
@@ -22,7 +22,7 @@ terraform init && terraform apply
|
|||||||
|
|
||||||
# Configure services
|
# Configure services
|
||||||
cd ../ansible
|
cd ../ansible
|
||||||
source ~/env/agathos/bin/activate
|
source ~/env/ouranos/bin/activate
|
||||||
ansible-playbook site.yml
|
ansible-playbook site.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars:
|
vars:
|
||||||
agent_s_archive: "{{rel_dir}}/agent_s_{{agent_s_rel}}.tar"
|
agent_s_archive: "{{rel_dir}}/agent_s_{{agent_s_rel}}.tar"
|
||||||
agent_s_repo_dir: "{{github_repo_dir}}/Agent-S"
|
agent_s_repo_dir: "{{github_dir}}/Agent-S"
|
||||||
pulse_xrdp_archive: "{{rel_dir}}/pulseaudio_module_xrdp_{{pulseaudio_module_xrdp_rel}}.tar"
|
pulse_xrdp_archive: "{{rel_dir}}/pulseaudio_module_xrdp_{{pulseaudio_module_xrdp_rel}}.tar"
|
||||||
pulse_xrdp_repo_dir: "{{github_repo_dir}}/pulseaudio-module-xrdp"
|
pulse_xrdp_repo_dir: "{{github_dir}}/pulseaudio-module-xrdp"
|
||||||
|
rommie_archive: "{{rel_dir}}/rommie_{{rommie_rel}}.tar"
|
||||||
|
rommie_repo_dir: "{{repo_dir}}/rommie"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Ensure release directory exists
|
- name: Ensure release directory exists
|
||||||
@@ -46,3 +48,19 @@
|
|||||||
ansible.builtin.command: git archive -o "{{pulse_xrdp_archive}}" "{{pulseaudio_module_xrdp_rel}}"
|
ansible.builtin.command: git archive -o "{{pulse_xrdp_archive}}" "{{pulseaudio_module_xrdp_rel}}"
|
||||||
args:
|
args:
|
||||||
chdir: "{{pulse_xrdp_repo_dir}}"
|
chdir: "{{pulse_xrdp_repo_dir}}"
|
||||||
|
|
||||||
|
# Rommie
|
||||||
|
- name: Fetch all remote branches and tags (rommie)
|
||||||
|
ansible.builtin.command: git fetch --all
|
||||||
|
args:
|
||||||
|
chdir: "{{rommie_repo_dir}}"
|
||||||
|
|
||||||
|
- name: Pull latest changes (rommie)
|
||||||
|
ansible.builtin.command: git pull
|
||||||
|
args:
|
||||||
|
chdir: "{{rommie_repo_dir}}"
|
||||||
|
|
||||||
|
- name: Create rommie archive for specified release
|
||||||
|
ansible.builtin.command: git archive -o "{{rommie_archive}}" "{{rommie_rel}}"
|
||||||
|
args:
|
||||||
|
chdir: "{{rommie_repo_dir}}"
|
||||||
|
|||||||
@@ -79,20 +79,6 @@ loki.source.syslog "neo4j_cypher_logs" {
|
|||||||
forward_to = [loki.write.default.receiver]
|
forward_to = [loki.write.default.receiver]
|
||||||
}
|
}
|
||||||
|
|
||||||
loki.source.syslog "neo4j_memory_logs" {
|
|
||||||
listener {
|
|
||||||
address = "127.0.0.1:{{neo4j_memory_syslog_port}}"
|
|
||||||
protocol = "tcp"
|
|
||||||
syslog_format = "{{ syslog_format }}"
|
|
||||||
labels = {
|
|
||||||
job = "neo4j-memory",
|
|
||||||
hostname = "{{inventory_hostname}}",
|
|
||||||
environment = "{{deployment_environment}}",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
forward_to = [loki.write.default.receiver]
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.source.syslog "gitea_mcp_logs" {
|
loki.source.syslog "gitea_mcp_logs" {
|
||||||
listener {
|
listener {
|
||||||
address = "127.0.0.1:{{gitea_mcp_syslog_port}}"
|
address = "127.0.0.1:{{gitea_mcp_syslog_port}}"
|
||||||
|
|||||||
@@ -33,20 +33,6 @@ loki.source.syslog "rabbitmq_logs" {
|
|||||||
forward_to = [loki.write.default.receiver]
|
forward_to = [loki.write.default.receiver]
|
||||||
}
|
}
|
||||||
|
|
||||||
loki.source.syslog "searxng_logs" {
|
|
||||||
listener {
|
|
||||||
address = "127.0.0.1:{{searxng_syslog_port}}"
|
|
||||||
protocol = "tcp"
|
|
||||||
syslog_format = "{{ syslog_format }}"
|
|
||||||
labels = {
|
|
||||||
job = "searxng",
|
|
||||||
hostname = "{{inventory_hostname}}",
|
|
||||||
environment = "{{deployment_environment}}",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
forward_to = [loki.write.default.receiver]
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.source.syslog "smtp4dev_logs" {
|
loki.source.syslog "smtp4dev_logs" {
|
||||||
listener {
|
listener {
|
||||||
address = "127.0.0.1:{{smtp4dev_syslog_port}}"
|
address = "127.0.0.1:{{smtp4dev_syslog_port}}"
|
||||||
@@ -72,19 +58,6 @@ prometheus.scrape "default" {
|
|||||||
job_name = "containers"
|
job_name = "containers"
|
||||||
}
|
}
|
||||||
|
|
||||||
prometheus.scrape "hass" {
|
|
||||||
targets = [{
|
|
||||||
__address__ = "127.0.0.1:{{hass_port}}",
|
|
||||||
job = "hass",
|
|
||||||
hostname = "{{inventory_hostname}}",
|
|
||||||
environment = "{{deployment_environment}}",
|
|
||||||
}]
|
|
||||||
forward_to = [prometheus.remote_write.default.receiver]
|
|
||||||
scrape_interval = "60s"
|
|
||||||
metrics_path = "/api/prometheus"
|
|
||||||
bearer_token = "{{hass_metrics_token}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
prometheus.remote_write "default" {
|
prometheus.remote_write "default" {
|
||||||
endpoint {
|
endpoint {
|
||||||
url = "{{prometheus_remote_write_url}}"
|
url = "{{prometheus_remote_write_url}}"
|
||||||
|
|||||||
@@ -69,13 +69,13 @@ loki.source.syslog "kairos_logs" {
|
|||||||
forward_to = [loki.write.default.receiver]
|
forward_to = [loki.write.default.receiver]
|
||||||
}
|
}
|
||||||
|
|
||||||
loki.source.syslog "sagittarius_logs" {
|
loki.source.syslog "menosyne_logs" {
|
||||||
listener {
|
listener {
|
||||||
address = "127.0.0.1:{{sagittarius_syslog_port}}"
|
address = "127.0.0.1:{{mnemosyne_syslog_port}}"
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
syslog_format = "{{ syslog_format }}"
|
syslog_format = "{{ syslog_format }}"
|
||||||
labels = {
|
labels = {
|
||||||
job = "sagittarius",
|
job = "menosyne",
|
||||||
hostname = "{{inventory_hostname}}",
|
hostname = "{{inventory_hostname}}",
|
||||||
environment = "{{deployment_environment}}",
|
environment = "{{deployment_environment}}",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ loki.source.file "apache_logs" {
|
|||||||
forward_to = [loki.write.default.receiver]
|
forward_to = [loki.write.default.receiver]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prometheus.scrape "hass" {
|
||||||
|
targets = [{
|
||||||
|
__address__ = "127.0.0.1:{{hass_port}}",
|
||||||
|
job = "hass",
|
||||||
|
hostname = "{{inventory_hostname}}",
|
||||||
|
environment = "{{deployment_environment}}",
|
||||||
|
}]
|
||||||
|
forward_to = [prometheus.remote_write.default.receiver]
|
||||||
|
scrape_interval = "60s"
|
||||||
|
metrics_path = "/api/prometheus"
|
||||||
|
bearer_token = "{{hass_metrics_token}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Lobechat Docker syslog
|
// Lobechat Docker syslog
|
||||||
loki.source.syslog "lobechat_logs" {
|
loki.source.syslog "lobechat_logs" {
|
||||||
listener {
|
listener {
|
||||||
@@ -61,6 +75,20 @@ loki.source.syslog "lobechat_logs" {
|
|||||||
forward_to = [loki.write.default.receiver]
|
forward_to = [loki.write.default.receiver]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loki.source.syslog "searxng_logs" {
|
||||||
|
listener {
|
||||||
|
address = "127.0.0.1:{{searxng_syslog_port}}"
|
||||||
|
protocol = "tcp"
|
||||||
|
syslog_format = "{{ syslog_format }}"
|
||||||
|
labels = {
|
||||||
|
job = "searxng",
|
||||||
|
hostname = "{{inventory_hostname}}",
|
||||||
|
environment = "{{deployment_environment}}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forward_to = [loki.write.default.receiver]
|
||||||
|
}
|
||||||
|
|
||||||
// Loki endpoint
|
// Loki endpoint
|
||||||
loki.write "default" {
|
loki.write "default" {
|
||||||
endpoint {
|
endpoint {
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
inventory = inventory
|
inventory = inventory
|
||||||
stdout_callback = ansible.builtin.default
|
stdout_callback = ansible.builtin.default
|
||||||
result_format = yaml
|
result_format = yaml
|
||||||
remote_user = robert
|
remote_user = ponos
|
||||||
vault_password_file = .vault_pass
|
vault_password_file = .vault_pass
|
||||||
@@ -48,15 +48,6 @@
|
|||||||
state: directory
|
state: directory
|
||||||
mode: '750'
|
mode: '750'
|
||||||
|
|
||||||
- name: Transfer and unarchive git archive
|
|
||||||
become: true
|
|
||||||
ansible.builtin.unarchive:
|
|
||||||
src: "~/rel/argos_{{argos_rel}}.tar"
|
|
||||||
dest: "{{argos_directory}}"
|
|
||||||
owner: "{{argos_user}}"
|
|
||||||
group: "{{argos_group}}"
|
|
||||||
mode: '550'
|
|
||||||
|
|
||||||
- name: Template docker-compose.yml
|
- name: Template docker-compose.yml
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
services:
|
services:
|
||||||
argos-searxng:
|
argos-searxng:
|
||||||
build: .
|
image: git.helu.ca/r/argos:{{argos_rel}}
|
||||||
|
pull_policy: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- kvdb
|
- kvdb
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Stage Argos release tarball
|
|
||||||
hosts: localhost
|
|
||||||
gather_facts: false
|
|
||||||
vars:
|
|
||||||
argos_repo_dir: "{{repo_dir}}/argos"
|
|
||||||
archive_path: "{{rel_dir}}/argos_{{argos_rel}}.tar"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Ensure release directory exists
|
|
||||||
file:
|
|
||||||
path: "{{rel_dir}}"
|
|
||||||
state: directory
|
|
||||||
mode: '755'
|
|
||||||
|
|
||||||
- name: Fetch all remote branches and tags
|
|
||||||
ansible.builtin.command: git fetch --all
|
|
||||||
args:
|
|
||||||
chdir: "{{argos_repo_dir}}"
|
|
||||||
|
|
||||||
- name: Git pull
|
|
||||||
ansible.builtin.command: git pull
|
|
||||||
args:
|
|
||||||
chdir: "{{argos_repo_dir}}"
|
|
||||||
|
|
||||||
- name: Checkout specified argos release branch or tag
|
|
||||||
ansible.builtin.command: git checkout "{{argos_rel}}"
|
|
||||||
args:
|
|
||||||
chdir: "{{argos_repo_dir}}"
|
|
||||||
|
|
||||||
- name: Create argos archive for specified release
|
|
||||||
ansible.builtin.command: git archive -o "{{archive_path}}" "{{argos_rel}}"
|
|
||||||
args:
|
|
||||||
chdir: "{{argos_repo_dir}}"
|
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
- name: Ensure Python, Python Dev, Venv module is installed
|
- name: Ensure Python, Python Dev, Venv module is installed
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: [python3,python3-venv,python3-dev]
|
name: [python3,python3-venv,python3-dev, acl]
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,8 @@
|
|||||||
"enablePassword": true,
|
"enablePassword": true,
|
||||||
"enableSignUp": true,
|
"enableSignUp": true,
|
||||||
"disableSignin": false,
|
"disableSignin": false,
|
||||||
"clientId": "{{ vault_angelia_oauth_client_id }}",
|
"clientId": "{{ angelia_oauth2_client_id }}",
|
||||||
"clientSecret": "{{ vault_angelia_oauth_client_secret }}",
|
"clientSecret": "{{ angelia_oauth2_client_secret }}",
|
||||||
"providers": [],
|
"providers": [],
|
||||||
"signinMethods": [
|
"signinMethods": [
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"},
|
{"name": "Password", "displayName": "Password", "rule": "All"},
|
||||||
@@ -98,9 +98,147 @@
|
|||||||
"expireInHours": 168,
|
"expireInHours": 168,
|
||||||
"failedSigninLimit": 5,
|
"failedSigninLimit": 5,
|
||||||
"failedSigninFrozenTime": 15,
|
"failedSigninFrozenTime": 15,
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}.ant-input:focus,.ant-input-focused{border-color:#4b96ff!important;box-shadow:0 0 0 2px rgba(75,150,255,0.2)!important}.ant-checkbox-checked .ant-checkbox-inner{background-color:#4b96ff!important;border-color:#4b96ff!important}</style>",
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}.ant-input:focus,.ant-input-focused{border-color:#4b96ff!important;box-shadow:0 0 0 2px rgba(75,150,255,0.2)!important}.ant-checkbox-checked .ant-checkbox-inner{background-color:#4b96ff!important;border-color:#4b96ff!important}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"owner": "admin",
|
||||||
|
"name": "athena",
|
||||||
|
"displayName": "Athena",
|
||||||
|
"logo": "https://helu.ca/media/images/helu-ca_logo.original.svg",
|
||||||
|
"homepageUrl": "https://athena.ouranos.helu.ca",
|
||||||
|
"organization": "heluca",
|
||||||
|
"cert": "cert-heluca",
|
||||||
|
"enablePassword": true,
|
||||||
|
"enableSignUp": true,
|
||||||
|
"disableSignin": false,
|
||||||
|
"clientId": "{{ athena_oauth2_client_id }}",
|
||||||
|
"clientSecret": "{{ athena_oauth2_client_secret }}",
|
||||||
|
"providers": [],
|
||||||
|
"signinMethods": [
|
||||||
|
{"name": "Password", "displayName": "Password", "rule": "All"},
|
||||||
|
{"name": "Verification code", "displayName": "Verification code", "rule": "All"},
|
||||||
|
{"name": "WebAuthn", "displayName": "WebAuthn", "rule": "None"}
|
||||||
|
],
|
||||||
|
"signupItems": [
|
||||||
|
{"name": "ID", "visible": false, "required": true, "prompted": false, "rule": "Random"},
|
||||||
|
{"name": "Email", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Display name", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Confirm password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Agreement", "visible": true, "required": true, "prompted": false, "rule": "None"}
|
||||||
|
],
|
||||||
|
"grantTypes": [
|
||||||
|
"authorization_code",
|
||||||
|
"password",
|
||||||
|
"client_credentials",
|
||||||
|
"token",
|
||||||
|
"id_token",
|
||||||
|
"refresh_token"
|
||||||
|
],
|
||||||
|
"redirectUris": [
|
||||||
|
"https://athena.ouranos.helu.ca/accounts/oidc/casdoor/login/callback/"
|
||||||
|
],
|
||||||
|
"tokenFormat": "JWT",
|
||||||
|
"tokenFields": [],
|
||||||
|
"expireInHours": 168,
|
||||||
|
"failedSigninLimit": 5,
|
||||||
|
"failedSigninFrozenTime": 15,
|
||||||
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}.ant-input:focus,.ant-input-focused{border-color:#4b96ff!important;box-shadow:0 0 0 2px rgba(75,150,255,0.2)!important}.ant-checkbox-checked .ant-checkbox-inner{background-color:#4b96ff!important;border-color:#4b96ff!important}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "admin",
|
||||||
|
"name": "kairos",
|
||||||
|
"displayName": "Kairos",
|
||||||
|
"logo": "https://helu.ca/media/images/helu-ca_logo.original.svg",
|
||||||
|
"homepageUrl": "https://kairos.ouranos.helu.ca",
|
||||||
|
"organization": "heluca",
|
||||||
|
"cert": "cert-heluca",
|
||||||
|
"enablePassword": true,
|
||||||
|
"enableSignUp": true,
|
||||||
|
"disableSignin": false,
|
||||||
|
"clientId": "{{ kairos_oauth2_client_id }}",
|
||||||
|
"clientSecret": "{{ kairos_oauth2_client_secret }}",
|
||||||
|
"providers": [],
|
||||||
|
"signinMethods": [
|
||||||
|
{"name": "Password", "displayName": "Password", "rule": "All"},
|
||||||
|
{"name": "Verification code", "displayName": "Verification code", "rule": "All"},
|
||||||
|
{"name": "WebAuthn", "displayName": "WebAuthn", "rule": "None"}
|
||||||
|
],
|
||||||
|
"signupItems": [
|
||||||
|
{"name": "ID", "visible": false, "required": true, "prompted": false, "rule": "Random"},
|
||||||
|
{"name": "Email", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Display name", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Confirm password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Agreement", "visible": true, "required": true, "prompted": false, "rule": "None"}
|
||||||
|
],
|
||||||
|
"grantTypes": [
|
||||||
|
"authorization_code",
|
||||||
|
"password",
|
||||||
|
"client_credentials",
|
||||||
|
"token",
|
||||||
|
"id_token",
|
||||||
|
"refresh_token"
|
||||||
|
],
|
||||||
|
"redirectUris": [
|
||||||
|
"https://kairos.ouranos.helu.ca/accounts/oidc/casdoor/login/callback/"
|
||||||
|
],
|
||||||
|
"tokenFormat": "JWT",
|
||||||
|
"tokenFields": [],
|
||||||
|
"expireInHours": 168,
|
||||||
|
"failedSigninLimit": 5,
|
||||||
|
"failedSigninFrozenTime": 15,
|
||||||
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}.ant-input:focus,.ant-input-focused{border-color:#4b96ff!important;box-shadow:0 0 0 2px rgba(75,150,255,0.2)!important}.ant-checkbox-checked .ant-checkbox-inner{background-color:#4b96ff!important;border-color:#4b96ff!important}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "admin",
|
||||||
|
"name": "spelunker",
|
||||||
|
"displayName": "Spelunker",
|
||||||
|
"logo": "https://helu.ca/media/images/helu-ca_logo.original.svg",
|
||||||
|
"homepageUrl": "https://spelunker.ouranos.helu.ca",
|
||||||
|
"organization": "heluca",
|
||||||
|
"cert": "cert-heluca",
|
||||||
|
"enablePassword": true,
|
||||||
|
"enableSignUp": true,
|
||||||
|
"disableSignin": false,
|
||||||
|
"clientId": "{{ spelunker_oauth2_client_id }}",
|
||||||
|
"clientSecret": "{{ spelunker_oauth2_client_secret }}",
|
||||||
|
"providers": [],
|
||||||
|
"signinMethods": [
|
||||||
|
{"name": "Password", "displayName": "Password", "rule": "All"},
|
||||||
|
{"name": "Verification code", "displayName": "Verification code", "rule": "All"},
|
||||||
|
{"name": "WebAuthn", "displayName": "WebAuthn", "rule": "None"}
|
||||||
|
],
|
||||||
|
"signupItems": [
|
||||||
|
{"name": "ID", "visible": false, "required": true, "prompted": false, "rule": "Random"},
|
||||||
|
{"name": "Email", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Display name", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Confirm password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
||||||
|
{"name": "Agreement", "visible": true, "required": true, "prompted": false, "rule": "None"}
|
||||||
|
],
|
||||||
|
"grantTypes": [
|
||||||
|
"authorization_code",
|
||||||
|
"password",
|
||||||
|
"client_credentials",
|
||||||
|
"token",
|
||||||
|
"id_token",
|
||||||
|
"refresh_token"
|
||||||
|
],
|
||||||
|
"redirectUris": [
|
||||||
|
"https://spelunker.ouranos.helu.ca/accounts/oidc/casdoor/login/callback/"
|
||||||
|
],
|
||||||
|
"tokenFormat": "JWT",
|
||||||
|
"tokenFields": [],
|
||||||
|
"expireInHours": 168,
|
||||||
|
"failedSigninLimit": 5,
|
||||||
|
"failedSigninFrozenTime": 15,
|
||||||
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}.ant-input:focus,.ant-input-focused{border-color:#4b96ff!important;box-shadow:0 0 0 2px rgba(75,150,255,0.2)!important}.ant-checkbox-checked .ant-checkbox-inner{background-color:#4b96ff!important;border-color:#4b96ff!important}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"owner": "admin",
|
"owner": "admin",
|
||||||
"name": "gitea",
|
"name": "gitea",
|
||||||
@@ -111,8 +249,8 @@
|
|||||||
"cert": "cert-heluca",
|
"cert": "cert-heluca",
|
||||||
"enablePassword": true,
|
"enablePassword": true,
|
||||||
"enableSignUp": false,
|
"enableSignUp": false,
|
||||||
"clientId": "{{ vault_gitea_oauth_client_id }}",
|
"clientId": "{{ gitea_oauth2_client_id }}",
|
||||||
"clientSecret": "{{ vault_gitea_oauth_client_secret }}",
|
"clientSecret": "{{ gitea_oauth2_client_secret }}",
|
||||||
"providers": [],
|
"providers": [],
|
||||||
"signinMethods": [
|
"signinMethods": [
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"}
|
{"name": "Password", "displayName": "Password", "rule": "All"}
|
||||||
@@ -133,7 +271,7 @@
|
|||||||
],
|
],
|
||||||
"tokenFormat": "JWT",
|
"tokenFormat": "JWT",
|
||||||
"expireInHours": 168,
|
"expireInHours": 168,
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}</style>",
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -146,8 +284,8 @@
|
|||||||
"cert": "cert-heluca",
|
"cert": "cert-heluca",
|
||||||
"enablePassword": true,
|
"enablePassword": true,
|
||||||
"enableSignUp": false,
|
"enableSignUp": false,
|
||||||
"clientId": "{{ vault_jupyterlab_oauth_client_id }}",
|
"clientId": "{{ jupyterlab_oauth2_client_id }}",
|
||||||
"clientSecret": "{{ vault_jupyterlab_oauth_client_secret }}",
|
"clientSecret": "{{ jupyterlab_oauth2_client_secret }}",
|
||||||
"providers": [],
|
"providers": [],
|
||||||
"signinMethods": [
|
"signinMethods": [
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"}
|
{"name": "Password", "displayName": "Password", "rule": "All"}
|
||||||
@@ -168,7 +306,7 @@
|
|||||||
],
|
],
|
||||||
"tokenFormat": "JWT",
|
"tokenFormat": "JWT",
|
||||||
"expireInHours": 168,
|
"expireInHours": 168,
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}</style>",
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -181,8 +319,8 @@
|
|||||||
"cert": "cert-heluca",
|
"cert": "cert-heluca",
|
||||||
"enablePassword": true,
|
"enablePassword": true,
|
||||||
"enableSignUp": false,
|
"enableSignUp": false,
|
||||||
"clientId": "{{ vault_searxng_oauth_client_id }}",
|
"clientId": "{{ searxng_oauth2_client_id }}",
|
||||||
"clientSecret": "{{ vault_searxng_oauth_client_secret }}",
|
"clientSecret": "{{ searxng_oauth2_client_secret }}",
|
||||||
"providers": [],
|
"providers": [],
|
||||||
"signinMethods": [
|
"signinMethods": [
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"}
|
{"name": "Password", "displayName": "Password", "rule": "All"}
|
||||||
@@ -203,42 +341,7 @@
|
|||||||
],
|
],
|
||||||
"tokenFormat": "JWT",
|
"tokenFormat": "JWT",
|
||||||
"expireInHours": 168,
|
"expireInHours": 168,
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}</style>",
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"owner": "admin",
|
|
||||||
"name": "openwebui",
|
|
||||||
"displayName": "Open WebUI",
|
|
||||||
"logo": "https://helu.ca/media/images/helu-ca_logo.original.svg",
|
|
||||||
"homepageUrl": "https://openwebui.ouranos.helu.ca",
|
|
||||||
"organization": "heluca",
|
|
||||||
"cert": "cert-heluca",
|
|
||||||
"enablePassword": true,
|
|
||||||
"enableSignUp": false,
|
|
||||||
"clientId": "{{ vault_openwebui_oauth_client_id }}",
|
|
||||||
"clientSecret": "{{ vault_openwebui_oauth_client_secret }}",
|
|
||||||
"providers": [],
|
|
||||||
"signinMethods": [
|
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"}
|
|
||||||
],
|
|
||||||
"signupItems": [
|
|
||||||
{"name": "ID", "visible": false, "required": true, "prompted": false, "rule": "Random"},
|
|
||||||
{"name": "Email", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
|
||||||
{"name": "Display name", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
|
||||||
{"name": "Password", "visible": true, "required": true, "prompted": false, "rule": "None"},
|
|
||||||
{"name": "Confirm password", "visible": true, "required": true, "prompted": false, "rule": "None"}
|
|
||||||
],
|
|
||||||
"grantTypes": [
|
|
||||||
"authorization_code",
|
|
||||||
"refresh_token"
|
|
||||||
],
|
|
||||||
"redirectUris": [
|
|
||||||
"https://openwebui.ouranos.helu.ca/oauth/oidc/callback"
|
|
||||||
],
|
|
||||||
"tokenFormat": "JWT",
|
|
||||||
"expireInHours": 168,
|
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}</style>",
|
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -251,8 +354,8 @@
|
|||||||
"cert": "cert-heluca",
|
"cert": "cert-heluca",
|
||||||
"enablePassword": true,
|
"enablePassword": true,
|
||||||
"enableSignUp": false,
|
"enableSignUp": false,
|
||||||
"clientId": "{{ vault_daedalus_oauth_client_id }}",
|
"clientId": "{{ daedalus_oauth2_client_id }}",
|
||||||
"clientSecret": "{{ vault_daedalus_oauth_client_secret }}",
|
"clientSecret": "{{ daedalus_oauth2_client_secret }}",
|
||||||
"providers": [],
|
"providers": [],
|
||||||
"signinMethods": [
|
"signinMethods": [
|
||||||
{"name": "Password", "displayName": "Password", "rule": "All"}
|
{"name": "Password", "displayName": "Password", "rule": "All"}
|
||||||
@@ -273,7 +376,7 @@
|
|||||||
],
|
],
|
||||||
"tokenFormat": "JWT",
|
"tokenFormat": "JWT",
|
||||||
"expireInHours": 168,
|
"expireInHours": 168,
|
||||||
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}</style>",
|
"formCss": "<style>.login-panel{background-color:#ffffff;border-radius:10px;box-shadow:0 0 30px 20px rgba(255,164,21,0.12)}.ant-btn-primary{background-color:#4b96ff!important;border-color:#4b96ff!important}.ant-btn-primary:hover{background-color:#58c0ff!important;border-color:#58c0ff!important}a{color:#ffa415}a:hover{color:#ffc219}@media(prefers-color-scheme:dark){.login-panel{background-color:#1a1a2e!important;box-shadow:0 0 30px 20px rgba(255,164,21,0.06)!important}}</style>",
|
||||||
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
"footerHtml": "<div style=\"text-align:center;padding:10px;color:#666;\"><a href=\"https://helu.ca\" style=\"color:#4b96ff;text-decoration:none;\">Powered by Helu.ca</a></div>"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ fi
|
|||||||
# Set permissions and atomic move
|
# Set permissions and atomic move
|
||||||
chmod 644 "${TEMP_FILE}"
|
chmod 644 "${TEMP_FILE}"
|
||||||
chown prometheus:prometheus "${TEMP_FILE}" 2>/dev/null || true
|
chown prometheus:prometheus "${TEMP_FILE}" 2>/dev/null || true
|
||||||
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
mv "${TEMP_FILE}" "${METRICS_FILE}"
|
||||||
|
|||||||
@@ -2,14 +2,23 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Certbot Deployment Playbook
|
# Certbot Deployment Playbook
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Deploys certbot with Namecheap DNS-01 validation for wildcard certificates
|
# Deploys certbot with Namecheap DNS-01 validation and requests certificates.
|
||||||
# Host: hippocamp.helu.ca (OCI HAProxy instance)
|
# Reusable across all certbot hosts (horkos, bootes).
|
||||||
|
#
|
||||||
|
# Supports two host_vars patterns:
|
||||||
|
# Single-cert: certbot_cert_name + certbot_domains (horkos)
|
||||||
|
# Multi-cert: certbot_certificates list (bootes)
|
||||||
#
|
#
|
||||||
# Secrets are fetched automatically from OCI Vault via group_vars/all/secrets.yml
|
# Secrets are fetched automatically from OCI Vault via group_vars/all/secrets.yml
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ansible-playbook certbot/deploy.yml --limit horkos.helu.ca
|
||||||
|
# ansible-playbook certbot/deploy.yml --limit bootes.helu.ca
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Deploy Certbot with Namecheap DNS-01 Validation
|
- name: Deploy Certbot with Namecheap DNS-01 Validation
|
||||||
hosts: ubuntu
|
hosts: ubuntu
|
||||||
|
gather_facts: false
|
||||||
vars:
|
vars:
|
||||||
ansible_common_remote_group: "{{ certbot_group | default(omit) }}"
|
ansible_common_remote_group: "{{ certbot_group | default(omit) }}"
|
||||||
allow_world_readable_tmpfiles: true
|
allow_world_readable_tmpfiles: true
|
||||||
@@ -32,6 +41,16 @@
|
|||||||
ansible.builtin.meta: end_host
|
ansible.builtin.meta: end_host
|
||||||
when: not has_certbot_service
|
when: not has_certbot_service
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Build Unified Certificate List
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Build unified certificate list from host_vars
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_certbot_certs: >-
|
||||||
|
{{ certbot_certificates
|
||||||
|
| default([{'cert_name': certbot_cert_name, 'domains': certbot_domains}]) }}
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# System Setup
|
# System Setup
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -53,10 +72,17 @@
|
|||||||
home: "{{ certbot_directory }}"
|
home: "{{ certbot_directory }}"
|
||||||
create_home: false
|
create_home: false
|
||||||
|
|
||||||
- name: Add keeper_user to certbot group
|
- name: Add certbot user to ponos group
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ keeper_user }}"
|
name: "{{ certbot_user }}"
|
||||||
|
groups: ponos
|
||||||
|
append: true
|
||||||
|
|
||||||
|
- name: Add ponos user to certbot group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: ponos
|
||||||
groups: "{{ certbot_group }}"
|
groups: "{{ certbot_group }}"
|
||||||
append: true
|
append: true
|
||||||
|
|
||||||
@@ -80,32 +106,6 @@
|
|||||||
- "{{ certbot_directory }}/credentials"
|
- "{{ certbot_directory }}/credentials"
|
||||||
- "{{ certbot_directory }}/hooks"
|
- "{{ certbot_directory }}/hooks"
|
||||||
|
|
||||||
- name: Create haproxy group for certificate directory
|
|
||||||
become: true
|
|
||||||
ansible.builtin.group:
|
|
||||||
name: "{{ haproxy_group | default('haproxy') }}"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Create haproxy user for certificate directory
|
|
||||||
become: true
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ haproxy_user | default('haproxy') }}"
|
|
||||||
comment: "HAProxy Load Balancer"
|
|
||||||
group: "{{ haproxy_group | default('haproxy') }}"
|
|
||||||
system: true
|
|
||||||
shell: /usr/sbin/nologin
|
|
||||||
home: /nonexistent
|
|
||||||
create_home: false
|
|
||||||
|
|
||||||
- name: Create certificate output directory
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/haproxy/certs
|
|
||||||
owner: "{{ certbot_user }}"
|
|
||||||
group: "{{ haproxy_group | default('haproxy') }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0750'
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Python Virtual Environment
|
# Python Virtual Environment
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@@ -116,6 +116,7 @@
|
|||||||
name:
|
name:
|
||||||
- python3-venv
|
- python3-venv
|
||||||
- python3-pip
|
- python3-pip
|
||||||
|
- acl
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
@@ -125,50 +126,36 @@
|
|||||||
ansible.builtin.command: python3 -m venv {{ certbot_directory }}/.venv
|
ansible.builtin.command: python3 -m venv {{ certbot_directory }}/.venv
|
||||||
args:
|
args:
|
||||||
creates: "{{ certbot_directory }}/.venv/bin/activate"
|
creates: "{{ certbot_directory }}/.venv/bin/activate"
|
||||||
vars:
|
|
||||||
ansible_common_remote_group: "{{ certbot_group }}"
|
|
||||||
allow_world_readable_tmpfiles: true
|
|
||||||
|
|
||||||
- name: Upgrade pip in virtualenv
|
|
||||||
become: true
|
|
||||||
become_user: "{{ certbot_user }}"
|
|
||||||
ansible.builtin.pip:
|
|
||||||
name: pip
|
|
||||||
state: latest
|
|
||||||
virtualenv: "{{ certbot_directory }}/.venv"
|
|
||||||
vars:
|
|
||||||
ansible_common_remote_group: "{{ certbot_group }}"
|
|
||||||
allow_world_readable_tmpfiles: true
|
|
||||||
|
|
||||||
- name: Install certbot and Namecheap DNS plugin
|
- name: Install certbot and Namecheap DNS plugin
|
||||||
become: true
|
become: true
|
||||||
become_user: "{{ certbot_user }}"
|
become_user: "{{ certbot_user }}"
|
||||||
ansible.builtin.pip:
|
ansible.builtin.pip:
|
||||||
name:
|
name:
|
||||||
|
- pip
|
||||||
- certbot
|
- certbot
|
||||||
- certbot-dns-namecheap
|
- certbot-dns-namecheap
|
||||||
state: present
|
state: present
|
||||||
virtualenv: "{{ certbot_directory }}/.venv"
|
virtualenv: "{{ certbot_directory }}/.venv"
|
||||||
vars:
|
|
||||||
ansible_common_remote_group: "{{ certbot_group }}"
|
|
||||||
allow_world_readable_tmpfiles: true
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Namecheap Credentials
|
# Namecheap Credentials
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Get public IP for Namecheap API
|
- name: Get public IP for Namecheap API whitelisting
|
||||||
ansible.builtin.uri:
|
ansible.builtin.uri:
|
||||||
url: https://ifconfig.me/ip
|
url: https://ifconfig.me/ip
|
||||||
return_content: true
|
return_content: true
|
||||||
register: public_ip_result
|
register: public_ip_result
|
||||||
delegate_to: localhost
|
|
||||||
become: false
|
|
||||||
|
|
||||||
- name: Set client IP fact
|
- name: Set client IP fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
namecheap_client_ip: "{{ public_ip_result.content | trim }}"
|
namecheap_client_ip: "{{ public_ip_result.content | trim }}"
|
||||||
|
|
||||||
|
- name: Display public IP for Namecheap API whitelisting
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "Public IP: {{ namecheap_client_ip }} — ensure whitelisted at https://ap.www.namecheap.com/settings/tools/apiaccess/"
|
||||||
|
|
||||||
- name: Template Namecheap credentials
|
- name: Template Namecheap credentials
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -191,6 +178,15 @@
|
|||||||
group: "{{ certbot_group }}"
|
group: "{{ certbot_group }}"
|
||||||
mode: '0750'
|
mode: '0750'
|
||||||
|
|
||||||
|
- name: Create Prometheus textfile directory
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ prometheus_node_exporter_text_directory }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
- name: Template certificate metrics script
|
- name: Template certificate metrics script
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
@@ -201,20 +197,49 @@
|
|||||||
mode: '0750'
|
mode: '0750'
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Initial Certificate Request
|
# Certificate Requests
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Check if certificate already exists
|
- name: Check if certificates already exist
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "{{ certbot_directory }}/config/live/{{ certbot_cert_name }}/fullchain.pem"
|
path: "{{ certbot_directory }}/config/live/{{ item.cert_name }}/fullchain.pem"
|
||||||
register: cert_exists
|
register: cert_check
|
||||||
|
loop: "{{ _certbot_certs }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.cert_name }}"
|
||||||
|
|
||||||
- name: Build domain arguments for certbot
|
- name: Get current certificate domains
|
||||||
|
become: true
|
||||||
|
ansible.builtin.shell: |
|
||||||
|
set -euo pipefail
|
||||||
|
openssl x509 -in {{ certbot_directory }}/config/live/{{ item.item.cert_name }}/fullchain.pem \
|
||||||
|
-noout -ext subjectAltName | \
|
||||||
|
grep -oP 'DNS:\K[^,\s]+' | sort
|
||||||
|
args:
|
||||||
|
executable: /bin/bash
|
||||||
|
register: cert_domains
|
||||||
|
loop: "{{ cert_check.results }}"
|
||||||
|
when: item.stat.exists
|
||||||
|
changed_when: false
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.cert_name }}"
|
||||||
|
|
||||||
|
- name: Determine which certificates need requesting
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
certbot_domain_args: "{{ certbot_domains | map('regex_replace', '^', '-d ') | join(' ') }}"
|
_certs_to_request: >-
|
||||||
|
{{ _certs_to_request | default([]) + [
|
||||||
|
item.0.item | combine({
|
||||||
|
'needs_request': not item.0.stat.exists,
|
||||||
|
'domains_changed': item.0.stat.exists and
|
||||||
|
(item.1.stdout_lines | default([]) | sort) != (item.0.item.domains | sort)
|
||||||
|
})
|
||||||
|
] }}
|
||||||
|
loop: "{{ cert_check.results | zip_longest(cert_domains.results | default([]), fillvalue={}) | list }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.0.item.cert_name }}"
|
||||||
|
|
||||||
- name: Request initial certificate
|
- name: Request certificates
|
||||||
become: true
|
become: true
|
||||||
become_user: "{{ certbot_user }}"
|
become_user: "{{ certbot_user }}"
|
||||||
ansible.builtin.shell: |
|
ansible.builtin.shell: |
|
||||||
@@ -229,17 +254,42 @@
|
|||||||
--config-dir {{ certbot_directory }}/config \
|
--config-dir {{ certbot_directory }}/config \
|
||||||
--work-dir {{ certbot_directory }}/work \
|
--work-dir {{ certbot_directory }}/work \
|
||||||
--logs-dir {{ certbot_directory }}/logs \
|
--logs-dir {{ certbot_directory }}/logs \
|
||||||
--cert-name {{ certbot_cert_name }} \
|
--cert-name {{ item.cert_name }} \
|
||||||
{{ certbot_domain_args }}
|
{{ '--force-renewal' if item.domains_changed | default(false) else '' }} \
|
||||||
|
{{ item.domains | map('regex_replace', '^', '-d ') | join(' ') }}
|
||||||
args:
|
args:
|
||||||
executable: /bin/bash
|
executable: /bin/bash
|
||||||
when: not cert_exists.stat.exists
|
loop: "{{ _certs_to_request | selectattr('needs_request') | list +
|
||||||
register: certbot_request
|
_certs_to_request | selectattr('domains_changed') | list }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.cert_name }}"
|
||||||
|
register: certbot_requests
|
||||||
|
|
||||||
- name: Run renewal hook after initial certificate
|
- name: Run renewal hook after certificate requests
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.command: "{{ certbot_directory }}/hooks/renewal-hook.sh"
|
ansible.builtin.command: "{{ certbot_directory }}/hooks/renewal-hook.sh"
|
||||||
when: certbot_request.changed
|
environment:
|
||||||
|
RENEWED_LINEAGE: "{{ certbot_directory }}/config/live/{{ item.item.cert_name }}"
|
||||||
|
loop: "{{ certbot_requests.results | default([]) }}"
|
||||||
|
when: item.changed | default(false)
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.cert_name }}"
|
||||||
|
|
||||||
|
- name: Check if HAProxy PEM exists
|
||||||
|
become: true
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ haproxy_cert_path }}"
|
||||||
|
register: _haproxy_pem_check
|
||||||
|
when: haproxy_cert_path is defined
|
||||||
|
|
||||||
|
- name: Ensure HAProxy PEM is current
|
||||||
|
become: true
|
||||||
|
ansible.builtin.command: "{{ certbot_directory }}/hooks/renewal-hook.sh"
|
||||||
|
environment:
|
||||||
|
RENEWED_LINEAGE: "{{ certbot_directory }}/config/live/{{ _certbot_certs[0].cert_name }}"
|
||||||
|
when:
|
||||||
|
- haproxy_cert_path is defined
|
||||||
|
- not (_haproxy_pem_check.stat.exists | default(false))
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Systemd Timer for Auto-Renewal
|
# Systemd Timer for Auto-Renewal
|
||||||
@@ -269,7 +319,7 @@
|
|||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
content: |
|
content: |
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Run certbot renewal twice daily
|
Description=Check certbot certificates and renew if expiring
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=*-*-* 00,12:00:00
|
OnCalendar=*-*-* 00,12:00:00
|
||||||
@@ -294,15 +344,6 @@
|
|||||||
# Initial Metrics Update
|
# Initial Metrics Update
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Ensure prometheus textfile directory exists
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ prometheus_node_exporter_text_directory }}"
|
|
||||||
state: directory
|
|
||||||
owner: prometheus
|
|
||||||
group: prometheus
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Run certificate metrics script
|
- name: Run certificate metrics script
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.command: "{{ certbot_directory }}/hooks/cert-metrics.sh"
|
ansible.builtin.command: "{{ certbot_directory }}/hooks/cert-metrics.sh"
|
||||||
@@ -312,12 +353,30 @@
|
|||||||
# Verification
|
# Verification
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
- name: Verify certificate exists
|
- name: Verify certificates exist
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "{{ haproxy_cert_path }}"
|
path: "{{ certbot_directory }}/config/live/{{ item.cert_name }}/fullchain.pem"
|
||||||
register: final_cert
|
register: final_certs
|
||||||
|
loop: "{{ _certbot_certs }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.cert_name }}"
|
||||||
|
|
||||||
- name: Certificate deployment status
|
- name: Certificate deployment status
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: "Certificate deployed: {{ final_cert.stat.exists }}"
|
msg: "{{ item.item.cert_name }}: {{ 'deployed' if item.stat.exists else 'MISSING' }}"
|
||||||
|
loop: "{{ final_certs.results }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.item.cert_name }}"
|
||||||
|
|
||||||
|
- name: Verify HAProxy combined PEM exists
|
||||||
|
become: true
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ haproxy_cert_path }}"
|
||||||
|
register: _haproxy_pem
|
||||||
|
when: haproxy_cert_path is defined
|
||||||
|
|
||||||
|
- name: HAProxy PEM status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "HAProxy PEM {{ haproxy_cert_path }}: {{ 'present' if _haproxy_pem.stat.exists else 'MISSING — renewal hook may have failed' }}"
|
||||||
|
when: haproxy_cert_path is defined and _haproxy_pem is defined
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ dns_namecheap_username = {{ namecheap_username }}
|
|||||||
dns_namecheap_api_key = {{ namecheap_api_key }}
|
dns_namecheap_api_key = {{ namecheap_api_key }}
|
||||||
{% if namecheap_client_ip is defined %}
|
{% if namecheap_client_ip is defined %}
|
||||||
dns_namecheap_client_ip = {{ namecheap_client_ip }}
|
dns_namecheap_client_ip = {{ namecheap_client_ip }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
# This script:
|
# This script:
|
||||||
# 1. Combines fullchain.pem + privkey.pem into HAProxy format
|
# 1. Combines fullchain.pem + privkey.pem into HAProxy format
|
||||||
# 2. Sets correct permissions
|
# 2. Sets correct permissions
|
||||||
# 3. Reloads HAProxy via Docker
|
# 3. Reloads HAProxy via systemd
|
||||||
# 4. Updates certificate metrics for Prometheus
|
# 4. Updates certificate metrics for Prometheus
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
CERT_NAME="{{ certbot_cert_name }}"
|
# RENEWED_LINEAGE is set by certbot --deploy-hook or passed explicitly by deploy.yml
|
||||||
CERT_DIR="{{ certbot_directory }}/config/live/${CERT_NAME}"
|
CERT_DIR="${RENEWED_LINEAGE:?RENEWED_LINEAGE must be set}"
|
||||||
|
CERT_NAME=$(basename "${CERT_DIR}")
|
||||||
HAPROXY_CERT="{{ haproxy_cert_path }}"
|
HAPROXY_CERT="{{ haproxy_cert_path }}"
|
||||||
HAPROXY_DIR="{{ haproxy_directory }}"
|
HAPROXY_DIR="{{ haproxy_directory }}"
|
||||||
|
|
||||||
@@ -37,10 +38,9 @@ chmod 640 "${HAPROXY_CERT}"
|
|||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Certificate combined and written to ${HAPROXY_CERT}"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Certificate combined and written to ${HAPROXY_CERT}"
|
||||||
|
|
||||||
# Reload HAProxy if running
|
# Reload HAProxy if running
|
||||||
if docker ps --format '{{ '{{' }}.Names{{ '}}' }}' | grep -q haproxy; then
|
if systemctl is-active --quiet haproxy; then
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Reloading HAProxy..."
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Reloading HAProxy..."
|
||||||
cd "${HAPROXY_DIR}"
|
systemctl reload haproxy
|
||||||
docker compose kill -s HUP haproxy || docker-compose kill -s HUP haproxy
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] HAProxy reloaded"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] HAProxy reloaded"
|
||||||
else
|
else
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] HAProxy not running, skipping reload"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] HAProxy not running, skipping reload"
|
||||||
@@ -49,4 +49,4 @@ fi
|
|||||||
# Update certificate metrics
|
# Update certificate metrics
|
||||||
{{ certbot_directory }}/hooks/cert-metrics.sh
|
{{ certbot_directory }}/hooks/cert-metrics.sh
|
||||||
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Renewal hook completed successfully"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Renewal hook completed successfully"
|
||||||
|
|||||||
21
ansible/freecad_mcp/.env.j2
Normal file
21
ansible/freecad_mcp/.env.j2
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# FreeCAD Robust MCP Server — Environment Configuration
|
||||||
|
# Managed by Ansible — do not edit manually
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MCP Transport Configuration
|
||||||
|
# =============================================================================
|
||||||
|
FREECAD_MCP_TRANSPORT=http
|
||||||
|
FREECAD_MCP_HTTP_PORT={{ freecad_mcp_port }}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# FreeCAD Connection Mode
|
||||||
|
# =============================================================================
|
||||||
|
FREECAD_MCP_MODE={{ freecad_mcp_mode | default('xmlrpc') }}
|
||||||
|
FREECAD_MCP_XMLRPC_HOST={{ freecad_mcp_xmlrpc_host | default('localhost') }}
|
||||||
|
FREECAD_MCP_XMLRPC_PORT={{ freecad_mcp_xmlrpc_port | default('9875') }}
|
||||||
|
FREECAD_MCP_TIMEOUT_MS={{ freecad_mcp_timeout_ms | default('30000') }}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Logging
|
||||||
|
# =============================================================================
|
||||||
|
FREECAD_MCP_LOG_LEVEL={{ freecad_mcp_log_level | default('INFO') }}
|
||||||
130
ansible/freecad_mcp/README.md
Normal file
130
ansible/freecad_mcp/README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# FreeCAD Robust MCP Server — Ansible Deployment
|
||||||
|
|
||||||
|
Deploys the [FreeCAD Robust MCP Server](https://pypi.org/project/freecad-robust-mcp/)
|
||||||
|
to Caliban as a systemd service with HTTP transport, ready for MCP Switchboard
|
||||||
|
consumption.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ caliban.incus │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────┐ │
|
||||||
|
│ │ freecad-mcp.service │ │
|
||||||
|
│ │ (streamable-http) │◄─── :22032 ──────────┤◄── MCP Switchboard
|
||||||
|
│ │ venv + PyPI package │ │ (oberon.incus)
|
||||||
|
│ └──────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ │ xmlrpc :9875 │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────┐ │
|
||||||
|
│ │ FreeCAD (future) │ │
|
||||||
|
│ │ XML-RPC server │ │
|
||||||
|
│ └──────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Caliban host in Ansible inventory (already exists in Ouranos)
|
||||||
|
- Python 3.11+ on Caliban (already present)
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### 1. Copy playbook files to Ouranos
|
||||||
|
|
||||||
|
Copy the contents of this directory into your Ouranos repo:
|
||||||
|
|
||||||
|
```
|
||||||
|
ansible/freecad_mcp/
|
||||||
|
├── deploy.yml
|
||||||
|
├── .env.j2
|
||||||
|
└── freecad-mcp.service.j2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add inventory group
|
||||||
|
|
||||||
|
Add to `ansible/inventory/hosts`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
freecad_mcp:
|
||||||
|
hosts:
|
||||||
|
caliban.incus:
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add host variables
|
||||||
|
|
||||||
|
Add to `ansible/inventory/host_vars/caliban.incus.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# FreeCAD Robust MCP Server
|
||||||
|
freecad_mcp_user: harper
|
||||||
|
freecad_mcp_group: harper
|
||||||
|
freecad_mcp_directory: /srv/freecad-mcp
|
||||||
|
freecad_mcp_port: 22032
|
||||||
|
freecad_mcp_version: "0.5.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `services` list:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
- alloy
|
||||||
|
- caliban
|
||||||
|
- docker
|
||||||
|
- freecad_mcp
|
||||||
|
- kernos
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Run the playbook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook freecad_mcp/deploy.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
To upgrade to a new PyPI version, update `freecad_mcp_version` in host_vars
|
||||||
|
and re-run the playbook. The pip install task will detect the version change
|
||||||
|
and the handler will restart the service.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
The playbook automatically validates the deployment by:
|
||||||
|
|
||||||
|
1. Waiting for the HTTP port to become available
|
||||||
|
2. Sending an MCP `initialize` JSON-RPC request to `/mcp`
|
||||||
|
3. Verifying a 200 response
|
||||||
|
|
||||||
|
You can also manually test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://caliban.incus:22032/mcp \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0.0"}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On Caliban
|
||||||
|
sudo systemctl status freecad-mcp
|
||||||
|
sudo systemctl restart freecad-mcp
|
||||||
|
sudo journalctl -u freecad-mcp -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
The systemd service runs with hardened settings:
|
||||||
|
|
||||||
|
| Setting | Value | Rationale |
|
||||||
|
|---------|-------|-----------|
|
||||||
|
| `NoNewPrivileges` | `true` | No privilege escalation |
|
||||||
|
| `ProtectSystem` | `strict` | Filesystem is read-only except allowed paths |
|
||||||
|
| `ProtectHome` | `read-only` | Home directories protected |
|
||||||
|
| `PrivateTmp` | `true` | Isolated /tmp namespace |
|
||||||
|
| `ReadWritePaths` | `/srv/freecad-mcp` | Only app directory is writable |
|
||||||
|
|
||||||
|
This is significantly more hardened than the Kernos service (which needs
|
||||||
|
broad filesystem access for shell commands).
|
||||||
218
ansible/freecad_mcp/deploy.yml
Normal file
218
ansible/freecad_mcp/deploy.yml
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
---
|
||||||
|
# =============================================================================
|
||||||
|
# FreeCAD Robust MCP Server — Ansible Deployment Playbook
|
||||||
|
# =============================================================================
|
||||||
|
# Deploys the FreeCAD MCP Server to Larissa as a systemd service.
|
||||||
|
#
|
||||||
|
# The server runs in xmlrpc mode with HTTP transport, exposing the MCP
|
||||||
|
# protocol over streamable-http for consumption by MCP Switchboard on Triton.
|
||||||
|
#
|
||||||
|
# Pattern: venv + pip install from GitHub fork (fastmcp>=3.2.0)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ansible-playbook freecad_mcp/deploy.yml
|
||||||
|
#
|
||||||
|
# Required host_vars:
|
||||||
|
# freecad_mcp_user: freecad-mcp
|
||||||
|
# freecad_mcp_group: freecad-mcp
|
||||||
|
# freecad_mcp_directory: /srv/freecad-mcp
|
||||||
|
# freecad_mcp_port: 22063
|
||||||
|
#
|
||||||
|
# Optional host_vars:
|
||||||
|
# freecad_mcp_host: "0.0.0.0"
|
||||||
|
# freecad_mcp_log_level: INFO
|
||||||
|
# freecad_mcp_mode: xmlrpc
|
||||||
|
# freecad_mcp_xmlrpc_host: "localhost"
|
||||||
|
# freecad_mcp_xmlrpc_port: 9875
|
||||||
|
# freecad_mcp_timeout_ms: 30000
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
- name: Deploy FreeCAD Robust MCP Server
|
||||||
|
hosts: freecad_mcp
|
||||||
|
vars:
|
||||||
|
ansible_common_remote_group: "{{ freecad_mcp_group }}"
|
||||||
|
allow_world_readable_tmpfiles: true
|
||||||
|
tasks:
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# User & Directory Setup
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Create FreeCAD MCP group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ freecad_mcp_group }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create FreeCAD MCP user
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ freecad_mcp_user }}"
|
||||||
|
group: "{{ freecad_mcp_group }}"
|
||||||
|
home: "/home/{{ freecad_mcp_user }}"
|
||||||
|
shell: /bin/bash
|
||||||
|
system: false
|
||||||
|
create_home: true
|
||||||
|
|
||||||
|
- name: Add keeper_user to FreeCAD MCP group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ keeper_user }}"
|
||||||
|
groups: "{{ freecad_mcp_group }}"
|
||||||
|
append: true
|
||||||
|
|
||||||
|
- name: Add principal_user to FreeCAD MCP group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ principal_user }}"
|
||||||
|
groups: "{{ freecad_mcp_group }}"
|
||||||
|
append: true
|
||||||
|
|
||||||
|
- name: Add freecad_mcp_user to principal_user's primary group
|
||||||
|
become: true
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ freecad_mcp_user }}"
|
||||||
|
groups: "{{ principal_user }}"
|
||||||
|
append: true
|
||||||
|
|
||||||
|
- name: Reset connection to pick up new group membership
|
||||||
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
|
- name: Create application directory
|
||||||
|
become: true
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ freecad_mcp_directory }}"
|
||||||
|
owner: "{{ freecad_mcp_user }}"
|
||||||
|
group: "{{ freecad_mcp_group }}"
|
||||||
|
state: directory
|
||||||
|
mode: '750'
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# System Dependencies
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Ensure Python 3, venv, dev headers, and FreeCAD are installed
|
||||||
|
become: true
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: [python3, python3-venv, python3-dev, git, freecad]
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Virtual Environment & Package Installation
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Create virtual environment
|
||||||
|
become: true
|
||||||
|
become_user: "{{ freecad_mcp_user }}"
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "python3 -m venv {{ freecad_mcp_directory }}/.venv/"
|
||||||
|
creates: "{{ freecad_mcp_directory }}/.venv/bin/activate"
|
||||||
|
|
||||||
|
- name: Install wheel in virtual environment
|
||||||
|
become: true
|
||||||
|
become_user: "{{ freecad_mcp_user }}"
|
||||||
|
ansible.builtin.pip:
|
||||||
|
name:
|
||||||
|
- wheel
|
||||||
|
state: latest
|
||||||
|
virtualenv: "{{ freecad_mcp_directory }}/.venv"
|
||||||
|
|
||||||
|
- name: Install freecad-robust-mcp from Heluca fork
|
||||||
|
become: true
|
||||||
|
become_user: "{{ freecad_mcp_user }}"
|
||||||
|
ansible.builtin.pip:
|
||||||
|
name:
|
||||||
|
- "freecad-robust-mcp @ git+https://github.com/heluca/freecad-addon-robust-mcp-server.git@{{ freecad_mcp_git_ref }}"
|
||||||
|
virtualenv: "{{ freecad_mcp_directory }}/.venv"
|
||||||
|
virtualenv_command: python3 -m venv
|
||||||
|
notify: restart freecad-mcp
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Configuration
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Template FreeCAD MCP .env configuration
|
||||||
|
become: true
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: .env.j2
|
||||||
|
dest: "{{ freecad_mcp_directory }}/.env"
|
||||||
|
owner: "{{ freecad_mcp_user }}"
|
||||||
|
group: "{{ freecad_mcp_group }}"
|
||||||
|
mode: '640'
|
||||||
|
notify: restart freecad-mcp
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Systemd Service
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Template systemd service file
|
||||||
|
become: true
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: freecad-mcp.service.j2
|
||||||
|
dest: /etc/systemd/system/freecad-mcp.service
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: '644'
|
||||||
|
notify: restart freecad-mcp
|
||||||
|
|
||||||
|
- name: Enable and start freecad-mcp service
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: freecad-mcp
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Validation
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
- name: Flush handlers to restart service before validation
|
||||||
|
ansible.builtin.meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Wait for FreeCAD MCP server to start
|
||||||
|
ansible.builtin.wait_for:
|
||||||
|
port: "{{ freecad_mcp_port }}"
|
||||||
|
host: localhost
|
||||||
|
delay: 3
|
||||||
|
timeout: 30
|
||||||
|
|
||||||
|
- name: Validate FreeCAD MCP HTTP endpoint is responding
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{ freecad_mcp_port }}/mcp"
|
||||||
|
method: POST
|
||||||
|
body_format: json
|
||||||
|
body:
|
||||||
|
jsonrpc: "2.0"
|
||||||
|
method: "initialize"
|
||||||
|
id: 1
|
||||||
|
params:
|
||||||
|
protocolVersion: "2025-03-26"
|
||||||
|
capabilities: {}
|
||||||
|
clientInfo:
|
||||||
|
name: "ansible-healthcheck"
|
||||||
|
version: "1.0.0"
|
||||||
|
status_code: [200]
|
||||||
|
return_content: true
|
||||||
|
register: mcp_check
|
||||||
|
retries: 5
|
||||||
|
delay: 5
|
||||||
|
until: mcp_check.status == 200
|
||||||
|
|
||||||
|
- name: Display server info
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
FreeCAD MCP Server deployed successfully on
|
||||||
|
{{ inventory_hostname }}:{{ freecad_mcp_port }}
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# Handlers
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: restart freecad-mcp
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: freecad-mcp
|
||||||
|
state: restarted
|
||||||
23
ansible/freecad_mcp/freecad-mcp.service.j2
Normal file
23
ansible/freecad_mcp/freecad-mcp.service.j2
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FreeCAD Robust MCP Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ freecad_mcp_user }}
|
||||||
|
Group={{ freecad_mcp_group }}
|
||||||
|
WorkingDirectory={{ freecad_mcp_directory }}
|
||||||
|
ExecStart={{ freecad_mcp_directory }}/.venv/bin/freecad-mcp
|
||||||
|
EnvironmentFile={{ freecad_mcp_directory }}/.env
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Security hardening — MCP server needs no special privileges
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=read-only
|
||||||
|
PrivateTmp=true
|
||||||
|
ReadWritePaths={{ freecad_mcp_directory }}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -187,8 +187,8 @@
|
|||||||
--config {{ gitea_config_file }}
|
--config {{ gitea_config_file }}
|
||||||
--name "{{ gitea_oauth_name }}"
|
--name "{{ gitea_oauth_name }}"
|
||||||
--provider openidConnect
|
--provider openidConnect
|
||||||
--key "{{ gitea_oauth_client_id }}"
|
--key "{{ gitea_oauth2_client_id }}"
|
||||||
--secret "{{ gitea_oauth_client_secret }}"
|
--secret "{{ gitea_oauth2_client_secret }}"
|
||||||
--auto-discover-url "https://id.ouranos.helu.ca/.well-known/openid-configuration"
|
--auto-discover-url "https://id.ouranos.helu.ca/.well-known/openid-configuration"
|
||||||
--scopes "{{ gitea_oauth_scopes }}"
|
--scopes "{{ gitea_oauth_scopes }}"
|
||||||
--skip-local-2fa
|
--skip-local-2fa
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
grafana-mcp:
|
grafana-mcp:
|
||||||
image: mcp/grafana:latest
|
image: git.helu.ca/r/mcp-grafana:latest
|
||||||
pull_policy: always
|
pull_policy: always
|
||||||
container_name: grafana-mcp
|
container_name: grafana-mcp
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
101
ansible/haproxy/configure.yml
Normal file
101
ansible/haproxy/configure.yml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# HAProxy Configuration Playbook
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Templates haproxy.cfg and starts the HAProxy service. Must run AFTER both
|
||||||
|
# haproxy/deploy.yml and certbot/deploy.yml so that:
|
||||||
|
# - The HAProxy package is installed
|
||||||
|
# - The real Let's Encrypt certificate exists at haproxy_cert_path
|
||||||
|
#
|
||||||
|
# Dependency chain:
|
||||||
|
# haproxy/deploy.yml ← package + dirs
|
||||||
|
# certbot/deploy.yml ← writes cert to /etc/haproxy/certs/
|
||||||
|
# haproxy/configure.yml ← this playbook (config + start)
|
||||||
|
#
|
||||||
|
# Hosts: horkos (public reverse proxy), bootes (internal HAProxy)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Configure and start HAProxy
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tags: [haproxy, service, configure]
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload haproxy
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: haproxy
|
||||||
|
state: reloaded
|
||||||
|
|
||||||
|
- name: restart haproxy
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: haproxy
|
||||||
|
state: restarted
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Check if host has haproxy service
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
has_haproxy_service: "{{ 'haproxy' in services | default([]) }}"
|
||||||
|
|
||||||
|
- name: Skip hosts without haproxy service
|
||||||
|
ansible.builtin.meta: end_host
|
||||||
|
when: not has_haproxy_service
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Certificate Check
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Check if TLS certificate exists
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ haproxy_cert_path }}"
|
||||||
|
register: cert_file
|
||||||
|
|
||||||
|
- name: Fail if certificate is missing
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: >
|
||||||
|
Certificate not found at {{ haproxy_cert_path }}.
|
||||||
|
Run certbot/deploy.yml before haproxy/configure.yml.
|
||||||
|
Command: ansible-playbook certbot/deploy.yml
|
||||||
|
when: not cert_file.stat.exists
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Configuration
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Template HAProxy configuration
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: haproxy.cfg.j2
|
||||||
|
dest: /etc/haproxy/haproxy.cfg
|
||||||
|
owner: root
|
||||||
|
group: "{{ haproxy_group | default('haproxy') }}"
|
||||||
|
mode: '0640'
|
||||||
|
validate: "haproxy -c -f %s"
|
||||||
|
notify: reload haproxy
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Service Management
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Enable and start HAProxy service
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: haproxy
|
||||||
|
enabled: true
|
||||||
|
state: started
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Verification
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Wait for HAProxy stats port to be ready
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{ haproxy_stats_port }}/metrics"
|
||||||
|
method: GET
|
||||||
|
status_code: 200
|
||||||
|
register: haproxy_health
|
||||||
|
retries: 5
|
||||||
|
delay: 3
|
||||||
|
until: haproxy_health.status == 200
|
||||||
|
|
||||||
|
- name: HAProxy configuration status
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "HAProxy is running and serving metrics on port {{ haproxy_stats_port }}"
|
||||||
@@ -1,117 +1,83 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy HAProxy
|
# -----------------------------------------------------------------------------
|
||||||
hosts: ubuntu
|
# HAProxy Deployment Playbook
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Installs HAProxy and creates the directory structure required by downstream
|
||||||
|
# playbooks. This playbook must run BEFORE certbot/deploy.yml so that the
|
||||||
|
# /etc/haproxy/certs directory exists with the correct haproxy group ownership
|
||||||
|
# when certbot writes the combined PEM file.
|
||||||
|
#
|
||||||
|
# Dependency chain:
|
||||||
|
# haproxy/deploy.yml ← this playbook (package + dirs)
|
||||||
|
# certbot/deploy.yml ← writes cert to /etc/haproxy/certs/
|
||||||
|
# haproxy/configure.yml ← templates haproxy.cfg and starts the service
|
||||||
|
#
|
||||||
|
# Hosts: horkos (public reverse proxy), bootes (internal HAProxy)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Deploy HAProxy (package and directory structure)
|
||||||
|
hosts: all
|
||||||
|
become: true
|
||||||
|
tags: [haproxy, service, deploy]
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Check if host has haproxy service
|
- name: Check if host has haproxy service
|
||||||
set_fact:
|
ansible.builtin.set_fact:
|
||||||
has_haproxy_service: "{{'haproxy' in services}}"
|
has_haproxy_service: "{{ 'haproxy' in services | default([]) }}"
|
||||||
|
|
||||||
- name: Skip hosts without haproxy service
|
- name: Skip hosts without haproxy service
|
||||||
meta: end_host
|
ansible.builtin.meta: end_host
|
||||||
when: not has_haproxy_service
|
when: not has_haproxy_service
|
||||||
|
|
||||||
- name: Create haproxy group
|
# -------------------------------------------------------------------------
|
||||||
become: true
|
# Install HAProxy
|
||||||
ansible.builtin.group:
|
# -------------------------------------------------------------------------
|
||||||
name: "{{haproxy_group}}"
|
|
||||||
gid: "{{haproxy_gid}}"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Create haproxy user
|
- name: Ensure HAProxy is installed
|
||||||
become: true
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{haproxy_user}}"
|
|
||||||
comment: "{{haproxy_user}}"
|
|
||||||
group: "{{haproxy_group}}"
|
|
||||||
uid: "{{haproxy_uid}}"
|
|
||||||
system: true
|
|
||||||
|
|
||||||
- name: Add group haproxy to keeper_user
|
|
||||||
become: true
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{keeper_user}}"
|
|
||||||
groups: "{{haproxy_group}}"
|
|
||||||
append: true
|
|
||||||
|
|
||||||
- name: Create required directories
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{haproxy_directory}}"
|
|
||||||
owner: "{{haproxy_user}}"
|
|
||||||
group: "{{haproxy_group}}"
|
|
||||||
state: directory
|
|
||||||
mode: '750'
|
|
||||||
|
|
||||||
- name: Create /etc/haproxy directory
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/haproxy
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
state: directory
|
|
||||||
mode: '755'
|
|
||||||
|
|
||||||
- name: Create certs directory
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/haproxy/certs
|
|
||||||
owner: "{{haproxy_user}}"
|
|
||||||
group: "{{haproxy_group}}"
|
|
||||||
state: directory
|
|
||||||
mode: '750'
|
|
||||||
|
|
||||||
- name: Check if certificate already exists
|
|
||||||
become: true
|
|
||||||
stat:
|
|
||||||
path: "{{ haproxy_cert_path }}"
|
|
||||||
register: cert_file
|
|
||||||
|
|
||||||
- name: Generate self-signed wildcard certificate
|
|
||||||
become: true
|
|
||||||
command: >
|
|
||||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048
|
|
||||||
-keyout {{ haproxy_cert_path }}
|
|
||||||
-out {{ haproxy_cert_path }}
|
|
||||||
-subj "/C=US/ST=State/L=City/O=Agathos/CN=*.{{ haproxy_domain }}"
|
|
||||||
-addext "subjectAltName=DNS:*.{{ haproxy_domain }},DNS:{{ haproxy_domain }}"
|
|
||||||
when: not cert_file.stat.exists and 'certbot' not in services
|
|
||||||
|
|
||||||
- name: Set certificate permissions
|
|
||||||
become: true
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: "{{ haproxy_cert_path }}"
|
|
||||||
owner: "{{haproxy_user}}"
|
|
||||||
group: "{{haproxy_group}}"
|
|
||||||
mode: '640'
|
|
||||||
|
|
||||||
- name: Install HAProxy
|
|
||||||
become: true
|
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: haproxy
|
name: haproxy
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
- name: Template HAProxy configuration
|
# -------------------------------------------------------------------------
|
||||||
become: true
|
# User / Group
|
||||||
ansible.builtin.template:
|
# HAProxy's apt package creates the haproxy user/group, but we also need
|
||||||
src: "haproxy.cfg.j2"
|
# the certbot group to exist so that /etc/haproxy/certs can be group-owned
|
||||||
dest: /etc/haproxy/haproxy.cfg
|
# by haproxy and written by certbot.
|
||||||
owner: "{{haproxy_user}}"
|
# -------------------------------------------------------------------------
|
||||||
group: "{{haproxy_group}}"
|
|
||||||
mode: "640"
|
|
||||||
validate: haproxy -c -f %s
|
|
||||||
register: haproxy_config
|
|
||||||
|
|
||||||
- name: Enable and start HAProxy service
|
- name: Ensure haproxy group exists
|
||||||
become: true
|
ansible.builtin.group:
|
||||||
ansible.builtin.systemd:
|
name: "{{ haproxy_group | default('haproxy') }}"
|
||||||
name: haproxy
|
system: true
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
|
|
||||||
- name: Reload HAProxy if configuration changed
|
- name: Ensure haproxy user exists
|
||||||
become: true
|
ansible.builtin.user:
|
||||||
ansible.builtin.systemd:
|
name: "{{ haproxy_user | default('haproxy') }}"
|
||||||
name: haproxy
|
group: "{{ haproxy_group | default('haproxy') }}"
|
||||||
state: reloaded
|
system: true
|
||||||
when: haproxy_config.changed
|
shell: /usr/sbin/nologin
|
||||||
|
home: /nonexistent
|
||||||
|
create_home: false
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Directory Structure
|
||||||
|
# /etc/haproxy/certs must exist with haproxy group ownership before certbot
|
||||||
|
# runs so that the renewal hook can write the combined PEM file there.
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
- name: Ensure /etc/haproxy directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/haproxy
|
||||||
|
owner: root
|
||||||
|
group: "{{ haproxy_group | default('haproxy') }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Ensure /etc/haproxy/certs directory exists
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/haproxy/certs
|
||||||
|
owner: "{{ haproxy_user | default('haproxy') }}"
|
||||||
|
group: "{{ haproxy_group | default('haproxy') }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0750'
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
# HAProxy configuration for Agathos Titania
|
# HAProxy configuration for Taurus Production Environment
|
||||||
# Managed by Ansible - Red Panda Approved
|
# Managed by Ansible - Red Panda Approved
|
||||||
|
#
|
||||||
|
# SSL: Let's Encrypt certificate for helu.ca subdomains
|
||||||
|
# HTTP backends: Casdoor (talos), Gitea (xenia), SearXNG (xenia)
|
||||||
|
# TCP backend: Gitea SSH (xenia)
|
||||||
|
|
||||||
global
|
global
|
||||||
log 127.0.0.1:{{ haproxy_syslog_port }} local0
|
log /dev/log local0
|
||||||
|
log /dev/log local1 notice
|
||||||
stats timeout 30s
|
stats timeout 30s
|
||||||
|
# Ubuntu systemd service handles user/group and daemonization
|
||||||
|
|
||||||
# Default SSL material locations
|
# Default SSL material locations
|
||||||
ca-base /etc/ssl/certs
|
ca-base /etc/ssl/certs
|
||||||
@@ -38,29 +44,49 @@ listen stats
|
|||||||
# Prometheus metrics endpoint
|
# Prometheus metrics endpoint
|
||||||
http-request use-service prometheus-exporter if { path /metrics }
|
http-request use-service prometheus-exporter if { path /metrics }
|
||||||
|
|
||||||
# HTTP frontend - redirect all traffic to HTTPS
|
# HTTP to HTTPS redirect
|
||||||
frontend http_frontend
|
frontend http_frontend
|
||||||
bind *:{{ haproxy_http_port }}
|
bind *:{{ haproxy_http_port }}
|
||||||
mode http
|
mode http
|
||||||
option httplog
|
option httplog
|
||||||
|
|
||||||
|
# Redirect all HTTP to HTTPS
|
||||||
http-request redirect scheme https code 301
|
http-request redirect scheme https code 301
|
||||||
|
|
||||||
# HTTPS frontend with dynamic routing
|
# HTTPS frontend with dynamic routing
|
||||||
frontend https_frontend
|
frontend https_frontend
|
||||||
bind *:{{ haproxy_https_port }} ssl crt {{ haproxy_cert_path }}
|
bind *:{{ haproxy_https_port }} ssl crt {{ haproxy_cert_path }} alpn h2,http/1.1
|
||||||
mode http
|
mode http
|
||||||
option httplog
|
option httplog
|
||||||
option forwardfor
|
option forwardfor
|
||||||
|
# Tell backends the original connection was HTTPS (TLS terminates here)
|
||||||
# Forward original protocol and host for reverse-proxied services
|
|
||||||
http-request set-header X-Forwarded-Proto https
|
http-request set-header X-Forwarded-Proto https
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
http-response set-header X-Frame-Options "SAMEORIGIN"
|
http-response set-header X-Frame-Options "SAMEORIGIN"
|
||||||
http-response set-header X-Content-Type-Options "nosniff"
|
http-response set-header X-Content-Type-Options "nosniff"
|
||||||
http-response set-header X-XSS-Protection "1; mode=block"
|
http-response set-header X-XSS-Protection "1; mode=block"
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# Rate limiting via stick-tables
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# General rate limit: 1000 req/min per source IP
|
||||||
|
stick-table type ip size 100k expire 1m store http_req_rate(1m)
|
||||||
|
http-request track-sc0 src
|
||||||
|
|
||||||
|
# Auth endpoint rate limit: 20 req/min per source IP
|
||||||
|
acl is_auth_endpoint path_beg /api/login /api/signup /api/get-captcha /login/oauth/authorize /api/login/oauth/access_token
|
||||||
|
acl host_id hdr_beg(host) -i id.{{ haproxy_domain }}
|
||||||
|
|
||||||
|
# Use backend stick-table for auth endpoint tracking
|
||||||
|
http-request track-sc1 src table st_casdoor_auth if host_id is_auth_endpoint
|
||||||
|
|
||||||
|
# Deny if general rate exceeded
|
||||||
|
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 1000 }
|
||||||
|
|
||||||
|
# Deny if auth endpoint rate exceeded
|
||||||
|
http-request deny deny_status 429 if host_id is_auth_endpoint { sc_http_req_rate(1,st_casdoor_auth) gt 20 }
|
||||||
|
|
||||||
{% for backend in haproxy_backends %}
|
{% for backend in haproxy_backends %}
|
||||||
{% if backend.subdomain %}
|
{% if backend.subdomain %}
|
||||||
@@ -86,29 +112,37 @@ backend backend_root
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
mode http
|
mode http
|
||||||
balance roundrobin
|
balance roundrobin
|
||||||
{% if backend.ssl_backend | default(false) %}
|
|
||||||
option httpchk
|
option httpchk
|
||||||
http-check send meth GET uri {{ backend.health_path }} hdr Host {{ backend.subdomain }}.{{ haproxy_domain }}
|
http-check send meth GET uri {{ backend.health_path }} ver HTTP/1.1 hdr Host {{ backend.health_host | default(backend.backend_host) }}
|
||||||
{% else %}
|
|
||||||
option httpchk GET {{ backend.health_path }}
|
|
||||||
{% endif %}
|
|
||||||
http-check expect status 200
|
http-check expect status 200
|
||||||
{% if backend.timeout_server is defined %}
|
{% if backend.timeout_server is defined %}
|
||||||
timeout server {{ backend.timeout_server }}
|
timeout server {{ backend.timeout_server }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
server {{ backend.subdomain or 'root' }}_1 {{ backend.backend_host }}:{{ backend.backend_port }} check{% if backend.ssl_backend | default(false) %} ssl verify none{% endif %}
|
server {{ backend.subdomain or 'root' }}_1 {{ backend.backend_host }}:{{ backend.backend_port }} check
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
# Stick-table for auth endpoint rate limiting (referenced by frontend)
|
||||||
|
backend st_casdoor_auth
|
||||||
|
stick-table type ip size 100k expire 1m store http_req_rate(1m)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TCP Frontends/Backends (non-HTTP protocols)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
{% for tcp_backend in haproxy_tcp_backends | default([]) %}
|
{% for tcp_backend in haproxy_tcp_backends | default([]) %}
|
||||||
# TCP passthrough: {{ tcp_backend.name }}
|
# TCP passthrough: {{ tcp_backend.name }}
|
||||||
frontend {{ tcp_backend.name }}_frontend
|
frontend {{ tcp_backend.name }}_frontend
|
||||||
bind *:{{ tcp_backend.listen_port }}
|
bind *:{{ tcp_backend.listen_port }}
|
||||||
mode tcp
|
mode tcp
|
||||||
option tcplog
|
option tcplog
|
||||||
|
timeout client 1h
|
||||||
default_backend {{ tcp_backend.name }}_backend
|
default_backend {{ tcp_backend.name }}_backend
|
||||||
|
|
||||||
backend {{ tcp_backend.name }}_backend
|
backend {{ tcp_backend.name }}_backend
|
||||||
mode tcp
|
mode tcp
|
||||||
|
option tcp-check
|
||||||
|
timeout server 1h
|
||||||
server {{ tcp_backend.name }}_1 {{ tcp_backend.backend_host }}:{{ tcp_backend.backend_port }} check
|
server {{ tcp_backend.name }}_1 {{ tcp_backend.backend_host }}:{{ tcp_backend.backend_port }} check
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@
|
|||||||
# principal_user - AI agent / human operator account (host-specific, defined in host_vars).
|
# principal_user - AI agent / human operator account (host-specific, defined in host_vars).
|
||||||
# NOTE: ansible.cfg retains 'remote_user = ponos' as the Ansible SSH built-in keyword.
|
# NOTE: ansible.cfg retains 'remote_user = ponos' as the Ansible SSH built-in keyword.
|
||||||
# Never use {{ remote_user }} or {{ ansible_user }} as Jinja2 variables in playbooks.
|
# Never use {{ remote_user }} or {{ ansible_user }} as Jinja2 variables in playbooks.
|
||||||
keeper_user: robert
|
keeper_user: ponos
|
||||||
keeper_uid: 1000
|
keeper_uid: 519
|
||||||
keeper_group: robert
|
keeper_group: ponos
|
||||||
keeper_home: /srv/ponos
|
keeper_home: /srv/ponos
|
||||||
watcher_user: poros
|
watcher_user: poros
|
||||||
watcher_uid: 520
|
watcher_uid: 520
|
||||||
deployment_environment: "agathos"
|
deployment_environment: "ouranos"
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
|
||||||
# Incus configuration (matches terraform.tfvars)
|
# Incus configuration (matches terraform.tfvars)
|
||||||
incus_project_name: agathos
|
incus_project_name: ouranos
|
||||||
incus_storage_pool: default
|
incus_storage_pool: default
|
||||||
|
|
||||||
# Gitea Runner
|
# Gitea Runner
|
||||||
@@ -22,19 +22,26 @@ act_runner_version: "0.2.13"
|
|||||||
gitea_runner_instance_url: "https://gitea.ouranos.helu.ca"
|
gitea_runner_instance_url: "https://gitea.ouranos.helu.ca"
|
||||||
|
|
||||||
# Release versions for staging playbooks
|
# Release versions for staging playbooks
|
||||||
|
agent_s_rel: main
|
||||||
anythingllm_rel: master
|
anythingllm_rel: master
|
||||||
athena_rel: master
|
athena_rel: main
|
||||||
athena_mcp_rel: master
|
athena_mcp_rel: main
|
||||||
argos_rel: master
|
argos_rel: latest
|
||||||
arke_rel: master
|
arke_rel: main
|
||||||
angelia_rel: master
|
angelia_rel: main
|
||||||
kairos_rel: master
|
kairos_rel: master
|
||||||
kairos_mcp_rel: master
|
kairos_mcp_rel: master
|
||||||
spelunker_rel: master
|
spelunker_rel: master
|
||||||
mcp_switchboard_rel: master
|
mcp_switchboard_rel: main
|
||||||
kernos_rel: master
|
kernos_rel: main
|
||||||
|
rommie_rel: main
|
||||||
# PyPI release version (no 'v' prefix) - https://pypi.org/project/open-webui/
|
# PyPI release version (no 'v' prefix) - https://pypi.org/project/open-webui/
|
||||||
|
freecad_mcp_version: 0.6.1
|
||||||
openwebui_rel: 0.8.3
|
openwebui_rel: 0.8.3
|
||||||
|
pulseaudio_module_xrdp_rel: devel
|
||||||
|
searxng_oauth2_proxy_version: 7.6.0
|
||||||
|
# Git ref (branch, tag, or commit) - https://github.com/heluca/freecad-addon-robust-mcp-server
|
||||||
|
freecad_mcp_git_ref: "main"
|
||||||
|
|
||||||
# MCP URLs
|
# MCP URLs
|
||||||
argos_mcp_url: http://miranda.incus:25534/mcp
|
argos_mcp_url: http://miranda.incus:25534/mcp
|
||||||
@@ -49,7 +56,8 @@ huggingface_mcp_token: "{{ vault_huggingface_mcp_token }}"
|
|||||||
neo4j_mcp_url: http://circe.helu.ca:22034/mcp
|
neo4j_mcp_url: http://circe.helu.ca:22034/mcp
|
||||||
nike_mcp_url: http://puck.incus:22031/mcp
|
nike_mcp_url: http://puck.incus:22031/mcp
|
||||||
korax_mcp_url: http://korax.helu.ca:22021/mcp
|
korax_mcp_url: http://korax.helu.ca:22021/mcp
|
||||||
rommie_mcp_url: http://caliban.incus:22031/mcp
|
rommie_mcp_url: https://rommie.ouranos.helu.ca/mcp
|
||||||
|
freecad_mcp_url: https://freecad-mcp.ouranos.helu.ca/mcp
|
||||||
|
|
||||||
# Monitoring and Logging (internal endpoints on Prospero)
|
# Monitoring and Logging (internal endpoints on Prospero)
|
||||||
loki_url: http://prospero.incus:3100/loki/api/v1/push
|
loki_url: http://prospero.incus:3100/loki/api/v1/push
|
||||||
@@ -63,12 +71,16 @@ docker_gpg_key_checksum: sha256:1500c1f56fa9e26b9b8f42452a553675796ade0807cdce11
|
|||||||
# RabbitMQ provisioning config
|
# RabbitMQ provisioning config
|
||||||
rabbitmq_vhosts:
|
rabbitmq_vhosts:
|
||||||
- name: kairos
|
- name: kairos
|
||||||
|
- name: mnemosyne
|
||||||
- name: spelunker
|
- name: spelunker
|
||||||
|
|
||||||
rabbitmq_users:
|
rabbitmq_users:
|
||||||
- name: kairos
|
- name: kairos
|
||||||
password: "{{ kairos_rabbitmq_password }}"
|
password: "{{ kairos_rabbitmq_password }}"
|
||||||
tags: []
|
tags: []
|
||||||
|
- name: mnemosyne
|
||||||
|
password: "{{ vault_mnemosyne_rabbitmq_password }}"
|
||||||
|
tags: []
|
||||||
- name: spelunker
|
- name: spelunker
|
||||||
password: "{{ spelunker_rabbitmq_password }}"
|
password: "{{ spelunker_rabbitmq_password }}"
|
||||||
tags: []
|
tags: []
|
||||||
@@ -79,6 +91,11 @@ rabbitmq_permissions:
|
|||||||
configure_priv: .*
|
configure_priv: .*
|
||||||
read_priv: .*
|
read_priv: .*
|
||||||
write_priv: .*
|
write_priv: .*
|
||||||
|
- vhost: mnemosyne
|
||||||
|
user: mnemosyne
|
||||||
|
configure_priv: .*
|
||||||
|
read_priv: .*
|
||||||
|
write_priv: .*
|
||||||
- vhost: spelunker
|
- vhost: spelunker
|
||||||
user: spelunker
|
user: spelunker
|
||||||
configure_priv: .*
|
configure_priv: .*
|
||||||
@@ -89,11 +106,11 @@ rabbitmq_permissions:
|
|||||||
smtp_host: oberon.incus
|
smtp_host: oberon.incus
|
||||||
smtp_port: 22025
|
smtp_port: 22025
|
||||||
smtp_from: noreply@ouranos.helu.ca
|
smtp_from: noreply@ouranos.helu.ca
|
||||||
smtp_from_name: "Agathos"
|
smtp_from_name: "Ouranos"
|
||||||
|
|
||||||
# Release directory paths
|
# Release directory paths
|
||||||
github_dir: ~/gh
|
github_dir: ~/gh
|
||||||
repo_dir: ~/dv
|
repo_dir: ~/git
|
||||||
rel_dir: ~/rel
|
rel_dir: ~/rel
|
||||||
|
|
||||||
# Vault Variable Mappings
|
# Vault Variable Mappings
|
||||||
|
|||||||
@@ -1,419 +1,448 @@
|
|||||||
$ANSIBLE_VAULT;1.1;AES256
|
$ANSIBLE_VAULT;1.1;AES256
|
||||||
35333766653739663835646535623833386463366135646261633263303636646634373633636465
|
33633331373932353931373937626532353538353064656235666461643362373366353839366638
|
||||||
3834633835613530633734353966353666656436653964340a623832666661643266613333366134
|
3833386332646634383239333633303331623463353261380a643336303632313435356437616563
|
||||||
35363065393030353665656231313534626635663366353233393736336137323266343232323664
|
39366664306565316132636531333635666237316132346165303633343465653239346630323834
|
||||||
6565343965306566320a396662313662616236333234643138393631653739653732396432363138
|
6532616236636535370a656536643635313462303430376334623862613934333665653963366561
|
||||||
30653430303565333365633461376131663131303964363261646263313863303365636463653839
|
33643563643266613562393961643062343365353431376261353261323236626161623139346430
|
||||||
38363633323638346661623833633463363139353033346634306461396232356434316133366533
|
62616538323231323636393665316566303234633235346537383832613834623764636230666434
|
||||||
36653830323464623866336133633031333431386335346130353436643366613165323330623837
|
31653565393938393438613237383261376539316362383462626133356133303330313966666262
|
||||||
35613831346538323965343930393830663561376666386565623736666461393439366261333564
|
35313435653063386235393564653364366661663563323066666661323637373761616334666631
|
||||||
63396534356465643462383437653930386662623038376462306564616166636332383566623163
|
61333137613436646139643937336464323465313562376438336434346638313133323562393366
|
||||||
39616330636631373562313735336365623863343733363337336561323239333861633061323561
|
61363639613731313261326133663861323333326337633134366232396661356666646663376438
|
||||||
63343032363931343137336238393935356536633166623662383735303033613135623239616334
|
33386630663431316261306237663339306166643635643861303034663430316531353131626538
|
||||||
35306337323533386364643263653638323236323538336436373563393436643031323433626466
|
63363266303332633864303335303131613761626531623164666538643432643766336535613139
|
||||||
64366631636461613665656262346231376532623638663364386630663238643536363231316462
|
37626132613032363966663939346639343665363833633337343937623962656136373638636339
|
||||||
32663763393266663434393365313763366237643931633337343464393164653932346230346432
|
39666336396433363938383432653936393262383631353635396562383961323763363931316633
|
||||||
32666530633837303532663438646138326236333736323633646336623963396530656539363439
|
31653465616661393031373433626464393235356430643630363436313862323464356263303931
|
||||||
32623065363961396533373666633737613133306130313638393364653865313035376466386334
|
31333839616161623936323436656333393138343666383461326564346337343839323039653433
|
||||||
66383231373538623432633738333739636237633162353662623033613538303030336135633162
|
36633736303136336638643736633361643038356133353234633363643563316135353333633539
|
||||||
39636566383033633263363066666365303539353932383565306261333236633163383932383932
|
62336137623762373030316361663135353737656635656139636664316331623233333762623734
|
||||||
38653965613335343633373265623935663062303839363730663534336430323434623035636338
|
36643330653961316238613731383961303238363364623839643262663539646631393562663230
|
||||||
66303638343333323839386238363032343835346636626363616166626539363462396537373439
|
61306164623965346565306430343137313364613330386332313963623237313631326636346266
|
||||||
64336332623666356639303034623436343737643231376663623735623734616564643533343838
|
31616631376363323365303431653332343165613465346635623237636664396531333330613136
|
||||||
33396331356661363465656636346363343466646463353235393438346664376139313762376639
|
38316233333062653731333366376230373139336235313030346237366131346631396566323861
|
||||||
62303437356135623164656136303937383838623333643838373534313538636137396166616664
|
33386630393636316632633735623836663738393262346162656263343236633862363265616133
|
||||||
64613730656436383236373034633530373332396135353838353633636263333462616535323937
|
64653461646361643632626633326439376666386335343930363662323230656561303262336336
|
||||||
61363938393664323635356363643661313834306336363566333830623863633231613864643234
|
37653331623265666531636331333564376230316263386261656635633864323562633938633335
|
||||||
35333030656266346638643363633837663236653335616532353662363137313034343739393139
|
38623961336366366162333162646662323034366533396336653565323234336161336364633437
|
||||||
61613337373239346433663632353238383764363335646232663762666534393664663939303565
|
37383766313637346330393238353761346636643162373064336134626335623063393833393034
|
||||||
64393035346163316463656535393662386135303535303334336537303261613364613135303234
|
39626338333530643330663338613334396465623030653761336639663635363235663731333565
|
||||||
61623662373663306339396637313039626537313030626231356637306538373932346461656432
|
61303065643065636165623030376333643730643535363535623063643735316532363738383935
|
||||||
65373935353930386133663765656236326539623262663032646230626338303965346564333464
|
32623632396663313861303066343736666565303732643339343837343761376437316564646235
|
||||||
33376231616630643866613534653764623266356133363762376138663534353061366231643439
|
32313162616439373536393263613331393661613730646162353733666362643366373133353030
|
||||||
38333530396631386164313161656562613337656130343633316531343864636132383361633563
|
66326561353934333035363266646238313863343432316634336639646234363461333732633533
|
||||||
39396565623662623965303834646336656639663661666339333562636337643363336463633232
|
62313539353136303832663234333364663265393764333965326265653332613036383138303639
|
||||||
31346532616533333532626133393739326434666564623966343236353063343631366363366634
|
34366266646133363031623734663537616264643634653033633663646432333033343534666337
|
||||||
63616562633637346236623730643039666664646562363061313831363733393035393664383863
|
37373364663538383664643861313339376535356132646235393334396633316661353236303464
|
||||||
30663165306237303736373938396235373037656639333265383638383734653831323035393731
|
61303630303233396537343565626462613032333261326131376432396431396131346565633834
|
||||||
62626332396366386238313437363632323830376264666431663031646231643366316564303932
|
62666365333562303730636137366537616661313866353336303765633464353531633134333666
|
||||||
37626238383039336664646464616262323938323364363336373466303765306465303766646135
|
35333262646263323765623637383438653632663265353933393739656232633536633564313835
|
||||||
62373639366463653434333964636338353934363164663538613663653735383138353533336264
|
63383031656430363962363061643439383762373838386664326631613065393138623933623966
|
||||||
64636635313763616566663231393632336137306436386264303161336562366535666337353036
|
39616536386531366237656637626337623330393064346330643661613863376630613333393631
|
||||||
65666364376632373532326539333065303534333833306664623636633637663631623465343433
|
32393936353432323662636537346131383366396236303838366432336266626338353064393262
|
||||||
64346630653338396435353065303335333633393933616439643136346639653861383534353833
|
32663637336438303135336566646231656333376436353135316232393161343763666363303435
|
||||||
38333530383666333039303434353266626365643235626237346636393235653531333661333138
|
33346630396231376436343238393038653837613661663162623662386564336239666235366435
|
||||||
65653930653366393731396463653663666261303463303432356366326630356335323030616331
|
62383535356663616139336131636461623764306161316334633037616338323165626462323237
|
||||||
65383864346662386462656437386639306563663935313961663736346362316333623435613331
|
33333861313333646562306464613036313933363930666638393633663839346539353866346666
|
||||||
35613833666636316535656437626265326131663239316563646236656435636131653331633761
|
62633332386135393666313839343839303437656565636236373033666464396634303031363934
|
||||||
34393336643064383135623335393233393536663337356433396361666339636465623435366135
|
64336630323232396438366131306338643131346362326639666365326639653037346136613462
|
||||||
39633233383435616137333361386532303736666136326131653565306161343334383937373930
|
37386161313031333961323865363837323835326537373631663636306637373736613932303264
|
||||||
34373135613133656264643138353339663130383962383331643831333762363364643637623665
|
66616363376662373965373366643363666464383466666263336164663331646366663336393065
|
||||||
32663064616137643866323866666139323537343065336464373034336532356439643938663763
|
66396563666564303932323263653736653539376262653833326231616133353239323230326161
|
||||||
32336131356666666435353338633130636566343863636336363430346137306337656161313637
|
64303731303335353138343665326531393837373338346531336636366162333033666364356463
|
||||||
64353666303466333663383433616635636238653730623631636635633464623437666462396633
|
36373631336561363963666133396262643939643939326264333439376331386531326662323434
|
||||||
39646436373932653338383664393739396138303632323131646539656531363664623333386662
|
30636364636464633263386334666137356635336164343666356637643266613665666666383262
|
||||||
61626535663436393238383762383037376664313438643565353337633638313066353861663736
|
39363266643730333966333562353134303436666430363737373962376436643435373965626166
|
||||||
62363636366238346661306162333630643564373433623137366637343934616136633961396263
|
61366439636234616537376338373361373061353631626635346561306562663961616666363630
|
||||||
33373237353631333235366230353262383235653533316461396137393964386463303631393363
|
31393338396465363636336231633830373864373564643364346437363231393936633831383363
|
||||||
34653030623866653332363534626139626531386238313233623963646633316339613163666238
|
30666235363066616134383262386137613835343833386237356431373062666634663737323161
|
||||||
32616337633561666466306132646634396132643065343435643164386634396133393661383164
|
65326434303030373532353137633465396165303537396634306166653664663736303331626532
|
||||||
30646463313631643361646634633435383662353932653538633332313931383730383236613863
|
37633035346264323030353831373532336437626361636139663562636562643961373735343765
|
||||||
30666363373131386134663831626334323139386631386566613132323533623661656637663433
|
37363630323963393138353132396464313536356261613161323765363961306263336530356262
|
||||||
38353863616434363238643339386538383934313465316566306438633964663761363261316339
|
38386337306262323339353836363366326331666364313566643036633164636665633735323530
|
||||||
37323933633433613935383965383934366532363838663633303835346661386138323830383064
|
37306464313234393066666364653834633966333034613035346430663763656639656364356334
|
||||||
65333564643962346662643331363239383131306138633134386664663533633962373236656134
|
35303062616237343162633931303433313165386163383064643863396332316136623336366333
|
||||||
66336634386363343862633638373338343362663331323863393366363934373962306162623232
|
65386361386439616330666466653139356639623037356463626166623134373030333036306238
|
||||||
30643261333832643538663264336531346432663961326436393066373263663132336537303062
|
32646665333631356362356633386665313765303439633535346133656138363035393332313837
|
||||||
34393831353638343539623832326162393539363734633130336338393864343735346663666632
|
63343639393131303061313964346363653034633730653038643131383337383634303461323636
|
||||||
39323563343834643661343536396436366336663137333534343436353031353936366133623733
|
33366565336338613261336638316334303263333936616635336436323532633464383434333539
|
||||||
33663538333061356132383965663861626162366366633562653933633837386139363337633464
|
35336434333131353363626261343233363862326431343861333230306563393566653138623234
|
||||||
66323564363161316563616136666333623266396137343639356232623132383761323065666131
|
36396133353339356239373432346465646532313036666662396239383262646237333661326139
|
||||||
65623161656565383533316636353539343236393037386165646364336231626164393430363866
|
66353463346663323864386335643731383039356665353038633435643736333666623439336635
|
||||||
63396563393732353133633431313534333362323066346639303166666432323339336463303463
|
30373461353336663437386465313935393061663366323235326230396332653632663966373338
|
||||||
30353566303730653866376638636333313135373335393863656338356461336565613334633234
|
32363039303432666465656236666133393864393365633735323066623931326566303230616261
|
||||||
31306365363364343536373739653431396562656333393962636139656663386137396630313561
|
63353563323033306331633264366439313830393438353137613332346164646366343864323766
|
||||||
39666635383435386638363462333636363735636365343034653131333633313533333436383332
|
35656436356636373065356562353266353838623736323361386437613865343936303337373963
|
||||||
66666436373938323633303066323532326466326134363139646232313437383136306562363962
|
37613234366637356637633966363162373364353337616263633965383538623133313862623333
|
||||||
39636239336262323032306165376337643832306338616163663463333862613736366561363334
|
30366139343334383264316665653132346139386436353261616334356562323638313938386365
|
||||||
62353937346131333831343365386339326337336162383639303233643834396266363365363831
|
62353235356439343337613539313431366137343335623661363635313831663333633566636630
|
||||||
61336532323034323732356531643461376566623163363035613332333738326435623532376163
|
65333066303662623664363139303732646666396134663235623036623363343064393861303563
|
||||||
66633965393635393839316336633162646136623665343535353433326538373231323638323062
|
66633366323630326437396639306637366535313164363864386531306162366638373938663463
|
||||||
65363633363262353638653430653435363932366335616163663633323464346166616261623139
|
30643962613837373263363364613766373832366335653566353332373163343736633166663437
|
||||||
34356535333965303139613665626366303165653262383961633937666366306537363262346563
|
33393637613330393434636536353663303061366162333533653662313366353836353763633435
|
||||||
65383834326331663137633735393762363330303132353833396231666564343664363836643562
|
66666430373536353936313830333135333036316633663632336636376538313032666531616137
|
||||||
31313233306461356665393839626466373035333938646562353135623332633532313132373462
|
61376636343166393362656664333432323762386561323735323239346566613339646165343464
|
||||||
37373864616665613730633561306636303762336265393865353061653532353566326534646466
|
30383265616465333837616162333135373032333764663939663061613230366564396635613739
|
||||||
34323235646362313861303061376137353233363463383161663062373739616365396230306633
|
64356461346630313731386230333032323639343463613431376338616139383435326534366433
|
||||||
30373638326639336365346434396564346435383739323066623730643162663562306263323664
|
35653463336265353135376165353666363133613264623066393464646361613662373131633762
|
||||||
64653365623665653530626631623330303832383562393336663234316432623436346334356532
|
30313866396636306135643662323430343335396266333966666535643536316232386334653639
|
||||||
66396261343534356161363139613932303466313762663164313634666631343833633034316632
|
65653261303162653136323762613938356139656166346230376664643432323232636338626632
|
||||||
33373637343938636538613063643965366234613832313839383762643962366539323435613332
|
39393964653734376634303362663566653963623035656535356665363661653565633938616363
|
||||||
63363865353531316361303965323763653837373962373734353333363264323832366132333738
|
37383838343661643963306662623264646465336435336665343435373064666631396438633866
|
||||||
34396230323361636632626139396336313238376235633163396463613739306138653565363466
|
31666239623263623839363162353561333461366363626630303166633935613662316237666131
|
||||||
37356664323534393065643866396438646163383836653832333938366362646636303565353135
|
31356663353163656138663463653534343930653037353236643862633535396233643164393839
|
||||||
33356463643330393564653464633539636462333337353164373937336461396565656633636235
|
66333538643430353030366262303632363662363730666464316666656336323930383361623830
|
||||||
32653164313837386338363165303166303161623638323438623839333661343361366434313063
|
32333561633762656336383230386636643033663364343536346665666362343764666237363537
|
||||||
33623061306466623263363230353163613531303138663233316163336531303933303635316361
|
36363238383633346661356364383763323338613964626566386236333432653537353661393837
|
||||||
38666331303663656263353134366663306162393037373430333965393731343862653431623734
|
65306265633936363866643963363238633765306338396262383634383564386531643130623039
|
||||||
65303562653934326262323762343232356463333432636633363439353038343939356234353039
|
39613939626565336362633962633362383165333463383866323536333232393337393066363933
|
||||||
31663630636634646635313138376130353864323430356539333166323263663533643666656538
|
37636230303433366638363238356564346135376434646238663334353830383065643537613865
|
||||||
62343232316466303136313139653332346437356564316163326438383636356530386665653834
|
63303264373865383033343563303335303261613866346238303838646639663964313431623364
|
||||||
35646634343433643331613030356538326633623263663638393535356166326262636262393539
|
36333631323066643439643532373631366362626465643861613930343966613239313031626333
|
||||||
62353235313362356134663238313739633737653365613064656162326463383533303065663266
|
32613132636337666336396537333832653638623161613066653864366463646564346635323837
|
||||||
64616666383261613731343832313331626237323234356564613030616463393533336630326435
|
37623433633462396433643635613938346338306465616234643031346634363039643361396431
|
||||||
62666535666539333730306561323737636530366530633539356232383865336233373330313962
|
61386532643030303661646638623536636564343636383931656337633730316666663536356461
|
||||||
34613432396539613533306435366439633063666265363136353130636661303763373161663131
|
38626337333430386361383239636636383365396338333561306264383136643866623063643138
|
||||||
32613539613631613765623465353962633339353538373733663963336334646363376165376164
|
64383233623062633262616531373734636164636662623038383630613364386264663064366263
|
||||||
34616435303431386239666132323631633833373234366265316632303764303561626636393836
|
63623063383938646635663764373763356236306463363961616564323564356363626334353333
|
||||||
39303863363833343136646539343137343833633862343137613336333031623033343330303666
|
38313831326232323861636533356233613831653731303636386132653866623337323963653239
|
||||||
37393361613938633165646135356536326133396437663232666230636665663036613737393632
|
63653265323939343434343262363462666363373061303065653435663230333465393366356163
|
||||||
36633865633933393530333730633434613732653264383433623637346637313362353039376232
|
61343963643937613265353963393362323532303661626663653136396463616238643565616336
|
||||||
38373338333437383165613330623231653061333332303038383565613530383333356632353730
|
31313562653335646261363730323862613236386633383735316261373661653130613164336336
|
||||||
38333936653338396533333863623361326362373863353837663536616235306365383638343737
|
64666234383639366432646533613039343838356333333734633533336161346631336365646231
|
||||||
64326334373935646632646366653237616138613139326565643332616466633166626139386637
|
37316630396139336634343962356464616234666137366437393564386135383836633964363933
|
||||||
33643136396137343232323762336162303132343432376638383739663763346134633639346662
|
63653261633366643262383636366162653563356533633539306139633339376564346136316138
|
||||||
34323533643835363065313965376631663332663236633461356239303263663261666332383736
|
38623038316638663838666630303263353831643532333435663034613033373437356435623438
|
||||||
66353066666364303035623462666233663134666665333763346466383138386636306334373632
|
65356435356561343533663866363632643930363832313665353937653237343661383938363262
|
||||||
38633963623263333439386430323933623861326237323137343338336230313863386661613035
|
38353534653030306263343164363634636639326163323834623465323337303832623235363565
|
||||||
37373133663430626331343037623363663365366639613936666631323366666332326231666235
|
61333739363139366364366663363661366164623436613937633461336166333265653935353165
|
||||||
39613837393363316530316164383832373565386132393932313539356630363638623834623333
|
37306264316162623433636162636238643939663836323966663532333332303263643934343131
|
||||||
65306638396164666630636333373664383735396561363339666464356232316135346261613535
|
64316334663933666163366130393762616331643438323266376663396434626131616237303034
|
||||||
66393330646634383633643737323336666534356264383838313466363834376236306139386565
|
61393038616361353161663435653665666530313236646230303963303533333132623534353639
|
||||||
65636634643334616161323666393730616436363062383263393037663966623432643665393130
|
31636636613737666366633637326532386461633061393538323334663638313134383237366135
|
||||||
30313564333061663438626633653239396139636162313238343963326365366365353934643635
|
38306530336562346362613337653539303831386436656535313430656566303236636365643231
|
||||||
61613637613032386666303162343465616364323232363637323036653435623963313530343964
|
31613938303038626630663631313437303865643634386463313238393466623464316635396138
|
||||||
64653664643932333434376530616434306233633462333765663731383739373461643234313134
|
63393231393464323864306563303739643438386432646337366664383763396562626139613135
|
||||||
65383839343034313833636136393037386464663063643864363634383863323864653733663235
|
35343533383932633233313435653238323932623233326234653366636234333166643730353538
|
||||||
33653961626535646430303763326364666161386561363734636633363831396665306332363061
|
66613061326364623138383435373335373531303563303538373063663065366263633435396366
|
||||||
62373161653564313432343230316538663735383265393332303838633939376165613264613131
|
35616562393434613036623235386637386462646437393530643637323964633536663135663036
|
||||||
37643063333564303361656163396131323737616266653062626665656635666637353132383333
|
31653933353865333632616333613738626564626165306637383164616133666166663862326232
|
||||||
61393830616462353232373863343439393532396461366566393739643661306135646263663064
|
62643033343339396462356231356333386564633330616437666535356233303339393432346535
|
||||||
32613830336261383765386131633938623639376662336431663562333934343133373666353231
|
37623230626337656263396566663461393766663431633638383863653966363037653762313364
|
||||||
38663534353036353065343565666234353431383236636536643762653136303063663064396237
|
64343365373363643533653339623138363965623331636566316238613038303735633535393262
|
||||||
66616433346639346339396638313161356361396366623831313137396464386363323335663662
|
65323361643335333839653961356130373632656236633333633166353832383934306366633862
|
||||||
33393266303064303966663033393738666531373439633535643131366333353661323761396134
|
63623537353433623363373561656233336462616665353631613864343639613939633930303863
|
||||||
63366238633739313437663766323866666539303066346363646633303563366664353134326136
|
66333632613031653836326636366661323331316639643962633862336632356237366161616166
|
||||||
32313363643734396464643336616430363833326334313664666231643261313666383138343633
|
36313536343763316365386163373363653038356561343064396265353331376530636132653530
|
||||||
32383262373131373766616134303438313061353465323563363731666565643237333233343236
|
34343966386161323361663765376534316162313530396231393039313238353465613138376632
|
||||||
63353838663630666136646661376161653161303261303736333938663232633563323966663461
|
32303832613936396262636166396432366335336133303063353332363736663163663430383632
|
||||||
35363265373731613332666130306637626338363861643630373966623132323133656139636136
|
38616230393230646133656564643334633536303966636439653434393262616237643263323065
|
||||||
61376439656231663765643965356538336331663230373833313464356333646262373631396231
|
63666261353432346339323232343762363630333036613230333133356339363833303432643531
|
||||||
35326635346635633561376465386534666461346437336435323361353562343838623234623461
|
35616636363333613537666265343136343039393366646437666434336634666162623462303163
|
||||||
65316666383334383730343636613937616633303663393866663566343633646162326434376466
|
65626532376332663733613166396538333438633139656666333062643237393038373636326661
|
||||||
36396532663332393232616131333639356639333866336137623931306334623638626231613265
|
63373531376166323830653763663934366265306432333031633830313130336332666265613565
|
||||||
36653435366162373336376538313631633831623735383863663461636535633037643338393035
|
61626264336232643165366639393066653661323030666562383664336234346631393331363932
|
||||||
65396534356632653661636462616631316631316536373466343338643564633363646165613433
|
63306430363536646665333463336336313666656461636633336464623836376133613461633937
|
||||||
35373563326339306633376335636662663836306335373237613939653738653464613964663361
|
33643932316336316236656435373233633833336534316166656464396539376531633638373230
|
||||||
65626166666230316166623730363761623233393738646163356538303261653631336637386138
|
64633165336665643836353432343032323662326265343530343335626535313162343963303062
|
||||||
66623163653963386565626532616266353863653831356434313266643366346230343635346231
|
37333431313435363338396433623533663561663134616134333333373634383736303938316538
|
||||||
37376637343561316639356335643730653464313163646536333332376661333666376336386534
|
65646530366433383331613630383763633936356534643731663963363537333238666536643236
|
||||||
62326435386230326134623766633534306464643734396662633539633165393336656461383838
|
32613139383935393563623561396366356334643731636532376334643063333231363138393065
|
||||||
33613337623830333364313131313463303434646634363831346239663031306661663966646333
|
61373564326538306534613637316663393465633966386638336436346261616264336536373833
|
||||||
36336364326164333963653835623330366638633431653934393965626435383334386564333335
|
36653265303132613164633333326138613135346461336439653736653161313230633238303239
|
||||||
66313732613938343733386563613334613466343936346638656533343864633438356331663638
|
64386135326264663833313236353533366137306239636237306239393032366663316466666164
|
||||||
38623838346437613436303834393334616134316633336465343137376534313962316330366538
|
64616535643065313636623464643137383062326564333166646639616432353033393264303462
|
||||||
63366632666262666131323566663034383334313039313535666337383265316438623234343732
|
37613138663736366531393734353263653532633333386433363339326663336435646333393430
|
||||||
31303861343230333661656330636335303038313930343233623732313831613463343435373730
|
36356137323236326636656361306535323134636530623665626662613531623533653333336436
|
||||||
31386230373239626238343733663835333930366330663431623237366162323663323434333430
|
31346234313663333539353434643837333336353033663733623234656233326331306164653037
|
||||||
65366539303332386139343631306532376435326530306537663330336236353063656132366637
|
36666532613231643739363037303438653861346236663339323466346536326137666235323463
|
||||||
35636666356662373665326235663061633632396530636638303131376334386539613462303265
|
38366133316263616337363866336238396161383063616234376439653830343364613734353566
|
||||||
31626561623034323961643666313839303365396237323834383561376134343035323634613966
|
66653336636563626535626664373833386530323631323436353264386531633062633763343164
|
||||||
37326237626139316634613233353336663465326332346238643865393238323462373438323263
|
36343031303634386234373039326233346532643163623864653164323862633432323239633930
|
||||||
30663538366633313263666535623631613434303063313062373265376339353532343334323064
|
35623530623336323066653634396535333732633234306362353235623233343931303233616237
|
||||||
31663734323439633138366132313335373262323864383839613433633631346632313765306262
|
63366362353634633061646433373132613963303364616563663661303539616265646365613432
|
||||||
39366564646232623536383262386439626364336133353033396537363433616435383864646135
|
30363734353634353634373933316237306665633830366566313635356132356634323865623561
|
||||||
61613165646139323531386566333338653564393539353838316465303033353361316337656232
|
34396234393966623635666165663531383938333530313332346364303530323765663963633537
|
||||||
64383936613131356238366165653464373765316162386338363839366337653733376164666365
|
37393234636233666136326433646531383565666161323338626130323932633664346332323636
|
||||||
37323336313962356564646539646533623532646365336264623265326363643638353330373264
|
32313534363039666637616462656562363963393938643836363865376432333539323863646338
|
||||||
34613665623265313734633065343935663864306339366639323631373139316634383435633531
|
66306662363830656339623139366534393961313461376535343738623239393733363066306534
|
||||||
35613163303935386665666161316463386237373135333539383632306239386134663061633037
|
38353130303538346635616161306630386636363636663966326436343238313862383131366564
|
||||||
62626334643836326561313834333534396232666339643830326361373661383262323762626433
|
63363166373666643534333439386364383436356265633038303364656135323937656430653361
|
||||||
62343765313630306134376361313230653530626638356338636231336432396239393038303830
|
63393965626261623836613535653266313364643064356135666436656234303961376333303332
|
||||||
61653463656537373431343530363630353731653432373031326136613732616162333236343165
|
38353430643964663731633165613733646532653838646365383834386233376136393365373830
|
||||||
38343534343262633030356462393032313534613333366161636538643837323961396262376565
|
66326665373738653166396166353333386337356362623138313533343432626539663161623032
|
||||||
33313465646662313262393266346561653635303763396161653232383866333533646630343366
|
62623230306236653732626563393539363564373765376131363339383735386436383165616462
|
||||||
30366234363137303737373139363437333464346464646533646662396366393665303063316531
|
39313462306331626461303433663438326630326332633834393463393765666438356165373832
|
||||||
36326231333734643864616632646666613233623761666439323832336663626231376638313662
|
63366530623964616438313932643633613661653433663433346133656232326634343933323734
|
||||||
31306361613433623661366662643838393435633061653933343331346663353461366338393036
|
31383331353065313764376161323163393862656432303861353834336336623731623435343631
|
||||||
66353866326165356361313130336136623135663539633461626237333435666530663338383666
|
36373139346138363864613935326464353236346434393230623932656164336236356130363839
|
||||||
35316232333365663230346165383462323963373536336235363439613633353638623662663038
|
66656262366432616130613066393339356131653166323232356539306638346335303430376235
|
||||||
35303761373738656335353735366462386266343763383861323931643961626336343334343433
|
38336464633038336430656362626632396364333236666232343563633263346431613466396538
|
||||||
34386436323834656263633634346433353461646636353866666135643530333139303336363637
|
36663538633961366236626239303933363538353261303464333764626634333531353765356239
|
||||||
65393433653932626131353037613333643366353536643733323063383039393335373034623162
|
65663565646462393263613738326431366364336231383664613538336166323437653937383834
|
||||||
35366464366631643439616562316162646632373037343138646264326433656164336334663730
|
64666438646237336366373662626365316665346365326237376630393565643030316430653661
|
||||||
66616432366533336466333031663331373731303438616538643633653866303939616138323063
|
32363061636662383539396137333835356231313062343239343136396162643939303563353238
|
||||||
33633331633634633965303566346665333263343563396235356637313532353930626239313634
|
64653362303136653666316164343137633539333564346662653566333636626262386532393334
|
||||||
61383633383962653564333364313235646662663261303934343538643836623435366632363832
|
32653265633134373365663732663736306437623336336362313631353463633461363034623063
|
||||||
35613665623139353666643063373733633932316661643731316233643133313262623532376237
|
33643165363665336362613466323531656635343866393166366130353935306533346631333335
|
||||||
62383532353638303861623966663136343635336262636336626331623238393562343934343435
|
63396237616362336662346462333466373939343965366564323534666263303862643562346638
|
||||||
65373162633931663139323138306333336661613661303130373530386333666230316565316166
|
35626563306363383232653430356666353762396133303832326464393030366139353735343236
|
||||||
37646562326461303731663362323434646538303339343565633236633834313033643538386332
|
37343637353565636231313934336364333831353962386433643838336431333133626265613339
|
||||||
32623833383765353039303432633162623764326637643661626136656330613737343938613732
|
38333862663239363661346561323837636638356464623734313966396262313337303639363064
|
||||||
34653930383066636232643235353039616431626433646563323833303335616339386238326332
|
34356337316265386638656361396662306535383038363665366135393732613331636339326632
|
||||||
37366462313832333163613064643433373830643833336232646466313739396265303861373664
|
63303435343935613036653563663065623962303732373864666333623164386338316138663937
|
||||||
63633138623465373236393137363834323962346231376139343464303962613630643865303931
|
35633161376636363062643234663130616130633732336335646332663861356264336663383336
|
||||||
64393038386461353863646635326632343730646666373838356130393265336431316666323363
|
39316338623936643532376632313031656438653034336464343566323365643161373462343066
|
||||||
30363861333235666335313736626231366431353430336133613938353936386163616231383932
|
62623632326539363266636362343639653538633138646165666633343663363131313835613164
|
||||||
38326264656431366561663564326437393638656333613933303536313061363833396433383538
|
36613631643934616235636430356235623338346434323363663465616232613333636332363462
|
||||||
65646166643630306338626636356363373332343764396335653736383062623262326630376236
|
38373831653738383734366139656164613734323331653131343466623431373334376264393335
|
||||||
38336165343265363466373265333531316161323061323239363732356130316662633134333566
|
63616239363233316433626561323738363537313838313861656262633466643034636665313566
|
||||||
34373932346535393862646633353832306263623535313631653631373065326662383136616631
|
35333439376632623238633538633634383330356364323835646335623965336538356633666338
|
||||||
64643736313138393330313534643065343030643965383831383265653536303433336437663164
|
64383539323066666534316230396437313662376633646365663364633664636232336539323335
|
||||||
30343763326231356563343165653461313861366238636364616664313137333236376435636234
|
31306663643062343463363030323030383063303562333332373965646161353732383238636530
|
||||||
39616431626163653461376336613031626566373564633763393630646231363562376464396238
|
66376138363234623664306239373962303939643732343430633033333830323362353763303030
|
||||||
39373730316634366462616538626264326338366636393364636238633439353035336234333463
|
65316431616637653834333561313762386538306635613330343234303533643237646630313266
|
||||||
65646466336330663239303037626436343064316466393231306535376363623266343564666533
|
37656333393836346535396461306365663137393537663133396335666464316334656339343037
|
||||||
37316537666662633064646530346665653764306430316535363261636561666134663633613230
|
64346564326537336232646334383163363630333062316265356562346233366636376435313637
|
||||||
35616230613961616461633535383238656165653033336636363637303031653136666638656239
|
65366566626539643232616534333562313535366365653661616666306465326338336138346139
|
||||||
37663865656437646364346561323563346237393866323638616365373630633733616537336662
|
30313035393964333439666239353265396261326665326432646237323362393964656132636639
|
||||||
62353534636430356135343539633837633663316633343065396432393266373364613938353337
|
34383563346562373665323134333933353665303962313230326431303932653537363862326562
|
||||||
36633539646235376539613030653837396264356132346465303734346232306363326462623436
|
65613636396362303530316131643237373766333333353964356462376466326635646232356331
|
||||||
37623539356331343636613631396539623965663937326534666362336265643539326133613464
|
33373261306237646662373032353165313064373236363631366135303765626330336432663935
|
||||||
36383435306331303662333132663438623733623861313136656261646264666265303937663061
|
63366231646264376264306234316531643231636261353265373963363834626230386265636437
|
||||||
39303332396135363737373430316636376263663961643265363735333436623537326437633636
|
39656266363532663535363938343264306535653761306539623639386261613237313035366166
|
||||||
39616664636635623037653835323732323034313566336432626361313366383563613130643566
|
62663036363837313064353763393566383030323038336237343332643563363731623738656437
|
||||||
61333831633162333339313466373263666239326565373231653830663637663637396534363939
|
34633731663134373732363266336462383964653066366466653939633237666632303739623633
|
||||||
33303833303065366337353164343331313266643930303533666630383462316432643038653335
|
30616536303865623436663262646563393766613231363539303636613331643937633463623566
|
||||||
33343965366463373565613233373038316261323232663037613363313164303066396239316161
|
32353266616461613832356264323038363035633338356332646136653432326236323261373232
|
||||||
62643035666361633637623833333332623166396437323530333937636433313031656537343864
|
63393230316531376330656661323966313238623439326335386462323764356365623861643633
|
||||||
38316230626565643434653261656664653934656233623265376331336139313739313765623731
|
33323838393038373736646536643130363362646438323662306138626363633239356565633239
|
||||||
36656633323139626630383562666632356432303236336237643131393062306438373465623337
|
61613862393634376635303637663639316130323066633238336439386461333261333766323661
|
||||||
61373966643930313130306466393438343432303233646364646531376466626665623463373763
|
33323964646138623966653035353638373061356335613038643532303032333365383237376232
|
||||||
32323838343964346637616538346534643936376433643035353939323063666161613561363564
|
38306136346637643863666531643933653465353862666365653138653739613039623833343066
|
||||||
33316338383065313866336538666636396133356362613639356561393032393734623839336263
|
30643039666366366361633163386430666330656635313833363662366463323832343037643764
|
||||||
33316636333462363066353030396332393839643035303133356364393737383636663835386335
|
35646131653365373933396161393739313064383931623864393235663061333063636539626238
|
||||||
30663235323039633739333563393234333938663361383233666338616662373265653437386534
|
61316238633564376237633236343137333538393834316432616232393362613033303033653766
|
||||||
64653864343535653863663434666261373361653636323664313333393361383133373732613630
|
38386535303237633563373431363538643834613034366133373063363838623663303166333834
|
||||||
62343438306536303265653763623961656332313432303939613734663961653866303139303031
|
33323061393761346239666466396661613731616538623665383961363265396661633866313961
|
||||||
64323464353534616639653932623665313133366538353438356339656366613039653338656661
|
30333332386638363738326538643231323834623737356566333436386561633831373964333766
|
||||||
65383933663136646263366434353364303933373537326565616234616339623762333066616236
|
33396335306133626662346333363036623430633765373437383231356330353138646262396361
|
||||||
34326535653835643033333466326132353263616464343465343637386136303664306339353765
|
33366136353466313563633430303635663662343361643061663838303232383436626236666261
|
||||||
62656533396439356533303434333833643333343030643762623232326637393131616262373130
|
32336661613134333962373938386462653264366332633238633636666433383466663064336135
|
||||||
34333537366163326136373639643038636438326662393636653034653139326161633630353134
|
36633864326564313231303831333934376635363363303866306335386463613964393933633665
|
||||||
30663463313065336163646162353161336663333736636266666139373462353962343531373761
|
37633035333638643864616264626136363439353065643039323661663562343034323037656239
|
||||||
30396337393866383430656266626562346431353034613439333437643265393734653263356266
|
38613061303432313436646566393761396566383739633730623530336165636632336234666638
|
||||||
62306233656334623365653539646632636661363935656161393364306338626363323066376134
|
66303537353931393866333937376636383637343836356236366439383539656665623036346262
|
||||||
39633562336366643837303434356262613666353632373238346635646436313939306333326135
|
34353165393133613463636332613663623532366636396661366364333034303539353762643063
|
||||||
32623561343434663130396538636161663033643064396361663665616638323763316564343666
|
64616538356335386533636435366330396366326433373731656434343065633765333333363366
|
||||||
38623230623265343062373764336438633637383030633635633036613235316137316232636137
|
39343934633932356338326365626263613163323161386665333432626232636539656664393666
|
||||||
61646364636330373165643165353061363232313731643634643663333033393339616131666531
|
62303536373566656239373237366432323862363634623266323633626631366466333038326666
|
||||||
64383264383336323837366661623035356536343535336439323665376330383535613230393634
|
31666262636434343136633166616232343761306132373336343032313831376164363461643937
|
||||||
64343733313434333164393031336132323439343138633136333231643831303062666564313638
|
64653935363561663663616630653133343533653037383638336363393264636432366434623035
|
||||||
32323964303030386634623933643939666633633365303237396635663433333434636361666331
|
36313731363532626437366265653434396330353861356662343635353231333530333233386337
|
||||||
63396438653134636330323936383132393565646465343935643862636230643563623462623632
|
62363834393765383635613939613866383234333663646666333835363361646261336563313965
|
||||||
31393736653963366564396463613233656663346336373737663835303332613765633233633766
|
35623737363531333464666333393431356563653530306462383733353531613461333136306638
|
||||||
39643430393134613737303261623235643439626535643937633535616535383737646564653863
|
61363436626432373136303633326635626137393939623866663339306166663035633536376362
|
||||||
31663939383466353664386566303265343264613531373232636363393630303138313139613362
|
35663662343836383562636266313737613838336132616635653832363334306130313230643837
|
||||||
32386264323134343737376464316364366165343664373362663633356137396465396337333038
|
62643735373932323733333963343639313661383338646435326162353664313132336163303339
|
||||||
38626235323535616336653030613831656530653037363264633632386163303132366237393938
|
66663633643133333831393138316466343639613430623034383339333433636130373832313731
|
||||||
64656638333834373862306536313330666238353134353162323437396462356532393964643035
|
38616333393333383336656565653537343537396234366439663961333464396134376333613666
|
||||||
62666461333065373365346263353539666232616530343432303433663333613332313264313166
|
34313261636230653738656534623734653339393164653765393961363233343438393430633061
|
||||||
39653766343137323133333237313131303165623462363231383337363432653634653138333536
|
61353532623834613834613664353036316562363766396639626238633964653530616563626334
|
||||||
61623164373935666634346334613831326633346137306136326164623061386464623066396332
|
33616166616361646264623066373635323535383764663536663635366338353263366530613833
|
||||||
66666434623465346662366437623934663835363162666331653463373236656562306564616533
|
64333262316466336564313432396634376431303931373562636461663831616131386139376134
|
||||||
61313761386638313838376561383166373639633065353834653564633735623332613330366533
|
61306161383538383538666138633131343464333136623832616663623734343134663133633234
|
||||||
33333932353536646338343731666233633664356330666230633533623263666334383333313461
|
31373033613462643036396236376165366636373935353139376563613833393232336666306335
|
||||||
64633139636231353331643436333531346433633931373139653339366330316639653239373531
|
30343632316137326333383933396562346235363061366331306562643464633836613833386332
|
||||||
30396534646562396136313366343663393136333630396631616137373930663436363535373735
|
38613761653333633835383764326362323763363035646334326336646164653739393964356636
|
||||||
36656365323263396237373833343230356533663166666164623161326531633231323834343032
|
36303763343366646134326333646133343836386561323132306138636365386164613638326432
|
||||||
63343531616430343738323337613266666635303832396465343330623162643963623364643236
|
61333663356365316162663330613330316161663131633035353765323739613761643239383933
|
||||||
64623266386336373137373265303932303235653566373133613439343438323366303432633130
|
35353034326362343137643535313837313566336165336464336565363362343737326539373037
|
||||||
34383032373235633236313035653031376266326630666132366230363037383366316531626337
|
39316562643664353063363262333130653564353764316562346230363531616536323638376664
|
||||||
37623239373665306132643433353337376334313430323236393938396366613566313066643236
|
35393462643730303435316661636236313037303939663530313964346563336536303530666163
|
||||||
37326539373462643634633437386131376636333163623837623564623962626363356466306463
|
64636266663931306339353563366665643963386265656134363061343262366435383331643063
|
||||||
39643366386230326136373361626564636237356436363030616330636662366630363832313239
|
65373965393565613632623239343766663332633239653065353961333935303863326138633537
|
||||||
34633337656435613835363165383662313661323532343966643331353236646632653964643066
|
39373331316564393537356462356636333861373232663738376332646435343231636265326264
|
||||||
30396139626338336565303132626632336533303637626633313732373734613837333265663833
|
37333365356261386432396463353362616266383831353338303936663466376361613337363530
|
||||||
66643766623764616164386463353861373536633537353837303761366134613934343232636262
|
30346463653564623664663736316134303133316235643937306165313033663461643633383762
|
||||||
38613337613731643235643565343338346537633635323236653234663232376462616534323236
|
35353032653239353834663334313239376135353936663862303863336262666635663139666463
|
||||||
32303639386438346465353965613035666332393138343936383861333261303763386465643134
|
62313139626161636330643364333130343836373530383033353063643430616666326337353136
|
||||||
61346231356436396366653338356439386366633833616435386236653461396434396337663638
|
31646237353631336131356430386565346630313065666532656464666466326535376138333236
|
||||||
37623632343931626362636238343138346564343234313636643432653136316564656235383265
|
32626261303339323237393031333037656562633435376661393537643337633565643766363634
|
||||||
30356262663832313733633130663430323139323062346638303136373733313938616130333665
|
65363931353163333333343465323439383664666662633666636661393938656336356330666362
|
||||||
34373732363963356230343535373661383137643566323033376163323233653162666566656431
|
33393562393366376232636466643137356438396337366138646330393965646336316563373038
|
||||||
34366461363463353761333937613732616465646431356266653834613332373332326237343864
|
34376634633365393866346362363136353733343366613430316638643861396432323161313538
|
||||||
63313264383639323037653032336266316639343233333334636330333364383263313263373839
|
30616134303631313338373766393265663432623430383439386431336661643133383938373066
|
||||||
66366533613664613235613266346663373965636236373539643134636664396465303339306132
|
64346238303835313736656139656161313434653935386233313435656430653265333238663830
|
||||||
39386137366537383839343730636237646436333333643732356561613839383132363130366263
|
66303037643861336136356533663730653131353130373463393761383636316332396230633932
|
||||||
65353338653436373531633562386435313936643737663938366462333063643537646138353362
|
32643036633562306436643738633433356632636634663530396265616564323934333138316439
|
||||||
31313963656333663462643336373639613635363763623430613232383566336663346565386462
|
63353763653130643536636164323361333734363735323132373437656330653463343062306137
|
||||||
61323166366464643366663937383539336532333939323165656434636163326137303836303534
|
31333731373966393633363564643133343164373662633638313266363535353165383663666536
|
||||||
66343866386430653230626163373535356534653338336663336435356535656634313537643731
|
31373361626337333163353564653339356231646237363637663831363031326330313038363030
|
||||||
37316530666432353462343734666365396133363130646430346233643138366139623063643530
|
61633337366236646136323461336335373135663630623438326432343331666332386132333635
|
||||||
65613962383162386136666232366433383239386161616430633735316366643862396239313265
|
30313138303538333363363261613532633763313938373266353462653435663330633962396162
|
||||||
35636232363466616631656663303335616434343537323262616131373632356134313963363637
|
30356665313238646163343464613335626336663934343139363862343739353735323134643761
|
||||||
35353364623062633734336465363133646364353062656332386165306263626133373432653763
|
32383031396130376135333138623232336430303162316534316162303638663037316462323530
|
||||||
31626166633533643961623836633039396437313433336162623138326365356134393835323062
|
39633432646263663463646465623636393831366533313030653663383439363739653834396633
|
||||||
30383334346434653932306366623866323637393430613739316234343463323438346431303939
|
65346139336137343032623238343933396464323233356465383762363732306632363230633231
|
||||||
36383333336439343936343638353761303934653338623331626132316137666336666131626261
|
32316633316433626630323965313436336265376361393435636339666436323136633730323933
|
||||||
39353531663439386162663430316639623038383830333065616365636135616239613162306564
|
63613639383634663931656239656233643663623366326130653735343964316632303238326365
|
||||||
61626431633634646135313664396363353639646433366136356161636663653836666665316434
|
33353730313730316433613436346165376539316662643430633734666364346262613161303862
|
||||||
65323137613439313931656434356664346636333833393038326134626562396538376263363237
|
31653133363832383535653833383637343730313135623239363564333430626463616531356364
|
||||||
62633538666361393463613136613063656362366565393038386337383835353835383134623438
|
39366432356263363533653630326465633963383965366433623737333338636636633839353232
|
||||||
33356234613833323862613766373461653362633932376562343733633963646362353635663533
|
37653836373264323235303239366139343361303265616633323761353636646561646564333334
|
||||||
36366662616231613030343339613166323262323730323862663131643331316539313932623366
|
36633738393264363135626261326230383539313036323033306533313933623361393461313464
|
||||||
30366662633266396365323666653064343436623430613765393430633139646533353439373866
|
66373733333832373631366462353863633535303839333036356139353235613561646462666462
|
||||||
33376137383630383263376565393739363832386133373765323738303439313936343333333162
|
35393336323732313236613164373936373762336535303265316633333031636563393631326136
|
||||||
35313162616539333531323732306434373566383863623563653339363431303361613864616166
|
31393761383730303734356231656236666539643965353362313837386232323838333233613161
|
||||||
63386635636330343064363632373432333832373962666231386438396330643235636366386230
|
65383234323363633162636239643639326366336531383431303336366536626433303663326339
|
||||||
65313535393162316432306435616330633065323564633038663666363032376463356163393432
|
32326530393930393065663331666633373666343762333935373732316236346332316365633763
|
||||||
66333839373233303538363263656433336231623166623561383364353862613564393364633533
|
66653931333932643365356332633864313362343961663935373764616437623730333034613134
|
||||||
34343934656265323736396162643337643064643835616439663031636231346635623738396130
|
30353036363331623936336261376261303637306133353734646238666239323364383231663532
|
||||||
34323261396538643936633435643436363836653062393231616665326130626435326431353662
|
38346130303062366465303336333462636631306436646233343033336465343438376264336435
|
||||||
65323564626130646562343364373637346539313336393037343466343664303938346435363330
|
30616264663362323937633738646262303631313865343932643563353736623138343530336531
|
||||||
32323861393063613435376664386566383836653866313663336436353162613730346331353436
|
30386439323532336164396162613363346137333766343366316437346537636134666537386462
|
||||||
36353761313837306131323963363566393730393739343830336338316362376432366665393931
|
31323864333332326664343966653638313062613030376636366632643837373034333265383439
|
||||||
32373466363661376238626163383530613265643638313163353733623062653262303036366537
|
36303230383362653632386362396230623033356266616433663036326565366530396364393636
|
||||||
66643164653131623038666565616431613564643837326630653232313030656666366462353238
|
65626437653564313238386631656639633136366533373964656264666264663162633336306436
|
||||||
65393534386536343533373365323761306334383433353336313665306336373365363962313961
|
33653065363738343430333037393162386635306134343762376337303336386232393563616164
|
||||||
33303963633831356563366632613064356666623966303431613732323764386164633834633039
|
37646332663366333233386437383461363265323161613536396636313438303665656666646136
|
||||||
30616463633934366364346130383961393631306335393438323265613966653038386366643734
|
62396535303563613239303466363966653265613031383464633063646363396262616233303161
|
||||||
32303337653734306131353063376262623463353664623533666239663461336537666136646637
|
30323065363132333662653037306637343361343064643363663739313130373932383863313836
|
||||||
38653030393836313162323731323363656637656235393763303531623565353538316536306337
|
65373838616630333237306534663935386135653635363966666235396265656230623432313432
|
||||||
31393661393365343963643563633636303162303938656430633038373837316630316266303066
|
32356537376364653830373635346536366238333132656638613539383064666166386464653966
|
||||||
36373962376363623463646566363035383430666332323136366463353930626165383435623761
|
30316563323937393938653365326137626432623234343061356130306562613761313333626337
|
||||||
65383964303166313064336266383932663365656166663731333332353262323565643439616535
|
62333265336330653262303664396265363339636435393038313636363630653232346336343636
|
||||||
30303765663862643966643764333434356537353630396631303534303930656661363637343537
|
32343463336464396463383266613230333335653435656365623632343731376364306661396264
|
||||||
34343433633839396335323038383531393335633863323834653037313863666430396335353664
|
63333332396465326662313936336163646234646231306137616163623766336130323336386466
|
||||||
35653132343066343763623465396264666135393436646564306435646130626134313563646236
|
62313161356331373561643439643137306632396433333761353134343534383734356534376564
|
||||||
32393631626239643463303933646163633836633266303966633963386562623733666566343764
|
65646462616163653662306637326362666366633563313965386134373761633962666133373361
|
||||||
39633836633966626536643436353432366434656231363237613830653939336464336561643262
|
33303831336335663163363962383765346331393533336361393433323065393263373432643731
|
||||||
64306663613231313461666466626136613034633664656231633839333439363935626638373138
|
63326639346538646363306233306633666333366531343835376636303030313666363733343033
|
||||||
35336337326665313438313065663836623561316664333461613766636138656232366635363431
|
31306335633337303733343065333732366136393566336430626236323931313461643636316338
|
||||||
34373134646530636236653433643937663333323631323634646137616639386662633034373230
|
39636166313663376330623139313662346535356263396135376433343463386565653436666137
|
||||||
32623232613865616530323061356135353537613763613638626265333632396364366539333839
|
35373761386364323631336434613132343065623635303939666561393866643935616264313363
|
||||||
62616132303539633233326261393162663335363164386231663038656232616266383434633436
|
34633230313762356333373836346165643133646564393435383566396135633066393664306235
|
||||||
35303364346131636666386566303963336437653764623837313264343235626464333332616139
|
38653664663663613266626664643236323532323933353366386434343564633938346434383365
|
||||||
30346233613764323435623539653230303361616133373236363430653330613964636335623433
|
62633234393163333130333534646366383537303836353631616434633266613262383338303130
|
||||||
33333066386363346164623238643266353233353566306633333539366237633966646637396430
|
35343661386461346361626530656535636665653731383039303933323237323562356133316336
|
||||||
30316364343834646566643035336465653938643064346137656431643435383839383561623636
|
34363531343062303436333538613462643432636164366262623336346138306364343461316362
|
||||||
62323566636638623965623065626435396439663030323936356335323162336532373331393132
|
31633938633161343164643963656136303666613139663966663630643138663333323636343131
|
||||||
39343330646435326330616532363638653365623630343764663332306530613936656665666162
|
31363830653431653839326663666563363237613438383565366134653835653536383164376661
|
||||||
38346666333235333361356339303335653333333837323062303735306436323433386634373266
|
39386439653431343834623333363133383939303362326465626538326533326262623539663737
|
||||||
61663339613737306333653934663565316434396166343030626132653062373438386438366538
|
66343161303336353636306265396330633063346434366531346335356631386235316336376663
|
||||||
39373564303766353332656234393831353339343463666565383436346332366332333338386132
|
32313762323664303033353939353235656165326534636661363632656663333530333764626330
|
||||||
33323333323730303765663337656665363461373439646131373862646331646164616533633631
|
62313264353131616535616537653732613138343335316562353032616636636661323266336234
|
||||||
36313432313363326236313234343830626132353735383762326263333938343463626437303766
|
30653461636336326138316538386562333638303466326661336533393034653233666131323037
|
||||||
39643662386662336364643432393536326530373033366333336161323865373865343537333430
|
34646132396637303134616462353233373861613538613038653538356530653963303761633131
|
||||||
39363439656337336464626463613361643861613237626366313234333932613330346532393466
|
37363964373431623834653965393637383838343736376135333839333666626663343033303062
|
||||||
35616166333036343034393337333537616464393231393564353262326630336339633063336166
|
65623332366235623135323264656638633430323637303665303635346666386333306333376162
|
||||||
36326132643861653065303537393665313437393036346232363934646664346539646135623037
|
35386561663964386238323564376436373362636561616264396163306131353738613339373464
|
||||||
30653238623365383033636437616163613532666237653537333166353633353437396361613431
|
37316463666535363265326466393361386530346130363839666438343462363565336333616234
|
||||||
35343131653564613338366335623733376466366430336363366338373265306530333539356432
|
31386231623665363534333164326436643335323939623335623730623039343738356337326239
|
||||||
31363166636461623232383332313735626230303630373763306632663561663061326239616132
|
38363930363263666566633764393331336135333462626563643439366136383039383835383063
|
||||||
31383632313632633539656431613461366366373361363761643562373537346131336136356336
|
64396439663365623736386337336336666162376235643738386630646634366464356631656563
|
||||||
65373431653662336636663332626434623761636264313737633662323535376136356665643237
|
37656666336634623361356465633964373261636265333036653132303532333633613033326433
|
||||||
65343433343433623165383666333336383434616161306163656164643865303236386630396234
|
33376465346534323533663332346261373238616664646663376434383861303833653831323831
|
||||||
30313431653734663232643861653937323136383064366665613939336364383531396436613335
|
32383539323730333639336564363534373266343833303836386564353933646431626634613832
|
||||||
33343436303666373963383866353738643534316365393739323932373962636138366162363231
|
30646538666264636639333838613037636431633335373364333663633534653834333730346466
|
||||||
65663661393237373936336330363438363831373961643362643732356363636661653964386431
|
36623838363439326530353036383664316664393330623166333239343139373135353336333832
|
||||||
64616239346536346134613730626336353135333030336632396639303333366464376231343464
|
36393062396234663665623265636332393133376338343835613264306433613564626133343334
|
||||||
63613164323630623731396162653265323265633532666334373165633238343939646662353934
|
65313063396139396438396637326638383539373437396663623732656332643632643538643265
|
||||||
64383532633666663133646433623462323635623037363636646263396438336438313330373630
|
33343030333333663539326264633361623230346164306332623565323166336161366338363339
|
||||||
66366362333931373330303830323934623037623836326564373838386564323162303335386139
|
63633537666230646636343636636435333363353134373365656239353436396561346436326337
|
||||||
30393432353439656234663566376632646230656133346430346563653637313238346366626338
|
32626538346139643764616135616330303936666139333736306133313438393133303463323338
|
||||||
35623434383035316131643636306166656663343965653564396439393333343138626364616434
|
39643431393062613038346362643065323837376563613230383933313733363763666133646262
|
||||||
36633066303639383239383336653638313765383734356461373234626134653066376631353061
|
32653539333961653133663737303631323634663830656463363162373435643637656133316337
|
||||||
64373265333334626133363362653537353232633964613639653730623539306261363237326635
|
62623630646430636166356433633934356436343031343664366265626231373662613635623432
|
||||||
31353535373432336563386633626266633832383863613138633936383366353630373533663065
|
34346133626461313135666266306462633962356332633135333331346532386530393234646562
|
||||||
32376261616231613433653238386562346531656530383861383039326461323965303962353731
|
35303836313763636261323233626532353135633763346263326666636366336138396234353065
|
||||||
35373238333237633433363638393537626538363530616433386336326337643630663139313530
|
66313665393665393163383863333232336138656239643631653634376631396261373636646138
|
||||||
39333938666166343564346231326639366539313331653965316663316365663539656334663461
|
66636538666439663564386561363139313661656366643461336465376264343731643331336636
|
||||||
38353035313938363030633436303462653666643830386130343461363564623962376532393238
|
33363837316230396239663463346663306461623133323839323335316461386261363363653536
|
||||||
36376164323830376463653736613266653761393436353637346439326464333937626237313562
|
64356264613961636532663166363238363564333061316364643264643663306661666364383265
|
||||||
62313330656234356639653139633634333232346336333366663438636338623536383430326430
|
62653439353734613461323332666331336334323334336337386636643966373935393234323737
|
||||||
32316566353837353037316435633330613466643462326239393065336238646664636263616539
|
61383665383865306465343631303333396661643763333466363438383561376664336364633133
|
||||||
38636533343433663134663836653131633166613361303562353636653464653663383538323835
|
31616234376632343434613666643864393863363831653965623036343436363233383434373364
|
||||||
34346661393163656365653030623563326661386137373965666335633034393865323034376463
|
36616530386463623432393331316534383537303866343530373161316164666566633130346565
|
||||||
61316564636338363539616531333132303637376331616237343230343636646231663437373431
|
37643464343164623463646339346434343661663439363737656238303631666564356163336331
|
||||||
33633131333834366561393638313834396462396335353230393332663630616265323039353963
|
31663330316135663065366532363534373764323838656133386664626131363038313536383639
|
||||||
38366337353833623031613463386234303831303061633136623563636164313364663465366665
|
30373432656665313961626461663630393334376634633137336437663134356264326664356533
|
||||||
30366238333731636238336561363130363166333034336339333163626566366333356661323839
|
33653164346631643039336439383061623136343932323337353530313665303837366366306366
|
||||||
35376634633836303439343062343336363264323164646663353861303639613931333935663536
|
66336564653938663732326237666562396566323933376364623130633865366332306363326164
|
||||||
30363232313961666436336464393165303533333733333265646563616338623733393762306538
|
61393339356237643637313365343830623965643539363838646435346239353231653137373663
|
||||||
35656334663936313135333661373065386531333936353733393638383361623361316531366366
|
35366431633465313166303732323064333839393563643462376430366637613033663638396430
|
||||||
31316465663863663137633963336433393937643636653331353861643339333933396139386331
|
66306461616163353435333237323861653533646461393737396262623231623563643263333231
|
||||||
64323664613832363064346165393739353366373162346666346461373835393236313735363239
|
66353333393563313836353665663031633236373164333866386461646632663634613034643836
|
||||||
32363036653666336463353436333763353832393330333232396434333035333234626561356666
|
61303862383033613363333564663631383236393538653936376132386532316430353338316635
|
||||||
39653731333461646130653833393232363830623034613238383765656165653363353834373664
|
61646132393432616363656164303337346533626362613134376562383238323764366164396138
|
||||||
63386363656532373537373533376265633564346462663136336462373161623339386233373730
|
33626561323661636466396436303539383564353564303362663336326265373230386630623633
|
||||||
64316265303865666139646635366365653831356165663665616539313639353062333064306661
|
64646432636530393861363864623063396533643361323933353934663362353461663765626438
|
||||||
38626166393839383430326264366365633637383634373338333162653835663164396333336166
|
34613532336161653434313933623435653435343030376531613130383935363237646331323263
|
||||||
36336565366632326163393533363435383765386433633333373563626462653634623634326664
|
32306530313830633631663837393864636338356463303233653733333764323630376162333863
|
||||||
33323933656261653266303161663833326638653066663663646665666164303433383738623966
|
32623838663865323436386262323532366139633062346365363762623434373531356564316331
|
||||||
64383532626663373339353839613766623364643465373266643230336664313762393334666362
|
65366565383863326131343136336465653866383738653637343065353338386433303562653832
|
||||||
65666435343236343339333631386233353532386431616436663664376337373539376261393239
|
65316663623034643965356635396265373134336536633139396330303363316165363938353338
|
||||||
63636130666230383834336233313136636232376230323564306538323438613530353036356164
|
39393066323736623839346238326566303166316539326430643266366538356339613330656361
|
||||||
63326431393732333563396230353834356366623633346534346439303764363331356165666262
|
34333330363163313634646232376530646135333638656563303762363366613439386533316665
|
||||||
37373766623437626434613437313165633761353930663039363030343161323065343962363131
|
33653433616566383032663762636538643063323463383031663832336463353834616330343232
|
||||||
65356261643838616166353032633537313831356362653633393932623761666437623965356263
|
64346563616264376131633564323666383833613563643864666436386666383337383766376535
|
||||||
34623164343731336239626263663231623430336138373830383561636134663138643132393363
|
33383965363334323466306432653664336461653365646133613162373461616365666266363037
|
||||||
63353865346536313830353830376430353933313066333032366666383361373032373331336231
|
34666337353264333665326330303039343466373036323664376438343030623531353230383234
|
||||||
66623733396638613265306136633737616262336339663134653461346337643964656334396364
|
66336335326662656264313038643239646164396362616264613532306331356136616133393737
|
||||||
32316363363932653834366131366563323964646233333661343230336261363966366232663665
|
65333734366135663966613135303934633761343635653838643336316138336530396662633835
|
||||||
61663332383862376163613739663465356166326338383263333730353466623161303830646633
|
31636265373261323432366137346132393536636265326236396538643361343430656338643331
|
||||||
35326236666239623734386130653966346330316634363065363665383266353361333332366532
|
66393534393430326333636534373261396636343165663132646637643834316462656130333637
|
||||||
66623766663163373333373532376164616632386634303037626231636565346263653832373836
|
30643437303831353961613737633631373562613933316533623831346438383436393039386238
|
||||||
31393431666537656337336539626163333334653635656134356438393838343164313863346639
|
32653364663564313039663930373631376635613130353734346538616431343966663931306233
|
||||||
35353865353036326334346338333363616666666563333461653461376637616237373432336335
|
36356662353630316462633261656537623065646239666530363537353334363535653336653265
|
||||||
32303939333934393332396166303864376633346565306332323835653435316135313363353139
|
62313231623533333263663339353431643638616562343532303963303839343030343635313837
|
||||||
61363734613465663137323839376436386264663030653539356631353166376463363937333933
|
35633935376539336634356537323838623737633130666137323634343537376564363832343031
|
||||||
63653030336237303837373832323237313736353235363361373964393062303264326233343265
|
63356461303439376166363632616139653766613630353865316537373230303930623332353139
|
||||||
39363631613932653266386261623064636639363335623136336438323239633930336337646430
|
38663966326534393963656138346239666231373735363931383865623831313537306263366334
|
||||||
33633237336530626166316231643536633539643837656433663337366439303066663936623031
|
38363833353762373332313332393636383262363735613437323330383337643266356435316632
|
||||||
31653631636633386539333061346132663162613938356333616366356336653935316361623661
|
35613962353265373133623238656637663265333338666362353366666333363561613538346165
|
||||||
66393066653738303335363062386530363936646165333339616432366461353066303338303935
|
65613835656665613730663131643932303733663665636561393062653565373434366163613537
|
||||||
64363732383866643730343732613335326535383237303563386438313862643536386366613862
|
65653861386234633564363432616264653761313536623431353737373234313230623736376338
|
||||||
36343366653965326361636136663364623762373162623635643864373839633434656436393534
|
34613664316633643038333464313031376639356630366234396563313538656163643839636134
|
||||||
35623561313633653833333837666365343535373837383365336137323538393163326264306333
|
34653766303266386161373761303865613431363664373737656231633361653662353438626330
|
||||||
35373063313331373661626139616166313764353737643765373535613637666661326238396433
|
66643062316130366361353538303664303561616236386265313832646464623737303030313465
|
||||||
31643334323361333232636366656564343932396638336535313961383334376635616131613530
|
38336461336636616635333931313138373364613337366336306131393230386335656366383463
|
||||||
66313961663831643162656533303665656535343762306564633234306539393237383937313664
|
38303635333539363439353633306437633430633131336632373334626138306230383633613938
|
||||||
31643537346664303333663533316566386433323331376265653439386133626664303338633037
|
61623932313963396534326366663734613833366663323736383033386363663530656461393238
|
||||||
39663833356535373537613230346462346363323662386532636262376137343136623963303633
|
35643537343066643535376330653636303963333233663933356661383035343439343466376465
|
||||||
35336134356338393334383562663937386334663665663862323435356135376439383064306338
|
30626138396366386331383232383733636334303535353831646236393237383335336530626433
|
||||||
38363437323661663364636334326561333966356234353035373763366639363130333961303264
|
37613035646431386438396239343565336532653363326365396264616661623432393434346131
|
||||||
33396436636133306466333233663335316436323934616265346433376238373765623161663039
|
62363363623036653165336439373366396132326361383938333338363739353636616662363363
|
||||||
61343333356661326661616635363463633563303566663333323466373165353437633566323939
|
62623464643636656135666631343533613635376439346638393636643934663565306332363761
|
||||||
39393539363737386561653635323438636338333338383061393530613436653963333535353663
|
39663738633661613864353730326533633732336235663536656139623439663433383136376263
|
||||||
3835336436353837333633303165343733623633376533316132
|
30323135626238323863373435363265666366343033663966633239633161323639613536643564
|
||||||
|
64326430353063663935646663303838656131623362323836363938653238336633336430306566
|
||||||
|
36646235666663333037326563376130633130653262326464656363303831636130666665313932
|
||||||
|
61653531356235363136353465346131613138653837646130333732373866396233666536396133
|
||||||
|
35373435356137633836313561653932306339303262353732366132663730656564326632643230
|
||||||
|
61633930323962313961623332663231393033646334663634326264343232666531383861316437
|
||||||
|
62663962623461323336313239316161633761646131616263323463663133643631663064323437
|
||||||
|
39636332316564616464326163326634653839303236313761303930623932303031653266323463
|
||||||
|
37323637343831373763613833353735323064323364643161356262313835373562623565366434
|
||||||
|
65313033653032663163656431326464316166613463343932313837316365346638326561343132
|
||||||
|
36303739666563313231346135323663653834393463623363626437366231663538373234653962
|
||||||
|
64303161396262333030636135663238336230396533346331666439313561663065666666363139
|
||||||
|
39666132653032333162333363333534656433663537363664373233343265356166363832393639
|
||||||
|
34383436333834316239643730653763386538323334613435653765633834313862633139336439
|
||||||
|
36356636326631663463613839363634376561326137333864346565373738306532343463626261
|
||||||
|
37383561356539363734363362653265666662316233383137373964323164313166356165663262
|
||||||
|
37663963643861376133653463616462396437343035653439643537663131396362346333643065
|
||||||
|
39323462386235353036663362386338313062633338383630646635633836633966383437396366
|
||||||
|
32323036306331353233393234343432323964666163373565303964626533346630356232356661
|
||||||
|
32366433323739363030326531626264666536393630303061363136346633356162613334623532
|
||||||
|
65386366623031613136613233343931623235666362373265353163373765316533663332356664
|
||||||
|
33313363393463313537646132323664396433613333633030633766616530316136613131363430
|
||||||
|
38636438623761333935626264393462616462396364333263333931313036623062623032623539
|
||||||
|
34313035623162343131653337343335333132656431633534353662613737316432373063373163
|
||||||
|
62373164663238653033613632323363376463363535316532323432343361353833373766353564
|
||||||
|
61346330663362653866376162623562646236666565623935383738356330313333383262343363
|
||||||
|
61666264343831316439623037356431353130623038306437326661663563326266393032653138
|
||||||
|
64623032363166323336336633316232353030386435353931393432613032373464383539616435
|
||||||
|
38343666306165626530633634643038303238373766326665376539656262393433656564306333
|
||||||
|
316239326464633438323837663330633030
|
||||||
|
|||||||
@@ -87,6 +87,12 @@ vault_angelia_mcp_auth: changeme
|
|||||||
vault_athena_mcp_auth: changeme
|
vault_athena_mcp_auth: changeme
|
||||||
vault_kairos_mcp_auth: changeme
|
vault_kairos_mcp_auth: changeme
|
||||||
|
|
||||||
|
# Athena
|
||||||
|
vault_athena_secret_key: changeme
|
||||||
|
vault_athena_db_password: changeme
|
||||||
|
vault_athena_oauth_client_id: changeme
|
||||||
|
vault_athena_oauth_client_secret: changeme
|
||||||
|
|
||||||
# Arke NTTh API Tokens
|
# Arke NTTh API Tokens
|
||||||
vault_ntth_token_1_app_secret: changeme
|
vault_ntth_token_1_app_secret: changeme
|
||||||
vault_ntth_token_2_app_secret: changeme
|
vault_ntth_token_2_app_secret: changeme
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ services:
|
|||||||
- alloy
|
- alloy
|
||||||
- caliban
|
- caliban
|
||||||
- docker
|
- docker
|
||||||
|
- freecad_mcp
|
||||||
- kernos
|
- kernos
|
||||||
|
- rommie
|
||||||
|
|
||||||
# Account Taxonomy
|
# Account Taxonomy
|
||||||
# principal_user is the AI agent operator account on this host
|
# principal_user is the AI agent operator account on this host
|
||||||
@@ -16,11 +18,31 @@ principal_uid: 1000
|
|||||||
# Alloy
|
# Alloy
|
||||||
alloy_log_level: "warn"
|
alloy_log_level: "warn"
|
||||||
|
|
||||||
|
# Rommie MCP Server Configuration (Agent S GUI Automation)
|
||||||
|
rommie_port: 22061
|
||||||
|
rommie_host: "0.0.0.0"
|
||||||
|
rommie_display: ":10"
|
||||||
|
rommie_allowed_hosts: "caliban.incus,rommie.ouranos.helu.ca"
|
||||||
|
rommie_model: Qwen3.5-35B-A3B-UD-Q4_K_XL.gguf
|
||||||
|
rommie_model_url: "http://nyx.helu.ca:22079"
|
||||||
|
rommie_provider: "openai"
|
||||||
|
rommie_ground_provider: "huggingface"
|
||||||
|
rommie_ground_url: "http://pan.helu.ca:22078"
|
||||||
|
rommie_ground_model: "UI-TARS-7B-DPO-Q6_K_L.gguf"
|
||||||
|
rommie_grounding_width: 1024
|
||||||
|
rommie_grounding_height: 1024
|
||||||
|
|
||||||
|
# FreeCAD Robust MCP Server Configuration
|
||||||
|
freecad_mcp_user: harper
|
||||||
|
freecad_mcp_group: harper
|
||||||
|
freecad_mcp_directory: /srv/freecad-mcp
|
||||||
|
freecad_mcp_port: 22063
|
||||||
|
|
||||||
# Kernos MCP Shell Server Configuration
|
# Kernos MCP Shell Server Configuration
|
||||||
kernos_user: harper
|
kernos_user: harper
|
||||||
kernos_group: harper
|
kernos_group: harper
|
||||||
kernos_directory: /srv/kernos
|
kernos_directory: /srv/kernos
|
||||||
kernos_port: 22021
|
kernos_port: 22062
|
||||||
kernos_host: "0.0.0.0"
|
kernos_host: "0.0.0.0"
|
||||||
kernos_log_level: INFO
|
kernos_log_level: INFO
|
||||||
kernos_log_format: json
|
kernos_log_format: json
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ argos_group: argos
|
|||||||
argos_directory: /srv/argos
|
argos_directory: /srv/argos
|
||||||
argos_port: 25534
|
argos_port: 25534
|
||||||
argos_log_level: INFO
|
argos_log_level: INFO
|
||||||
argos_searxng_instances: http://oberon.incus:22083/
|
argos_searxng_instances: http://rosalind.incus:22089/
|
||||||
argos_cache_ttl: 300
|
argos_cache_ttl: 300
|
||||||
argos_max_results: 10
|
argos_max_results: 10
|
||||||
argos_request_timeout: 30.0
|
argos_request_timeout: 30.0
|
||||||
|
|||||||
@@ -4,76 +4,13 @@
|
|||||||
services:
|
services:
|
||||||
- alloy
|
- alloy
|
||||||
- docker
|
- docker
|
||||||
- hass
|
|
||||||
- mcp_switchboard
|
|
||||||
- openwebui
|
|
||||||
- rabbitmq
|
- rabbitmq
|
||||||
- searxng
|
|
||||||
- smtp4dev
|
- smtp4dev
|
||||||
|
|
||||||
# Alloy
|
# Alloy
|
||||||
alloy_log_level: "warn"
|
alloy_log_level: "warn"
|
||||||
rabbitmq_syslog_port: 51402
|
rabbitmq_syslog_port: 51402
|
||||||
searxng_syslog_port: 51403
|
smtp4dev_syslog_port: 51405
|
||||||
|
|
||||||
# MCP Switchboard Configuration
|
|
||||||
mcp_switchboard_user: mcpsb
|
|
||||||
mcp_switchboard_group: mcpsb
|
|
||||||
mcp_switchboard_directory: /srv/mcp_switchboard
|
|
||||||
mcp_switchboard_port: 22785
|
|
||||||
mcp_switchboard_docker_host: "tcp://miranda.incus:2375"
|
|
||||||
mcp_switchboard_db_host: portia.incus
|
|
||||||
mcp_switchboard_db_port: 5432
|
|
||||||
mcp_switchboard_db_name: mcp_switchboard
|
|
||||||
mcp_switchboard_db_user: mcpsb
|
|
||||||
mcp_switchboard_db_password: "{{ vault_mcp_switchboard_db_password }}"
|
|
||||||
mcp_switchboard_rabbitmq_host: localhost
|
|
||||||
mcp_switchboard_rabbitmq_port: 5672
|
|
||||||
mcp_switchboard_rabbitmq_user: rabbitmq
|
|
||||||
mcp_switchboard_rabbitmq_password: "{{ vault_mcp_switchboard_rabbitmq_password }}"
|
|
||||||
mcp_switchboard_secret_key: "{{ vault_mcp_switchboard_secret_key }}"
|
|
||||||
|
|
||||||
# Open WebUI Configuration
|
|
||||||
openwebui_user: openwebui
|
|
||||||
openwebui_group: openwebui
|
|
||||||
openwebui_directory: /srv/openwebui
|
|
||||||
openwebui_cors_allow_origin: https://openwebui.ouranos.helu.ca
|
|
||||||
openwebui_port: 22088
|
|
||||||
openwebui_host: puck.incus
|
|
||||||
openwebui_secret_key: "{{ vault_openwebui_secret_key }}"
|
|
||||||
openwebui_enable_signup: true
|
|
||||||
openwebui_enable_email_login: false
|
|
||||||
|
|
||||||
# OAuth/OIDC Configuration (Casdoor SSO)
|
|
||||||
openwebui_oauth_client_id: "{{ vault_openwebui_oauth_client_id }}"
|
|
||||||
openwebui_oauth_client_secret: "{{ vault_openwebui_oauth_client_secret }}"
|
|
||||||
openwebui_oauth_provider_name: "Casdoor"
|
|
||||||
openwebui_oauth_provider_url: "https://id.ouranos.helu.ca/.well-known/openid-configuration"
|
|
||||||
|
|
||||||
# Database Configuration
|
|
||||||
openwebui_db_host: portia.incus
|
|
||||||
openwebui_db_port: 5432
|
|
||||||
openwebui_db_name: openwebui
|
|
||||||
openwebui_db_user: openwebui
|
|
||||||
openwebui_db_password: "{{ vault_openwebui_db_password }}"
|
|
||||||
|
|
||||||
# API Keys
|
|
||||||
openwebui_openai_api_key: "{{ vault_openwebui_openai_api_key }}"
|
|
||||||
openwebui_anthropic_api_key: "{{ vault_openwebui_anthropic_api_key }}"
|
|
||||||
openwebui_groq_api_key: "{{ vault_openwebui_groq_api_key }}"
|
|
||||||
openwebui_mistral_api_key: "{{ vault_openwebui_mistral_api_key }}"
|
|
||||||
|
|
||||||
# Ollama Configuration
|
|
||||||
ollama_api_base_url: ""
|
|
||||||
openwebui_ollama_api_key: ""
|
|
||||||
|
|
||||||
# SSL Configuration
|
|
||||||
openwebui_enable_https: false
|
|
||||||
openwebui_ssl_cert_path: ""
|
|
||||||
openwebui_ssl_key_path: ""
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
openwebui_log_level: info
|
|
||||||
|
|
||||||
# RabbitMQ Config
|
# RabbitMQ Config
|
||||||
rabbitmq_user: rabbitmq
|
rabbitmq_user: rabbitmq
|
||||||
@@ -83,33 +20,6 @@ rabbitmq_amqp_port: 5672
|
|||||||
rabbitmq_management_port: 25582
|
rabbitmq_management_port: 25582
|
||||||
rabbitmq_password: "{{ vault_rabbitmq_password }}"
|
rabbitmq_password: "{{ vault_rabbitmq_password }}"
|
||||||
|
|
||||||
# Redis password
|
|
||||||
redis_password: "{{ vault_redis_password }}"
|
|
||||||
|
|
||||||
# SearXNG Configuration
|
|
||||||
searxng_user: searxng
|
|
||||||
searxng_group: searxng
|
|
||||||
searxng_directory: /srv/searxng
|
|
||||||
searxng_port: 22083
|
|
||||||
searxng_base_url: http://oberon.incus:22083/
|
|
||||||
searxng_instance_name: "Agathos Search"
|
|
||||||
searxng_secret_key: "{{ vault_searxng_secret_key }}"
|
|
||||||
|
|
||||||
# SearXNG OAuth2-Proxy Sidecar
|
|
||||||
# Note: Each host supports at most one OAuth2-Proxy sidecar instance
|
|
||||||
# (binary shared at /usr/local/bin/oauth2-proxy, unique systemd unit per service)
|
|
||||||
searxng_oauth2_proxy_dir: /etc/oauth2-proxy-searxng
|
|
||||||
searxng_oauth2_proxy_version: "7.6.0"
|
|
||||||
searxng_proxy_port: 22073
|
|
||||||
searxng_domain: "ouranos.helu.ca"
|
|
||||||
searxng_oauth2_oidc_issuer_url: "https://id.ouranos.helu.ca"
|
|
||||||
searxng_oauth2_redirect_url: "https://searxng.ouranos.helu.ca/oauth2/callback"
|
|
||||||
|
|
||||||
# OAuth2 Credentials (from vault)
|
|
||||||
searxng_oauth2_client_id: "{{ vault_searxng_oauth2_client_id }}"
|
|
||||||
searxng_oauth2_client_secret: "{{ vault_searxng_oauth2_client_secret }}"
|
|
||||||
searxng_oauth2_cookie_secret: "{{ vault_searxng_oauth2_cookie_secret }}"
|
|
||||||
|
|
||||||
# smtp4dev Configuration
|
# smtp4dev Configuration
|
||||||
smtp4dev_user: smtp4dev
|
smtp4dev_user: smtp4dev
|
||||||
smtp4dev_group: smtp4dev
|
smtp4dev_group: smtp4dev
|
||||||
@@ -117,18 +27,4 @@ smtp4dev_directory: /srv/smtp4dev
|
|||||||
smtp4dev_port: 22085
|
smtp4dev_port: 22085
|
||||||
smtp4dev_smtp_port: 22025
|
smtp4dev_smtp_port: 22025
|
||||||
smtp4dev_imap_port: 22045
|
smtp4dev_imap_port: 22045
|
||||||
smtp4dev_syslog_port: 51405
|
|
||||||
|
|
||||||
# Home Assistant Configuration
|
|
||||||
hass_user: hass
|
|
||||||
hass_group: hass
|
|
||||||
hass_directory: /srv/hass
|
|
||||||
hass_media_directory: /srv/hass/media
|
|
||||||
hass_port: 8123
|
|
||||||
hass_version: "2026.2.0"
|
|
||||||
hass_db_host: portia.incus
|
|
||||||
hass_db_port: 5432
|
|
||||||
hass_db_name: hass
|
|
||||||
hass_db_user: hass
|
|
||||||
hass_db_password: "{{ vault_hass_db_password }}"
|
|
||||||
hass_metrics_token: "{{ vault_hass_metrics_token }}"
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
# Services: alloy, postgresql
|
# Services: alloy, postgresql
|
||||||
# Note: PgAdmin moved to Prospero (PPLG stack)
|
# Note: PgAdmin moved to Prospero (PPLG stack)
|
||||||
|
|
||||||
|
ansible_user: robert
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- alloy
|
- alloy
|
||||||
- postgresql
|
- postgresql
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# Prospero Configuration - PPLG Observability & Admin Stack
|
# Prospero Configuration - PPLG Observability & Admin Stack
|
||||||
# Services: pplg (PgAdmin, Prometheus, Loki, Grafana + HAProxy + OAuth2-Proxy)
|
# Services: pplg (PgAdmin, Prometheus, Loki, Grafana + HAProxy + OAuth2-Proxy)
|
||||||
|
|
||||||
|
ansible_user: robert
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- alloy
|
- alloy
|
||||||
- pplg
|
- pplg
|
||||||
|
|||||||
@@ -2,25 +2,45 @@
|
|||||||
# Puck Configuration - Application Runtime
|
# Puck Configuration - Application Runtime
|
||||||
# Services: alloy, docker, lxqt, jupyterlab
|
# Services: alloy, docker, lxqt, jupyterlab
|
||||||
|
|
||||||
|
ansible_user: robert
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- alloy
|
- alloy
|
||||||
- docker
|
- docker
|
||||||
- gitea_runner
|
- gitea_runner
|
||||||
- jupyterlab
|
- jupyterlab
|
||||||
|
- athena
|
||||||
|
|
||||||
# Gitea Runner
|
# Gitea Runner
|
||||||
gitea_runner_name: "puck-runner"
|
gitea_runner_name: "puck-runner"
|
||||||
|
|
||||||
# Alloy
|
# Alloy
|
||||||
alloy_log_level: "warn"
|
alloy_log_level: "warn"
|
||||||
angelia_syslog_port: 51421
|
angelia_syslog_port: 51422
|
||||||
sagittarius_syslog_port: 51431
|
mnemosyne_syslog_port: 51431
|
||||||
athena_syslog_port: 51441
|
athena_syslog_port: 51424
|
||||||
kairos_syslog_port: 51451
|
kairos_syslog_port: 51425
|
||||||
icarlos_syslog_port: 51461
|
icarlos_syslog_port: 51426
|
||||||
spelunker_syslog_port: 51481
|
spelunker_syslog_port: 51428
|
||||||
jupyterlab_syslog_port: 51491
|
jupyterlab_syslog_port: 51411
|
||||||
daedalus_syslog_port: 51401
|
daedalus_syslog_port: 51430
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Athena Configuration
|
||||||
|
# =============================================================================
|
||||||
|
athena_user: athena
|
||||||
|
athena_group: athena
|
||||||
|
athena_directory: /srv/athena
|
||||||
|
athena_port: 22481
|
||||||
|
athena_domain: "ouranos.helu.ca"
|
||||||
|
|
||||||
|
# Casdoor SSO Credentials (from vault)
|
||||||
|
athena_casdoor_client_id: "{{ vault_athena_oauth_client_id }}"
|
||||||
|
athena_casdoor_client_secret: "{{ vault_athena_oauth_client_secret }}"
|
||||||
|
|
||||||
|
# Application Secrets (from vault)
|
||||||
|
athena_secret_key: "{{ vault_athena_secret_key }}"
|
||||||
|
athena_db_password: "{{ vault_athena_db_password }}"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# JupyterLab Configuration
|
# JupyterLab Configuration
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
# Rosalind Configuration - GO, Node.js, PHP Apps
|
# Rosalind Configuration - GO, Node.js, PHP Apps
|
||||||
# Services: alloy, gitea, lobechat, nextcloud
|
# Services: alloy, gitea, lobechat, nextcloud
|
||||||
|
|
||||||
|
ansible_user: robert
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- alloy
|
- alloy
|
||||||
- anythingllm
|
- anythingllm
|
||||||
@@ -10,10 +12,14 @@ services:
|
|||||||
- lobechat
|
- lobechat
|
||||||
- memcached
|
- memcached
|
||||||
- nextcloud
|
- nextcloud
|
||||||
|
- openwebui
|
||||||
|
- hass
|
||||||
|
- searxng
|
||||||
|
|
||||||
# Alloy
|
# Alloy
|
||||||
alloy_log_level: "warn"
|
alloy_log_level: "warn"
|
||||||
lobechat_syslog_port: 51461
|
lobechat_syslog_port: 51461
|
||||||
|
searxng_syslog_port: 51403
|
||||||
|
|
||||||
# AnythingLLM Configuration
|
# AnythingLLM Configuration
|
||||||
anythingllm_user: anythingllm
|
anythingllm_user: anythingllm
|
||||||
@@ -97,6 +103,20 @@ gitea_oauth_token_url: "https://id.ouranos.helu.ca/api/login/oauth/access_token"
|
|||||||
gitea_oauth_userinfo_url: "https://id.ouranos.helu.ca/api/userinfo"
|
gitea_oauth_userinfo_url: "https://id.ouranos.helu.ca/api/userinfo"
|
||||||
gitea_oauth_scopes: "openid profile email"
|
gitea_oauth_scopes: "openid profile email"
|
||||||
|
|
||||||
|
# Home Assistant Configuration
|
||||||
|
hass_user: hass
|
||||||
|
hass_group: hass
|
||||||
|
hass_directory: /srv/hass
|
||||||
|
hass_media_directory: /srv/hass/media
|
||||||
|
hass_port: 8123
|
||||||
|
hass_version: "2026.2.0"
|
||||||
|
hass_db_host: portia.incus
|
||||||
|
hass_db_port: 5432
|
||||||
|
hass_db_name: hass
|
||||||
|
hass_db_user: hass
|
||||||
|
hass_db_password: "{{ vault_hass_db_password }}"
|
||||||
|
hass_metrics_token: "{{ vault_hass_metrics_token }}"
|
||||||
|
|
||||||
# LobeChat Configuration
|
# LobeChat Configuration
|
||||||
lobechat_user: lobechat
|
lobechat_user: lobechat
|
||||||
lobechat_group: lobechat
|
lobechat_group: lobechat
|
||||||
@@ -153,3 +173,69 @@ nextcloud_domain: nextcloud.ouranos.helu.ca
|
|||||||
nextcloud_instance_id: ""
|
nextcloud_instance_id: ""
|
||||||
nextcloud_password_salt: ""
|
nextcloud_password_salt: ""
|
||||||
nextcloud_secret: ""
|
nextcloud_secret: ""
|
||||||
|
|
||||||
|
|
||||||
|
# Open WebUI Configuration
|
||||||
|
openwebui_user: openwebui
|
||||||
|
openwebui_group: openwebui
|
||||||
|
openwebui_directory: /srv/openwebui
|
||||||
|
openwebui_cors_allow_origin: https://openwebui.ouranos.helu.ca
|
||||||
|
openwebui_port: 22088
|
||||||
|
openwebui_host: puck.incus
|
||||||
|
openwebui_secret_key: "{{ vault_openwebui_secret_key }}"
|
||||||
|
openwebui_enable_signup: true
|
||||||
|
openwebui_enable_email_login: false
|
||||||
|
|
||||||
|
# OAuth/OIDC Configuration (Casdoor SSO)
|
||||||
|
openwebui_oauth_client_id: "{{ vault_openwebui_oauth_client_id }}"
|
||||||
|
openwebui_oauth_client_secret: "{{ vault_openwebui_oauth_client_secret }}"
|
||||||
|
openwebui_oauth_provider_name: "Casdoor"
|
||||||
|
openwebui_oauth_provider_url: "https://id.ouranos.helu.ca/.well-known/openid-configuration"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
openwebui_db_host: portia.incus
|
||||||
|
openwebui_db_port: 5432
|
||||||
|
openwebui_db_name: openwebui
|
||||||
|
openwebui_db_user: openwebui
|
||||||
|
openwebui_db_password: "{{ vault_openwebui_db_password }}"
|
||||||
|
|
||||||
|
# API Keys
|
||||||
|
openwebui_openai_api_key: "{{ vault_openwebui_openai_api_key }}"
|
||||||
|
openwebui_anthropic_api_key: "{{ vault_openwebui_anthropic_api_key }}"
|
||||||
|
openwebui_groq_api_key: "{{ vault_openwebui_groq_api_key }}"
|
||||||
|
openwebui_mistral_api_key: "{{ vault_openwebui_mistral_api_key }}"
|
||||||
|
|
||||||
|
# Ollama Configuration
|
||||||
|
ollama_api_base_url: ""
|
||||||
|
openwebui_ollama_api_key: ""
|
||||||
|
|
||||||
|
# SSL Configuration
|
||||||
|
openwebui_enable_https: false
|
||||||
|
openwebui_ssl_cert_path: ""
|
||||||
|
openwebui_ssl_key_path: ""
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
openwebui_log_level: info
|
||||||
|
|
||||||
|
# SearXNG Configuration
|
||||||
|
searxng_user: searxng
|
||||||
|
searxng_group: searxng
|
||||||
|
searxng_directory: /srv/searxng
|
||||||
|
searxng_port: 22089
|
||||||
|
searxng_base_url: http://rosalind.incus:22089/
|
||||||
|
searxng_instance_name: "Ouranos Search"
|
||||||
|
searxng_secret_key: "{{ vault_searxng_secret_key }}"
|
||||||
|
|
||||||
|
# SearXNG OAuth2-Proxy Sidecar
|
||||||
|
# Note: Each host supports at most one OAuth2-Proxy sidecar instance
|
||||||
|
# (binary shared at /usr/local/bin/oauth2-proxy, unique systemd unit per service)
|
||||||
|
searxng_oauth2_proxy_dir: /etc/oauth2-proxy-searxng
|
||||||
|
searxng_proxy_port: 22079
|
||||||
|
searxng_domain: "ouranos.helu.ca"
|
||||||
|
searxng_oauth2_oidc_issuer_url: "https://id.ouranos.helu.ca"
|
||||||
|
searxng_oauth2_redirect_url: "https://searxng.ouranos.helu.ca/oauth2/callback"
|
||||||
|
|
||||||
|
# OAuth2 Credentials (from vault)
|
||||||
|
searxng_oauth2_client_id: "{{ vault_searxng_oauth2_client_id }}"
|
||||||
|
searxng_oauth2_client_secret: "{{ vault_searxng_oauth2_client_secret }}"
|
||||||
|
searxng_oauth2_cookie_secret: "{{ vault_searxng_oauth2_cookie_secret }}"
|
||||||
@@ -26,10 +26,10 @@ certbot_group: certbot
|
|||||||
certbot_directory: /srv/certbot
|
certbot_directory: /srv/certbot
|
||||||
certbot_email: webmaster@helu.ca
|
certbot_email: webmaster@helu.ca
|
||||||
certbot_cert_name: ouranos.helu.ca
|
certbot_cert_name: ouranos.helu.ca
|
||||||
certbot_domains:
|
|
||||||
- "*.ouranos.helu.ca"
|
|
||||||
- "ouranos.helu.ca"
|
|
||||||
prometheus_node_exporter_text_directory: /var/lib/prometheus/node-exporter
|
prometheus_node_exporter_text_directory: /var/lib/prometheus/node-exporter
|
||||||
|
certbot_certificates:
|
||||||
|
- cert_name: wildcard.ouranos.helu.ca
|
||||||
|
domains: ["*.ouranos.helu.ca", "ouranos.helu.ca"]
|
||||||
|
|
||||||
# HAProxy Configuration
|
# HAProxy Configuration
|
||||||
haproxy_user: haproxy
|
haproxy_user: haproxy
|
||||||
@@ -65,7 +65,7 @@ haproxy_backends:
|
|||||||
redirect_root: "/login/heluca" # Redirect root to branded org login page
|
redirect_root: "/login/heluca" # Redirect root to branded org login page
|
||||||
|
|
||||||
- subdomain: "openwebui"
|
- subdomain: "openwebui"
|
||||||
backend_host: "oberon.incus"
|
backend_host: "rosalind.incus"
|
||||||
backend_port: 22088
|
backend_port: 22088
|
||||||
health_path: "/"
|
health_path: "/"
|
||||||
|
|
||||||
@@ -81,37 +81,37 @@ haproxy_backends:
|
|||||||
|
|
||||||
# SearXNG - routed through OAuth2-Proxy sidecar on Oberon
|
# SearXNG - routed through OAuth2-Proxy sidecar on Oberon
|
||||||
- subdomain: "searxng"
|
- subdomain: "searxng"
|
||||||
backend_host: "oberon.incus"
|
backend_host: "rosalind.incus"
|
||||||
backend_port: 22073
|
backend_port: 22079
|
||||||
health_path: "/ping"
|
health_path: "/ping"
|
||||||
|
|
||||||
- subdomain: "pgadmin"
|
- subdomain: "pgadmin"
|
||||||
backend_host: "prospero.incus"
|
backend_host: "prospero.incus"
|
||||||
backend_port: 443
|
backend_port: 5050
|
||||||
health_path: "/misc/ping"
|
health_path: "/misc/ping"
|
||||||
ssl_backend: true
|
ssl_backend: true
|
||||||
|
|
||||||
- subdomain: "grafana"
|
- subdomain: "grafana"
|
||||||
backend_host: "prospero.incus"
|
backend_host: "prospero.incus"
|
||||||
backend_port: 443
|
backend_port: 3000
|
||||||
health_path: "/api/health"
|
health_path: "/api/health"
|
||||||
ssl_backend: true
|
ssl_backend: true
|
||||||
|
|
||||||
- subdomain: "prometheus"
|
- subdomain: "prometheus"
|
||||||
backend_host: "prospero.incus"
|
backend_host: "prospero.incus"
|
||||||
backend_port: 443
|
backend_port: 9090
|
||||||
health_path: "/ping"
|
health_path: "/ping"
|
||||||
ssl_backend: true
|
ssl_backend: true
|
||||||
|
|
||||||
- subdomain: "loki"
|
- subdomain: "loki"
|
||||||
backend_host: "prospero.incus"
|
backend_host: "prospero.incus"
|
||||||
backend_port: 443
|
backend_port: 3100
|
||||||
health_path: "/ready"
|
health_path: "/ready"
|
||||||
ssl_backend: true
|
ssl_backend: true
|
||||||
|
|
||||||
- subdomain: "alertmanager"
|
- subdomain: "alertmanager"
|
||||||
backend_host: "prospero.incus"
|
backend_host: "prospero.incus"
|
||||||
backend_port: 443
|
backend_port: 9093
|
||||||
health_path: "/-/healthy"
|
health_path: "/-/healthy"
|
||||||
ssl_backend: true
|
ssl_backend: true
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ haproxy_backends:
|
|||||||
|
|
||||||
- subdomain: "daedalus"
|
- subdomain: "daedalus"
|
||||||
backend_host: "puck.incus"
|
backend_host: "puck.incus"
|
||||||
backend_port: 23081
|
backend_port: 20080
|
||||||
health_path: "/api/health"
|
health_path: "/api/health"
|
||||||
timeout_server: 120s
|
timeout_server: 120s
|
||||||
|
|
||||||
@@ -131,6 +131,11 @@ haproxy_backends:
|
|||||||
backend_port: 22081
|
backend_port: 22081
|
||||||
health_path: "/chat"
|
health_path: "/chat"
|
||||||
|
|
||||||
|
- subdomain: "mnemosyne"
|
||||||
|
backend_host: "puck.incus"
|
||||||
|
backend_port: 23181
|
||||||
|
health_path: "/ready/"
|
||||||
|
|
||||||
- subdomain: "nextcloud"
|
- subdomain: "nextcloud"
|
||||||
backend_host: "rosalind.incus"
|
backend_host: "rosalind.incus"
|
||||||
backend_port: 22083
|
backend_port: 22083
|
||||||
@@ -161,6 +166,16 @@ haproxy_backends:
|
|||||||
backend_port: 22781
|
backend_port: 22781
|
||||||
health_path: "/ready/"
|
health_path: "/ready/"
|
||||||
|
|
||||||
|
- subdomain: "nike"
|
||||||
|
backend_host: "puck.incus"
|
||||||
|
backend_port: 20681
|
||||||
|
health_path: "/ready/"
|
||||||
|
|
||||||
|
- subdomain: "periplus"
|
||||||
|
backend_host: "puck.incus"
|
||||||
|
backend_port: 20581
|
||||||
|
health_path: "/ready/"
|
||||||
|
|
||||||
- subdomain: "spelunker"
|
- subdomain: "spelunker"
|
||||||
backend_host: "puck.incus"
|
backend_host: "puck.incus"
|
||||||
backend_port: 22881
|
backend_port: 22881
|
||||||
@@ -183,6 +198,18 @@ haproxy_backends:
|
|||||||
health_path: "/api/"
|
health_path: "/api/"
|
||||||
timeout_server: 300s # WebSocket support for HA frontend
|
timeout_server: 300s # WebSocket support for HA frontend
|
||||||
|
|
||||||
|
- subdomain: "freecad-mcp"
|
||||||
|
backend_host: "caliban.incus"
|
||||||
|
backend_port: 22032
|
||||||
|
health_path: "/mcp"
|
||||||
|
timeout_server: 300s # SSE streaming support for MCP
|
||||||
|
|
||||||
|
- subdomain: "rommie"
|
||||||
|
backend_host: "caliban.incus"
|
||||||
|
backend_port: 22031
|
||||||
|
health_path: "/mcp"
|
||||||
|
timeout_server: 300s # SSE streaming support for MCP
|
||||||
|
|
||||||
- subdomain: "smtp4dev"
|
- subdomain: "smtp4dev"
|
||||||
backend_host: "oberon.incus"
|
backend_host: "oberon.incus"
|
||||||
backend_port: 22085
|
backend_port: 22085
|
||||||
@@ -220,3 +247,22 @@ casdoor_ldaps_server_port: 0
|
|||||||
casdoor_radius_server_port: 1812
|
casdoor_radius_server_port: 1812
|
||||||
casdoor_radius_default_organization: "built-in"
|
casdoor_radius_default_organization: "built-in"
|
||||||
casdoor_radius_secret: "{{ vault_casdoor_radius_secret }}"
|
casdoor_radius_secret: "{{ vault_casdoor_radius_secret }}"
|
||||||
|
# Oath2
|
||||||
|
angelia_oauth2_client_id: "{{ vault_angelia_oauth_client_id }}"
|
||||||
|
angelia_oauth2_client_secret: "{{ vault_angelia_oauth_client_secret }}"
|
||||||
|
athena_oauth2_client_id: "{{ vault_athena_oauth_client_id }}"
|
||||||
|
athena_oauth2_client_secret: "{{ vault_athena_oauth_client_secret }}"
|
||||||
|
daedalus_oauth2_client_id: "{{ vault_daedalus_oauth2_client_id }}"
|
||||||
|
daedalus_oauth2_client_secret: "{{ vault_daedalus_oauth2_client_secret }}"
|
||||||
|
gitea_oauth2_client_id: "{{ vault_gitea_oauth_client_id }}"
|
||||||
|
gitea_oauth2_client_secret: "{{ vault_gitea_oauth_client_secret }}"
|
||||||
|
jupyterlab_oauth2_client_id: "{{ vault_jupyterlab_oauth_client_id }}"
|
||||||
|
jupyterlab_oauth2_client_secret: "{{ vault_jupyterlab_oauth_client_secret }}"
|
||||||
|
kairos_oauth2_client_id: "{{ vault_athena_oauth_client_id }}"
|
||||||
|
kairos_oauth2_client_secret: "{{ vault_athena_oauth_client_secret }}"
|
||||||
|
openwebui_oauth2_client_id: "{{ vault_openwebui_oauth_client_id }}"
|
||||||
|
openwebui_oauth2_client_secret: "{{ vault_openwebui_oauth_client_secret }}"
|
||||||
|
searxng_oauth2_client_id: "{{ vault_searxng_oauth2_client_id }}"
|
||||||
|
searxng_oauth2_client_secret: "{{ vault_searxng_oauth2_client_secret }}"
|
||||||
|
spelunker_oauth2_client_id: "{{ vault_spelunker_oauth_client_id }}"
|
||||||
|
spelunker_oauth2_client_secret: "{{ vault_spelunker_oauth_client_secret }}"
|
||||||
@@ -32,15 +32,15 @@ casdoor:
|
|||||||
hosts:
|
hosts:
|
||||||
titania.incus:
|
titania.incus:
|
||||||
|
|
||||||
|
freecad_mcp:
|
||||||
|
hosts:
|
||||||
|
caliban.incus:
|
||||||
|
|
||||||
kernos:
|
kernos:
|
||||||
hosts:
|
hosts:
|
||||||
caliban.incus:
|
caliban.incus:
|
||||||
korax.helu.ca:
|
korax.helu.ca:
|
||||||
|
|
||||||
searxng:
|
|
||||||
hosts:
|
|
||||||
oberon.incus:
|
|
||||||
|
|
||||||
gitea:
|
gitea:
|
||||||
hosts:
|
hosts:
|
||||||
rosalind.incus:
|
rosalind.incus:
|
||||||
@@ -48,3 +48,7 @@ gitea:
|
|||||||
mcpo:
|
mcpo:
|
||||||
hosts:
|
hosts:
|
||||||
miranda.incus:
|
miranda.incus:
|
||||||
|
|
||||||
|
rommie:
|
||||||
|
hosts:
|
||||||
|
caliban.incus:
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy Kernos MCP Shell Server
|
- name: Deploy Kernos MCP Shell Server
|
||||||
hosts: kernos
|
hosts: ubuntu
|
||||||
vars:
|
vars:
|
||||||
ansible_common_remote_group: "{{kernos_group}}"
|
ansible_common_remote_group: "{{kernos_group | default([]) }}"
|
||||||
allow_world_readable_tmpfiles: true
|
allow_world_readable_tmpfiles: true
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: Check if host has kernos service
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
has_kernos_service: "{{ 'kernos' in services | default([]) }}"
|
||||||
|
|
||||||
|
- name: Skip hosts without kernos service
|
||||||
|
ansible.builtin.meta: end_host
|
||||||
|
when: not has_kernos_service
|
||||||
|
|
||||||
- name: Create Kernos group
|
- name: Create Kernos group
|
||||||
become: true
|
become: true
|
||||||
ansible.builtin.group:
|
ansible.builtin.group:
|
||||||
|
|||||||
@@ -12,15 +12,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"angelia": {
|
"angelia": {
|
||||||
"url": "{{angelia_mcp_url}}",
|
"url": "https://ouranos.helu.ca/mcp/sse/",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer {{angelia_mcp_auth}}"
|
"Authorization": "Bearer LmDTU1OoQm7nk8-T7NtGwwA5aut7LqcpVYpLxRKUS51klljJkFUbmu3KYnR8V6Ww"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"argos": {
|
"argos": {
|
||||||
"type": "streamable_http",
|
"type": "streamable_http",
|
||||||
"url": "{{argos_mcp_url}}"
|
"url": "{{argos_mcp_url}}"
|
||||||
},
|
},
|
||||||
|
"athena": {
|
||||||
|
"url": "https://athena.ouranos.helu.ca/mcp/sse/",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwidXNlcl9pZCI6MiwidXNlcm5hbWUiOiJyQGhlbHUuY2EiLCJpc3MiOiJhdGhlbmEiLCJhdWQiOiJhdGhlbmEtbWNwIiwiaWF0IjoxNzczODc4MDgwLCJrZXlfbmFtZSI6Ik1pcmFuZGEgTUNQTyBLZXkiLCJ0ZW5hbnRfaWQiOjF9.bpFKRbfEygKOW6_UlfQ7H5ZZZ5-LgMJ2UP653GhpZ5A"
|
||||||
|
},
|
||||||
"caliban": {
|
"caliban": {
|
||||||
"type": "streamable_http",
|
"type": "streamable_http",
|
||||||
"url": "{{caliban_mcp_url}}"
|
"url": "{{caliban_mcp_url}}"
|
||||||
@@ -29,20 +34,6 @@
|
|||||||
"type": "streamable_http",
|
"type": "streamable_http",
|
||||||
"url": "{{gitea_mcp_url}}"
|
"url": "{{gitea_mcp_url}}"
|
||||||
},
|
},
|
||||||
"github": {
|
|
||||||
"type": "streamable_http",
|
|
||||||
"url": "https://api.githubcopilot.com/mcp/",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer {{github_personal_access_token}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"huggingface": {
|
|
||||||
"type": "streamable_http",
|
|
||||||
"url": "https://huggingface.co/mcp",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer {{huggingface_mcp_token}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"korax": {
|
"korax": {
|
||||||
"type": "streamable_http",
|
"type": "streamable_http",
|
||||||
"url": "{{korax_mcp_url}}"
|
"url": "{{korax_mcp_url}}"
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
---
|
---
|
||||||
- name: Deploy MCPO as a system service
|
- name: Deploy MCPO as a system service
|
||||||
hosts: mcpo
|
hosts: mcpo
|
||||||
|
vars:
|
||||||
|
ansible_common_remote_group: "{{mcpo_group}}"
|
||||||
|
allow_world_readable_tmpfiles: true
|
||||||
handlers:
|
handlers:
|
||||||
- name: restart mcpo
|
- name: restart mcpo
|
||||||
become: true
|
become: true
|
||||||
@@ -39,6 +42,7 @@
|
|||||||
mode: '750'
|
mode: '750'
|
||||||
|
|
||||||
- name: Check if config.json exists
|
- name: Check if config.json exists
|
||||||
|
become: true
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "{{mcpo_directory}}/config.json"
|
path: "{{mcpo_directory}}/config.json"
|
||||||
register: config_file
|
register: config_file
|
||||||
|
|||||||
@@ -423,7 +423,7 @@
|
|||||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048
|
||||||
-keyout {{pplg_haproxy_cert_path}}
|
-keyout {{pplg_haproxy_cert_path}}
|
||||||
-out {{pplg_haproxy_cert_path}}
|
-out {{pplg_haproxy_cert_path}}
|
||||||
-subj "/C=US/ST=State/L=City/O=Agathos/CN=*.{{pplg_haproxy_domain}}"
|
-subj "/C=US/ST=State/L=City/O=Ouranos/CN=*.{{pplg_haproxy_domain}}"
|
||||||
-addext "subjectAltName=DNS:*.{{pplg_haproxy_domain}},DNS:{{pplg_haproxy_domain}}"
|
-addext "subjectAltName=DNS:*.{{pplg_haproxy_domain}},DNS:{{pplg_haproxy_domain}}"
|
||||||
when: "'titania.incus' not in groups['ubuntu']"
|
when: "'titania.incus' not in groups['ubuntu']"
|
||||||
args:
|
args:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# Incus configuration (should match terraform.tfvars)
|
# Incus configuration (should match terraform.tfvars)
|
||||||
storage_pool: default
|
storage_pool: default
|
||||||
project_name: agathos
|
project_name: ouranos
|
||||||
bucket_role: admin
|
bucket_role: admin
|
||||||
|
|
||||||
# Service-specific variables (must be provided)
|
# Service-specific variables (must be provided)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Role metadata and dependencies
|
# Role metadata and dependencies
|
||||||
|
|
||||||
galaxy_info:
|
galaxy_info:
|
||||||
author: Agathos Project
|
author: Ouranos Project
|
||||||
description: Manages Incus S3-compatible storage buckets with Ansible Vault credential storage
|
description: Manages Incus S3-compatible storage buckets with Ansible Vault credential storage
|
||||||
license: MIT
|
license: MIT
|
||||||
min_ansible_version: "2.9"
|
min_ansible_version: "2.9"
|
||||||
|
|||||||
32
ansible/rommie/.env.j2
Normal file
32
ansible/rommie/.env.j2
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Rommie Environment Configuration
|
||||||
|
# MCP server wrapping Agent S for GUI automation
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Required for Agent S
|
||||||
|
# ============================================================================
|
||||||
|
HF_TOKEN=0000
|
||||||
|
OPENAI_API_KEY=0000
|
||||||
|
DISPLAY={{ rommie_display }}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Agent S Model Configuration
|
||||||
|
# ============================================================================
|
||||||
|
ROMMIE_MODEL={{ rommie_model }}
|
||||||
|
ROMMIE_MODEL_URL={{ rommie_model_url }}
|
||||||
|
ROMMIE_PROVIDER={{ rommie_provider | default('openai') }}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Grounding Model Configuration
|
||||||
|
# ============================================================================
|
||||||
|
ROMMIE_GROUND_PROVIDER={{ rommie_ground_provider | default('huggingface') }}
|
||||||
|
ROMMIE_GROUND_URL={{ rommie_ground_url }}
|
||||||
|
ROMMIE_GROUND_MODEL={{ rommie_ground_model }}
|
||||||
|
ROMMIE_GROUNDING_WIDTH={{ rommie_grounding_width | default(1024) }}
|
||||||
|
ROMMIE_GROUNDING_HEIGHT={{ rommie_grounding_height | default(1024) }}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Server Configuration
|
||||||
|
# ============================================================================
|
||||||
|
ROMMIE_HOST={{ rommie_host | default('0.0.0.0') }}
|
||||||
|
ROMMIE_PORT={{ rommie_port }}
|
||||||
|
ROMMIE_ALLOWED_HOSTS={{ rommie_allowed_hosts }}
|
||||||
94
ansible/rommie/deploy.yml
Normal file
94
ansible/rommie/deploy.yml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
---
|
||||||
|
- name: Deploy Agent S (Rommie dependency)
|
||||||
|
import_playbook: ../agent_s/deploy.yml
|
||||||
|
|
||||||
|
- name: Deploy Rommie MCP Server
|
||||||
|
hosts: rommie
|
||||||
|
become: yes
|
||||||
|
vars:
|
||||||
|
rommie_venv: "/home/{{principal_user}}/env/rommie"
|
||||||
|
rommie_repo: "/home/{{principal_user}}/rommie"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Create Rommie application directory
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
file:
|
||||||
|
path: "{{rommie_repo}}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Transfer and extract Rommie
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "~/rel/rommie_{{rommie_rel}}.tar"
|
||||||
|
dest: "{{rommie_repo}}"
|
||||||
|
|
||||||
|
- name: Create Rommie virtual environment
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
command: python3 -m venv --system-site-packages {{rommie_venv}}
|
||||||
|
args:
|
||||||
|
creates: "{{rommie_venv}}/bin/activate"
|
||||||
|
|
||||||
|
- name: Install gui-agents (ignore upstream Python version cap)
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
command: >
|
||||||
|
{{rommie_venv}}/bin/pip install
|
||||||
|
--ignore-requires-python
|
||||||
|
"gui-agents>=0.3.1"
|
||||||
|
args:
|
||||||
|
creates: "{{rommie_venv}}/lib/python3.13/site-packages/gui_agents"
|
||||||
|
|
||||||
|
- name: Install Rommie into virtual environment
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
pip:
|
||||||
|
name: "{{rommie_repo}}"
|
||||||
|
extra_args: "-e"
|
||||||
|
virtualenv: "{{rommie_venv}}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Deploy Rommie environment file
|
||||||
|
become_user: "{{principal_user}}"
|
||||||
|
template:
|
||||||
|
src: .env.j2
|
||||||
|
dest: "{{rommie_repo}}/.env"
|
||||||
|
mode: '0600'
|
||||||
|
|
||||||
|
- name: Deploy Rommie systemd service
|
||||||
|
template:
|
||||||
|
src: rommie.service.j2
|
||||||
|
dest: /etc/systemd/system/rommie.service
|
||||||
|
mode: '0644'
|
||||||
|
notify:
|
||||||
|
- Reload systemd
|
||||||
|
- Restart rommie
|
||||||
|
|
||||||
|
- name: Enable and start Rommie service
|
||||||
|
systemd:
|
||||||
|
name: rommie
|
||||||
|
enabled: yes
|
||||||
|
state: started
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Flush handlers before health check
|
||||||
|
meta: flush_handlers
|
||||||
|
|
||||||
|
- name: Verify Rommie MCP endpoint is reachable
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "http://localhost:{{rommie_port}}/mcp"
|
||||||
|
method: GET
|
||||||
|
status_code: [200, 406]
|
||||||
|
timeout: 15
|
||||||
|
register: rommie_health
|
||||||
|
retries: 5
|
||||||
|
delay: 3
|
||||||
|
until: rommie_health.status in [200, 406]
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: Reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
|
|
||||||
|
- name: Restart rommie
|
||||||
|
systemd:
|
||||||
|
name: rommie
|
||||||
|
state: restarted
|
||||||
18
ansible/rommie/rommie.service.j2
Normal file
18
ansible/rommie/rommie.service.j2
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rommie MCP Server (Agent S GUI Automation)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ principal_user }}
|
||||||
|
WorkingDirectory=/home/{{ principal_user }}/rommie
|
||||||
|
EnvironmentFile=/home/{{ principal_user }}/rommie/.env
|
||||||
|
ExecStart=/home/{{ principal_user }}/env/rommie/bin/rommie
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=rommie
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Stop Agathos Sandbox Uranian Hosts
|
- name: Stop Ouranos Sandbox Uranian Hosts
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars:
|
vars:
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- name: Stop Uranian host containers
|
- name: Stop Uranian host containers
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: incus stop {{ item }} --project agathos
|
cmd: incus stop {{ item }} --project ouranos
|
||||||
loop: "{{ uranian_hosts }}"
|
loop: "{{ uranian_hosts }}"
|
||||||
register: stop_result
|
register: stop_result
|
||||||
failed_when: stop_result.rc != 0 and 'not running' not in stop_result.stderr
|
failed_when: stop_result.rc != 0 and 'not running' not in stop_result.stderr
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Start Agathos Sandbox Uranian Hosts
|
- name: Start Ouranos Sandbox Uranian Hosts
|
||||||
hosts: localhost
|
hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
vars:
|
vars:
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- name: Start Uranian host containers
|
- name: Start Uranian host containers
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: incus start {{ item }} --project agathos
|
cmd: incus start {{ item }} --project ouranos
|
||||||
loop: "{{ uranian_hosts }}"
|
loop: "{{ uranian_hosts }}"
|
||||||
register: start_result
|
register: start_result
|
||||||
failed_when: start_result.rc != 0 and 'already running' not in start_result.stderr
|
failed_when: start_result.rc != 0 and 'already running' not in start_result.stderr
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Service Documentation Template
|
# Service Documentation Template
|
||||||
|
|
||||||
This is a template for documenting services deployed in the Agathos sandbox. Copy this file and replace placeholders with service-specific information.
|
This is a template for documenting services deployed in the Ouranos sandbox. Copy this file and replace placeholders with service-specific information.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Ansible Project Structure - Best Practices
|
# Ansible Project Structure - Best Practices
|
||||||
|
|
||||||
This document describes the clean, maintainable Ansible structure implemented in the Agathos project. Use this as a reference template for other Ansible projects.
|
This document describes the clean, maintainable Ansible structure implemented in the Ouranos project. Use this as a reference template for other Ansible projects.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -661,17 +661,17 @@ casdoor_s3_region: "us-phoenix-1"
|
|||||||
**OCI Vault Organization**:
|
**OCI Vault Organization**:
|
||||||
```
|
```
|
||||||
OCI Compartment: production
|
OCI Compartment: production
|
||||||
├── Vault: agathos-databases
|
├── Vault: ouranos-databases
|
||||||
│ ├── Secret: postgres-admin-password
|
│ ├── Secret: postgres-admin-password
|
||||||
│ └── Secret: casdoor-db-password
|
│ └── Secret: casdoor-db-password
|
||||||
│
|
│
|
||||||
├── Vault: agathos-services
|
├── Vault: ouranos-services
|
||||||
│ ├── Secret: casdoor-s3-access-key
|
│ ├── Secret: casdoor-s3-access-key
|
||||||
│ ├── Secret: casdoor-s3-secret-key
|
│ ├── Secret: casdoor-s3-secret-key
|
||||||
│ ├── Secret: casdoor-s3-bucket
|
│ ├── Secret: casdoor-s3-bucket
|
||||||
│ └── Secret: openwebui-db-password
|
│ └── Secret: openwebui-db-password
|
||||||
│
|
│
|
||||||
└── Vault: agathos-integrations
|
└── Vault: ouranos-integrations
|
||||||
├── Secret: apikey-openai
|
├── Secret: apikey-openai
|
||||||
└── Secret: apikey-anthropic
|
└── Secret: apikey-anthropic
|
||||||
```
|
```
|
||||||
@@ -713,7 +713,7 @@ ansible-playbook remove_s3.yml -e bucket_name=casdoor -e service_name=casdoor
|
|||||||
- Incus CLI must be configured and accessible
|
- Incus CLI must be configured and accessible
|
||||||
|
|
||||||
**What Gets Created**:
|
**What Gets Created**:
|
||||||
1. Incus storage bucket in project `agathos`, pool `default`
|
1. Incus storage bucket in project `ouranos`, pool `default`
|
||||||
2. Admin access key for the bucket
|
2. Admin access key for the bucket
|
||||||
3. Encrypted vault entries: `vault_<service>_s3_access_key`, `vault_<service>_s3_secret_key`, `vault_<service>_s3_bucket`
|
3. Encrypted vault entries: `vault_<service>_s3_access_key`, `vault_<service>_s3_secret_key`, `vault_<service>_s3_bucket`
|
||||||
|
|
||||||
@@ -764,5 +764,5 @@ src: "{{playbook_dir}}/{{inventory_hostname_short}}/config.j2"
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: December 2025
|
**Last Updated**: December 2025
|
||||||
**Project**: Agathos Infrastructure
|
**Project**: Ouranos Infrastructure
|
||||||
**Approval**: Red Panda Approved™
|
**Approval**: Red Panda Approved™
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ No Terraform changes required—AnythingLLM uses port 22084 within Rosalind's ex
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ansible
|
cd ansible
|
||||||
source ~/env/agathos/bin/activate
|
source ~/env/ouranos/bin/activate
|
||||||
|
|
||||||
# Deploy PostgreSQL database first (if not already done)
|
# Deploy PostgreSQL database first (if not already done)
|
||||||
ansible-playbook postgresql/deploy.yml
|
ansible-playbook postgresql/deploy.yml
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Casdoor SSO Identity Provider
|
# Casdoor SSO Identity Provider
|
||||||
|
|
||||||
Casdoor provides Single Sign-On (SSO) authentication for Agathos services. This document covers the design decisions, architecture, and deployment procedures.
|
Casdoor provides Single Sign-On (SSO) authentication for Ouranos services. This document covers the design decisions, architecture, and deployment procedures.
|
||||||
|
|
||||||
## Design Philosophy
|
## Design Philosophy
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ This playbook deploys certbot with the Namecheap DNS plugin for DNS-01 validatio
|
|||||||
|
|
||||||
### Titania (ouranos.helu.ca)
|
### Titania (ouranos.helu.ca)
|
||||||
|
|
||||||
Production deployment providing Let's Encrypt certificates for the Agathos sandbox HAProxy reverse proxy.
|
Production deployment providing Let's Encrypt certificates for the Ouranos sandbox HAProxy reverse proxy.
|
||||||
|
|
||||||
| Setting | Value |
|
| Setting | Value |
|
||||||
|---------|-------|
|
|---------|-------|
|
||||||
|
|||||||
448
docs/certbot_internal_hosts.md
Normal file
448
docs/certbot_internal_hosts.md
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
# Let's Encrypt for Internal Production Hosts
|
||||||
|
|
||||||
|
Centralized certificate management using an OCI free-tier host running certbot with DNS-01 validation, OCI Vault for secure storage, and Ansible-driven distribution to internal production hosts.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
| Component | Value |
|
||||||
|
|-----------|-------|
|
||||||
|
| Certificate Authority | Let's Encrypt |
|
||||||
|
| Validation | DNS-01 via `certbot-dns-namecheap` |
|
||||||
|
| Certificate Generator | OCI free-tier host (certbot) |
|
||||||
|
| Certificate Store | OCI Vault (software-protected keys, free) |
|
||||||
|
| Distribution | Ansible playbook on controller (cron) |
|
||||||
|
| Target Hosts | pan.helu.ca, nyx.helu.ca (extensible) |
|
||||||
|
| Monitoring | Prometheus metrics + Grafana dashboard |
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Self-signed and private certificates on internal production hosts (pan.helu.ca, nyx.helu.ca) cause persistent issues:
|
||||||
|
|
||||||
|
- Clients must disable TLS verification or trust custom CAs
|
||||||
|
- Service-to-service HTTPS (e.g., LobeChat → MinIO S3 at `https://pan.helu.ca:8555`) requires trust workarounds
|
||||||
|
- Certificate management is manual and inconsistent across environments
|
||||||
|
- No automated renewal or expiry monitoring
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ OCI Free Host │
|
||||||
|
│ - certbot + dns-namecheap │
|
||||||
|
│ - systemd timer (twice daily) │
|
||||||
|
│ - post-hook: upload certs to │
|
||||||
|
│ OCI Vault via oci-cli │
|
||||||
|
└──────────────┬──────────────────┘
|
||||||
|
│ oci vault secret update-secret-content
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ OCI Vault │
|
||||||
|
│ ouranos-certificates │
|
||||||
|
│ ├── pan-helu-ca-fullchain │
|
||||||
|
│ ├── pan-helu-ca-privkey │
|
||||||
|
│ ├── nyx-helu-ca-fullchain │
|
||||||
|
│ ├── nyx-helu-ca-privkey │
|
||||||
|
│ └── (future domains) │
|
||||||
|
└──────────────┬──────────────────┘
|
||||||
|
│ community.oci.oci_secret lookup
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ Ansible Controller │
|
||||||
|
│ (restricted access) │
|
||||||
|
│ - cron: cert-distribute.yml │
|
||||||
|
│ - pulls certs from OCI Vault │
|
||||||
|
│ - deploys to target hosts │
|
||||||
|
│ - updates Prometheus metrics │
|
||||||
|
└──────────┬──────────┬───────────┘
|
||||||
|
│ │
|
||||||
|
┌────▼───┐ ┌───▼────┐
|
||||||
|
│ pan │ │ nyx │
|
||||||
|
│ .helu │ │ .helu │
|
||||||
|
│ .ca │ │ .ca │
|
||||||
|
└────┬───┘ └───┬────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
Prometheus cert metrics
|
||||||
|
→ Grafana dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| Individual certs (not wildcard `*.helu.ca`) | Limits blast radius; each host has only its own cert |
|
||||||
|
| Namecheap API keys only on OCI host | Production hosts never hold DNS API credentials |
|
||||||
|
| Pull model (controller pulls from vault) | OCI host doesn't need SSH access to production |
|
||||||
|
| OCI Vault as distribution bus | Aligns with existing OCI Vault pattern in `docs/ansible.md` |
|
||||||
|
| Software-protected keys | Free tier, no per-secret or per-version charges |
|
||||||
|
|
||||||
|
## Why DNS-01
|
||||||
|
|
||||||
|
DNS-01 validation is the correct choice for internal hosts. The challenge is validated via DNS TXT records managed through the Namecheap API — the target hosts (pan, nyx) do not need to be reachable from the internet on port 80 or 443.
|
||||||
|
|
||||||
|
This is the same proven approach used on Titania for `*.ouranos.helu.ca` (see `docs/cerbot.md`).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Phase 1: OCI Free Host — Certbot
|
||||||
|
|
||||||
|
Deploy certbot on the OCI free host using the same pattern as `ansible/certbot/deploy.yml`, stripped down for minimal footprint (no HAProxy, no Docker).
|
||||||
|
|
||||||
|
**Resource requirements**: Trivial. Certbot runs briefly twice per day. The OCI free host's constrained resources are more than sufficient.
|
||||||
|
|
||||||
|
#### Certbot Setup
|
||||||
|
|
||||||
|
1. Python virtualenv with `certbot` and `certbot-dns-namecheap`
|
||||||
|
2. Namecheap credentials in `/srv/certbot/credentials/namecheap.ini` (template: `ansible/certbot/namecheap.ini.j2`)
|
||||||
|
3. Individual certificate requests per domain:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
certbot certonly \
|
||||||
|
--non-interactive \
|
||||||
|
--agree-tos \
|
||||||
|
--email webmaster@helu.ca \
|
||||||
|
--authenticator dns-namecheap \
|
||||||
|
--dns-namecheap-credentials /srv/certbot/credentials/namecheap.ini \
|
||||||
|
--dns-namecheap-propagation-seconds 120 \
|
||||||
|
--config-dir /srv/certbot/config \
|
||||||
|
--work-dir /srv/certbot/work \
|
||||||
|
--logs-dir /srv/certbot/logs \
|
||||||
|
--cert-name pan.helu.ca \
|
||||||
|
-d pan.helu.ca
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Systemd timer for renewal (same pattern as Titania — twice daily with `RandomizedDelaySec=3600`)
|
||||||
|
|
||||||
|
#### Vault Upload Post-Hook
|
||||||
|
|
||||||
|
Replaces the HAProxy reload hook used on Titania. After each renewal, base64-encodes and uploads certificate files to OCI Vault:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Post-renewal hook: upload certificates to OCI Vault
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CERT_NAME="${RENEWED_LINEAGE##*/}"
|
||||||
|
CERT_DIR="${RENEWED_LINEAGE}"
|
||||||
|
VAULT_ID="ocid1.vault.oc1..." # ouranos-certificates vault
|
||||||
|
COMPARTMENT_ID="ocid1.compartment..."
|
||||||
|
|
||||||
|
# Derive OCI secret name from cert name (pan.helu.ca → pan-helu-ca)
|
||||||
|
SECRET_PREFIX=$(echo "${CERT_NAME}" | tr '.' '-')
|
||||||
|
|
||||||
|
# Upload fullchain
|
||||||
|
oci vault secret update-secret-content \
|
||||||
|
--secret-id "$(oci vault secret list \
|
||||||
|
--compartment-id "${COMPARTMENT_ID}" \
|
||||||
|
--vault-id "${VAULT_ID}" \
|
||||||
|
--name "${SECRET_PREFIX}-fullchain" \
|
||||||
|
--query 'data[0].id' --raw-output)" \
|
||||||
|
--content-type BASE64 \
|
||||||
|
--content "$(base64 -w0 "${CERT_DIR}/fullchain.pem")"
|
||||||
|
|
||||||
|
# Upload private key
|
||||||
|
oci vault secret update-secret-content \
|
||||||
|
--secret-id "$(oci vault secret list \
|
||||||
|
--compartment-id "${COMPARTMENT_ID}" \
|
||||||
|
--vault-id "${VAULT_ID}" \
|
||||||
|
--name "${SECRET_PREFIX}-privkey" \
|
||||||
|
--query 'data[0].id' --raw-output)" \
|
||||||
|
--content-type BASE64 \
|
||||||
|
--content "$(base64 -w0 "${CERT_DIR}/privkey.pem")"
|
||||||
|
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Uploaded ${CERT_NAME} to OCI Vault"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: OCI Vault — Certificate Storage
|
||||||
|
|
||||||
|
#### Vault Organization
|
||||||
|
|
||||||
|
Extends the existing OCI Vault structure documented in `docs/ansible.md`:
|
||||||
|
|
||||||
|
```
|
||||||
|
OCI Compartment: production
|
||||||
|
├── Vault: ouranos-databases (existing)
|
||||||
|
├── Vault: ouranos-services (existing)
|
||||||
|
├── Vault: ouranos-integrations (existing)
|
||||||
|
└── Vault: ouranos-certificates (new)
|
||||||
|
├── Secret: pan-helu-ca-fullchain
|
||||||
|
├── Secret: pan-helu-ca-privkey
|
||||||
|
├── Secret: nyx-helu-ca-fullchain
|
||||||
|
├── Secret: nyx-helu-ca-privkey
|
||||||
|
└── (future domains follow same pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Naming convention**: Domain dots replaced with hyphens, suffixed with `-fullchain` or `-privkey`.
|
||||||
|
|
||||||
|
**Secret format**: Base64-encoded PEM content. OCI Vault secrets support versioning natively — every renewal creates a new version, providing automatic rollback capability and audit trail.
|
||||||
|
|
||||||
|
**Cost**: OCI Vault with software-protected keys is free. No per-secret or per-version charges. Store certs for as many domains as needed at zero cost.
|
||||||
|
|
||||||
|
#### IAM Policies
|
||||||
|
|
||||||
|
```
|
||||||
|
# OCI free host: can write certs to the certificates vault
|
||||||
|
Allow dynamic-group certbot-host to manage secrets in compartment production
|
||||||
|
where target.vault.id = '<ouranos-certificates vault OCID>'
|
||||||
|
|
||||||
|
# Ansible controller: can read certs from the certificates vault
|
||||||
|
Allow dynamic-group ansible-controller to read secrets in compartment production
|
||||||
|
where target.vault.id = '<ouranos-certificates vault OCID>'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Ansible Controller — Distribution
|
||||||
|
|
||||||
|
#### Distribution Playbook
|
||||||
|
|
||||||
|
A `cert-distribute.yml` playbook runs on the Ansible controller via cron. It uses `community.oci.oci_secret` lookups (same pattern as existing OCI Vault usage):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
- name: Distribute Let's Encrypt Certificates
|
||||||
|
hosts: certbot_targets
|
||||||
|
vars:
|
||||||
|
oci_compartment_id: "{{ vault_oci_compartment_id }}"
|
||||||
|
oci_certificates_vault_id: "{{ vault_oci_certificates_vault_id }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Retrieve fullchain from OCI Vault
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cert_fullchain: >-
|
||||||
|
{{ lookup('community.oci.oci_secret',
|
||||||
|
certbot_cert_name | replace('.', '-') ~ '-fullchain',
|
||||||
|
compartment_id=oci_compartment_id,
|
||||||
|
vault_id=oci_certificates_vault_id) | b64decode }}
|
||||||
|
|
||||||
|
- name: Retrieve private key from OCI Vault
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
cert_privkey: >-
|
||||||
|
{{ lookup('community.oci.oci_secret',
|
||||||
|
certbot_cert_name | replace('.', '-') ~ '-privkey',
|
||||||
|
compartment_id=oci_compartment_id,
|
||||||
|
vault_id=oci_certificates_vault_id) | b64decode }}
|
||||||
|
|
||||||
|
- name: Deploy certificate (HAProxy combined format)
|
||||||
|
become: true
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ cert_fullchain }}{{ cert_privkey }}"
|
||||||
|
dest: "{{ haproxy_cert_path }}"
|
||||||
|
owner: "{{ certbot_user }}"
|
||||||
|
group: "{{ haproxy_group }}"
|
||||||
|
mode: '0640'
|
||||||
|
when: certbot_cert_format | default('haproxy_combined') == 'haproxy_combined'
|
||||||
|
notify: reload services
|
||||||
|
|
||||||
|
- name: Deploy certificate (separate files)
|
||||||
|
become: true
|
||||||
|
ansible.builtin.copy:
|
||||||
|
content: "{{ item.content }}"
|
||||||
|
dest: "{{ item.dest }}"
|
||||||
|
owner: "{{ certbot_user }}"
|
||||||
|
group: "{{ certbot_group }}"
|
||||||
|
mode: '0640'
|
||||||
|
loop:
|
||||||
|
- { content: "{{ cert_fullchain }}", dest: "{{ certbot_cert_dir }}/fullchain.pem" }
|
||||||
|
- { content: "{{ cert_privkey }}", dest: "{{ certbot_cert_dir }}/privkey.pem" }
|
||||||
|
when: certbot_cert_format | default('haproxy_combined') == 'separate'
|
||||||
|
notify: reload services
|
||||||
|
|
||||||
|
- name: Update certificate metrics
|
||||||
|
become: true
|
||||||
|
ansible.builtin.command: "{{ certbot_metrics_script }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: reload services
|
||||||
|
become: true
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: reloaded
|
||||||
|
loop: "{{ certbot_reload_services | default([]) }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Host Variables (Example)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# host_vars/pan.helu.ca.yml
|
||||||
|
certbot_cert_name: pan.helu.ca
|
||||||
|
certbot_cert_format: separate # MinIO expects separate files
|
||||||
|
certbot_cert_dir: /etc/minio/certs
|
||||||
|
certbot_user: minio
|
||||||
|
certbot_group: minio
|
||||||
|
certbot_reload_services:
|
||||||
|
- minio
|
||||||
|
|
||||||
|
certbot_metrics_script: /srv/certbot/hooks/cert-metrics.sh
|
||||||
|
prometheus_node_exporter_text_directory: /var/lib/prometheus/node-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# host_vars/nyx.helu.ca.yml (if running HAProxy)
|
||||||
|
certbot_cert_name: nyx.helu.ca
|
||||||
|
certbot_cert_format: haproxy_combined
|
||||||
|
haproxy_cert_path: /etc/haproxy/certs/nyx.helu.ca.pem
|
||||||
|
certbot_user: certbot
|
||||||
|
haproxy_group: haproxy
|
||||||
|
certbot_reload_services:
|
||||||
|
- haproxy
|
||||||
|
|
||||||
|
certbot_metrics_script: /srv/certbot/hooks/cert-metrics.sh
|
||||||
|
prometheus_node_exporter_text_directory: /var/lib/prometheus/node-exporter
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Cron Schedule
|
||||||
|
|
||||||
|
```cron
|
||||||
|
# Run cert distribution every 6 hours on the Ansible controller
|
||||||
|
0 */6 * * * cd /path/to/ansible && ansible-playbook cert-distribute.yml --limit certbot_targets 2>&1 | logger -t cert-distribute
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's Encrypt certs are valid for 90 days and renew at 30 days remaining. A 6-hour distribution cadence ensures certs propagate within hours of renewal.
|
||||||
|
|
||||||
|
### Phase 4: Monitoring
|
||||||
|
|
||||||
|
#### Prometheus Metrics
|
||||||
|
|
||||||
|
Deploy the `cert-metrics.sh` script (existing template: `ansible/certbot/cert-metrics.sh.j2`) on each target host. After each distribution, it writes metrics to the node-exporter textfile directory:
|
||||||
|
|
||||||
|
| Metric | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `ssl_certificate_expiry_timestamp` | Unix timestamp when cert expires |
|
||||||
|
| `ssl_certificate_expiry_seconds` | Seconds until cert expires |
|
||||||
|
| `ssl_certificate_valid` | 1 if valid, 0 if expired/missing |
|
||||||
|
|
||||||
|
Labels: `domain`, `issuer`
|
||||||
|
|
||||||
|
#### Alert Rules
|
||||||
|
|
||||||
|
Add to `ansible/prometheus/alert_rules.yml.j2`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: ssl_alerts
|
||||||
|
rules:
|
||||||
|
- alert: SSLCertificateExpiringSoon
|
||||||
|
expr: ssl_certificate_expiry_seconds < 604800
|
||||||
|
for: 1h
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "SSL certificate expiring soon"
|
||||||
|
description: "Certificate for {{ $labels.domain }} expires in {{ $value | humanizeDuration }}"
|
||||||
|
|
||||||
|
- alert: SSLCertificateExpired
|
||||||
|
expr: ssl_certificate_valid == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "SSL certificate expired or missing"
|
||||||
|
description: "Certificate for {{ $labels.domain }} is expired or not found"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Grafana Dashboard
|
||||||
|
|
||||||
|
Add a certificate status dashboard to `ansible/grafana/dashboards/` showing:
|
||||||
|
|
||||||
|
- Certificate validity status (green/red) per domain
|
||||||
|
- Days until expiry (gauge per domain)
|
||||||
|
- Renewal history (annotation markers from cert version timestamps)
|
||||||
|
- Issuer label (confirms Let's Encrypt vs self-signed)
|
||||||
|
|
||||||
|
## Certificate Format Reference
|
||||||
|
|
||||||
|
Different services expect certificates in different formats. The distribution playbook handles this via the `certbot_cert_format` host variable:
|
||||||
|
|
||||||
|
| Format | Services | Files Produced |
|
||||||
|
|--------|----------|----------------|
|
||||||
|
| `haproxy_combined` | HAProxy | Single PEM: fullchain + privkey concatenated |
|
||||||
|
| `separate` | MinIO, nginx, most services | `fullchain.pem` + `privkey.pem` as separate files |
|
||||||
|
|
||||||
|
MinIO specifically expects certs at `~/.minio/certs/public.crt` and `~/.minio/certs/private.key`, or a custom path via `--certs-dir`.
|
||||||
|
|
||||||
|
## Let's Encrypt Rate Limits
|
||||||
|
|
||||||
|
| Limit | Value | Impact |
|
||||||
|
|-------|-------|--------|
|
||||||
|
| Certificates per registered domain | 50 per week | Individual certs for pan + nyx are well within limits |
|
||||||
|
| Duplicate certificates | 5 per week | Avoid unnecessary re-issuance |
|
||||||
|
| Failed validations | 5 per hour | DNS propagation failures count against this |
|
||||||
|
|
||||||
|
## Comparison with Titania Model
|
||||||
|
|
||||||
|
| Aspect | Titania (Current) | Centralized (This Document) |
|
||||||
|
|--------|-------------------|----------------------------|
|
||||||
|
| Certbot location | On the host itself | OCI free host |
|
||||||
|
| Namecheap credentials | On the host | Only on OCI host |
|
||||||
|
| Cert delivery | Direct to HAProxy | Via OCI Vault → Ansible |
|
||||||
|
| Renewal hook | Docker HAProxy reload | OCI Vault upload |
|
||||||
|
| Distribution | N/A (local only) | Ansible cron on controller |
|
||||||
|
| Environments served | Ouranos sandbox only | All environments |
|
||||||
|
| Service reload | `docker compose kill -s HUP` | `systemctl reload` per host_vars |
|
||||||
|
|
||||||
|
Titania can remain self-contained (it's working) or migrate to this centralized model later.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
### OCI Free Host
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check certbot managed certificates
|
||||||
|
certbot certificates --config-dir /srv/certbot/config
|
||||||
|
|
||||||
|
# Dry-run renewal
|
||||||
|
certbot renew --config-dir /srv/certbot/config \
|
||||||
|
--work-dir /srv/certbot/work \
|
||||||
|
--logs-dir /srv/certbot/logs \
|
||||||
|
--dry-run
|
||||||
|
|
||||||
|
# Check systemd timer
|
||||||
|
systemctl status certbot-renew.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### OCI Vault
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List certificate secrets
|
||||||
|
oci vault secret list \
|
||||||
|
--compartment-id $COMPARTMENT_ID \
|
||||||
|
--vault-id $VAULT_ID \
|
||||||
|
--query 'data[*].{"name":"secret-name","updated":"time-of-current-version-expiry"}' \
|
||||||
|
--output table
|
||||||
|
```
|
||||||
|
|
||||||
|
### Target Hosts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify issuer is Let's Encrypt
|
||||||
|
openssl x509 -noout -issuer -in /path/to/cert.pem
|
||||||
|
|
||||||
|
# Check expiry
|
||||||
|
openssl x509 -noout -enddate -in /path/to/cert.pem
|
||||||
|
|
||||||
|
# Test TLS connection
|
||||||
|
openssl s_client -connect pan.helu.ca:8555 </dev/null 2>/dev/null \
|
||||||
|
| openssl x509 -noout -issuer -dates
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prometheus
|
||||||
|
|
||||||
|
```promql
|
||||||
|
# All certs valid
|
||||||
|
ssl_certificate_valid == 1
|
||||||
|
|
||||||
|
# Days until expiry
|
||||||
|
ssl_certificate_expiry_seconds / 86400
|
||||||
|
|
||||||
|
# Certs expiring within 30 days
|
||||||
|
ssl_certificate_expiry_seconds < 2592000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- `docs/cerbot.md` — Titania certbot deployment (DNS-01 with Namecheap)
|
||||||
|
- `docs/ansible.md` — OCI Vault secret lookup patterns and vault organization
|
||||||
|
- `ansible/certbot/deploy.yml` — Certbot deployment playbook (base pattern)
|
||||||
|
- `ansible/certbot/renewal-hook.sh.j2` — Renewal hook template (Titania/HAProxy variant)
|
||||||
|
- `ansible/certbot/cert-metrics.sh.j2` — Prometheus metrics script template
|
||||||
|
- `ansible/certbot/namecheap.ini.j2` — Namecheap credentials template
|
||||||
|
- `ansible/prometheus/alert_rules.yml.j2` — Prometheus alert rules
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Daedalus — Deployment Requirements
|
# Daedalus — Deployment Requirements
|
||||||
|
|
||||||
All infrastructure runs within the Agathos Incus sandbox. Hosts are resolved via DNS using the `.incus` suffix.
|
All infrastructure runs within the Ouranos Incus sandbox. Hosts are resolved via DNS using the `.incus` suffix.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ backend daedalus
|
|||||||
**Requirements:**
|
**Requirements:**
|
||||||
- ACL entry in the HAProxy `frontend https` block
|
- ACL entry in the HAProxy `frontend https` block
|
||||||
- Backend definition with health check on `/api/health`
|
- Backend definition with health check on `/api/health`
|
||||||
- Casdoor application configured for `daedalus.ouranos.helu.ca` (same pattern as other Agathos services)
|
- Casdoor application configured for `daedalus.ouranos.helu.ca` (same pattern as other Ouranos services)
|
||||||
- TLS certificate covering `daedalus.ouranos.helu.ca` (wildcard or SAN)
|
- TLS certificate covering `daedalus.ouranos.helu.ca` (wildcard or SAN)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<nav class="navbar navbar-dark bg-dark rounded mb-4">
|
<nav class="navbar navbar-dark bg-dark rounded mb-4">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="agathos.html">
|
<a class="navbar-brand" href="ouranos.html">
|
||||||
<i class="bi bi-arrow-left"></i> Back to Main Documentation
|
<i class="bi bi-arrow-left"></i> Back to Main Documentation
|
||||||
</a>
|
</a>
|
||||||
<div class="navbar-nav d-flex flex-row">
|
<div class="navbar-nav d-flex flex-row">
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="agathos.html"><i class="bi bi-house-door"></i> Main Documentation</a></li>
|
<li class="breadcrumb-item"><a href="ouranos.html"><i class="bi bi-house-door"></i> Main Documentation</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page">Style Guide</li>
|
<li class="breadcrumb-item active" aria-current="page">Style Guide</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
729
docs/freecad_mcp.md
Normal file
729
docs/freecad_mcp.md
Normal file
@@ -0,0 +1,729 @@
|
|||||||
|
# FreeCAD Robust MCP Server
|
||||||
|
|
||||||
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
[](https://pypi.org/project/freecad-robust-mcp/)
|
||||||
|
[](https://pypi.org/project/freecad-robust-mcp/)
|
||||||
|
[](https://hub.docker.com/r/spkane/freecad-robust-mcp)
|
||||||
|
[](https://spkane.github.io/freecad-addon-robust-mcp-server/)
|
||||||
|
|
||||||
|
[](https://github.com/spkane/freecad-addon-robust-mcp-server/actions/workflows/test.yaml)
|
||||||
|
[](https://github.com/spkane/freecad-addon-robust-mcp-server/actions/workflows/docker.yaml)
|
||||||
|
[](https://github.com/spkane/freecad-addon-robust-mcp-server/actions/workflows/pre-commit.yaml)
|
||||||
|
[](https://github.com/spkane/freecad-addon-robust-mcp-server/actions/workflows/codeql.yaml)
|
||||||
|
|
||||||
|
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that enables integration between AI assistants (Claude, GPT, and other MCP-compatible tools) and [FreeCAD](https://www.freecadweb.org/), allowing AI-assisted development and debugging of 3D models, macros, and workbenches.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
<!--TOC-->
|
||||||
|
|
||||||
|
- [FreeCAD Robust MCP Server](#freecad-robust-mcp-server)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Installation Requirements / Dependencies](#installation-requirements--dependencies)
|
||||||
|
- [For Users](#for-users)
|
||||||
|
- [Quick Links](#quick-links)
|
||||||
|
- [Robust MCP Server](#robust-mcp-server)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Using pip (recommended)](#using-pip-recommended)
|
||||||
|
- [Using mise and just (from source)](#using-mise-and-just-from-source)
|
||||||
|
- [Using Docker](#using-docker)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Environment Variables](#environment-variables)
|
||||||
|
- [Connection Modes](#connection-modes)
|
||||||
|
- [MCP Client Configuration](#mcp-client-configuration)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Starting the MCP Bridge in FreeCAD](#starting-the-mcp-bridge-in-freecad)
|
||||||
|
- [Option A: Using the Workbench (Recommended)](#option-a-using-the-workbench-recommended)
|
||||||
|
- [Option B: Using just commands (from source)](#option-b-using-just-commands-from-source)
|
||||||
|
- [Uninstalling the MCP Bridge](#uninstalling-the-mcp-bridge)
|
||||||
|
- [Checking for Legacy Components](#checking-for-legacy-components)
|
||||||
|
- [Manual Cleanup (if needed)](#manual-cleanup-if-needed)
|
||||||
|
- [Running Modes](#running-modes)
|
||||||
|
- [XML-RPC Mode (Recommended)](#xml-rpc-mode-recommended)
|
||||||
|
- [Socket Mode (JSON-RPC)](#socket-mode-json-rpc)
|
||||||
|
- [Headless Mode](#headless-mode)
|
||||||
|
- [Embedded Mode (Linux Only)](#embedded-mode-linux-only)
|
||||||
|
- [Available Tools](#available-tools)
|
||||||
|
- [Execution & Debugging (5 tools)](#execution--debugging-5-tools)
|
||||||
|
- [Document Management (7 tools)](#document-management-7-tools)
|
||||||
|
- [Object Creation - Primitives (8 tools)](#object-creation---primitives-8-tools)
|
||||||
|
- [Object Management (12 tools)](#object-management-12-tools)
|
||||||
|
- [PartDesign - Sketching (14 tools)](#partdesign---sketching-14-tools)
|
||||||
|
- [PartDesign - Patterns & Edges (5 tools)](#partdesign---patterns--edges-5-tools)
|
||||||
|
- [View & Display (11 tools)](#view--display-11-tools)
|
||||||
|
- [Undo/Redo (3 tools)](#undoredo-3-tools)
|
||||||
|
- [Export/Import (7 tools)](#exportimport-7-tools)
|
||||||
|
- [Macro Management (6 tools)](#macro-management-6-tools)
|
||||||
|
- [Parts Library (2 tools)](#parts-library-2-tools)
|
||||||
|
- [For Developers](#for-developers)
|
||||||
|
- [Robust MCP Server Development](#robust-mcp-server-development)
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Initial Setup](#initial-setup)
|
||||||
|
- [MCP Client Configuration (Development)](#mcp-client-configuration-development)
|
||||||
|
- [Development Workflow](#development-workflow)
|
||||||
|
- [Running FreeCAD with the MCP Bridge](#running-freecad-with-the-mcp-bridge)
|
||||||
|
- [GUI Mode (recommended for development)](#gui-mode-recommended-for-development)
|
||||||
|
- [Headless Mode (for automation/CI)](#headless-mode-for-automationci)
|
||||||
|
- [Running Tests](#running-tests)
|
||||||
|
- [Code Quality](#code-quality)
|
||||||
|
- [Architecture](#architecture)
|
||||||
|
- [Acknowledgements](#acknowledgements)
|
||||||
|
- [Related Projects](#related-projects)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
<!--TOC-->
|
||||||
|
|
||||||
|
> The macros that were originally in this repo under the `/macros` directory have been permanently moved to two new GitHub repos:
|
||||||
|
>
|
||||||
|
> - [spkane/freecad-macro-cut-for-magnets](https://github.com/spkane/freecad-macro-cut-for-magnets)
|
||||||
|
> - [spkane/freecad-macro-3d-print-multi-export](https://github.com/spkane/freecad-macro-3d-print-multi-export)
|
||||||
|
|
||||||
|
**FreeCAD Forum:** [addon discussion post](https://forum.freecad.org/viewtopic.php?p=866012)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **150+ MCP Tools**: Comprehensive CAD operations including primitives, PartDesign, booleans, export
|
||||||
|
- **Multiple Connection Modes**: XML-RPC (recommended), JSON-RPC socket, or embedded
|
||||||
|
- **GUI & Headless Support**: Full modeling in headless mode, plus screenshots/colors in GUI mode
|
||||||
|
- **Macro Development**: Create, edit, run, and template FreeCAD macros via MCP
|
||||||
|
|
||||||
|
## Installation Requirements / Dependencies
|
||||||
|
|
||||||
|
- [FreeCAD](https://www.freecadweb.org/) 0.21+ or 1.0+
|
||||||
|
- Python 3.11 (required for FreeCAD ABI compatibility)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## For Users
|
||||||
|
|
||||||
|
This section covers installation and usage for end users who want to use the Robust MCP Server with AI assistants.
|
||||||
|
|
||||||
|
### Quick Links
|
||||||
|
|
||||||
|
| Resource | Description |
|
||||||
|
| ------------------------------------------------------------------------------------- | --------------------------------------------- |
|
||||||
|
| [**Documentation**](https://spkane.github.io/freecad-addon-robust-mcp-server/) | Full documentation, guides, and API reference |
|
||||||
|
| [Docker Hub](https://hub.docker.com/r/spkane/freecad-robust-mcp) | Pre-built Docker images for easy deployment |
|
||||||
|
| [PyPI](https://pypi.org/project/freecad-robust-mcp/) | Python package for pip installation |
|
||||||
|
| [GitHub Releases](https://github.com/spkane/freecad-addon-robust-mcp-server/releases) | Release archives and changelogs |
|
||||||
|
|
||||||
|
## Robust MCP Server
|
||||||
|
|
||||||
|
> **Note**: The Linux container and PyPI package are both named `freecad-robust-mcp` which differs slightly from this git repository name.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
#### Using pip (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install freecad-robust-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using mise and just (from source)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/spkane/freecad-addon-robust-mcp-server.git
|
||||||
|
cd freecad-addon-robust-mcp-server
|
||||||
|
|
||||||
|
# Install mise via the Official mise installer script (if not already installed)
|
||||||
|
curl https://mise.run | sh
|
||||||
|
|
||||||
|
mise trust
|
||||||
|
mise install
|
||||||
|
just setup
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Docker
|
||||||
|
|
||||||
|
Run the Robust MCP Server in a container. This is useful for isolated environments or when you don't want to install Python dependencies on your host.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull from Docker Hub (when published)
|
||||||
|
docker pull spkane/freecad-robust-mcp
|
||||||
|
|
||||||
|
# Or build locally
|
||||||
|
git clone https://github.com/spkane/freecad-addon-robust-mcp-server.git
|
||||||
|
cd freecad-addon-robust-mcp-server
|
||||||
|
docker build -t freecad-robust-mcp .
|
||||||
|
|
||||||
|
# Or use just commands (if you have mise/just installed)
|
||||||
|
just docker::build # Build for local architecture
|
||||||
|
just docker::build-multi # Build multi-arch (amd64 + arm64)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The containerized Robust MCP Server only supports `xmlrpc` and `socket` modes since FreeCAD runs on your host machine (not in the container). The container connects to FreeCAD via `host.docker.internal`.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| --------------------- | ---------------------------------------------------- | ----------- |
|
||||||
|
| `FREECAD_MODE` | Connection mode: `xmlrpc`, `socket`, or `embedded` | `xmlrpc` |
|
||||||
|
| `FREECAD_PATH` | Path to FreeCAD's lib directory (embedded mode only) | Auto-detect |
|
||||||
|
| `FREECAD_SOCKET_HOST` | Socket/XML-RPC server hostname | `localhost` |
|
||||||
|
| `FREECAD_SOCKET_PORT` | JSON-RPC socket server port | `9876` |
|
||||||
|
| `FREECAD_XMLRPC_PORT` | XML-RPC server port | `9875` |
|
||||||
|
| `FREECAD_TIMEOUT_MS` | Execution timeout in ms | `30000` |
|
||||||
|
|
||||||
|
#### Connection Modes
|
||||||
|
|
||||||
|
| Mode | Description | Platform Support |
|
||||||
|
| ---------- | ------------------------------------------- | --------------------------------- |
|
||||||
|
| `xmlrpc` | Connects to FreeCAD via XML-RPC (port 9875) | **All platforms** (recommended) |
|
||||||
|
| `socket` | Connects via JSON-RPC socket (port 9876) | **All platforms** |
|
||||||
|
| `embedded` | Imports FreeCAD directly into process | **Linux only** (crashes on macOS) |
|
||||||
|
|
||||||
|
**Note:** Embedded mode crashes on macOS because FreeCAD's `FreeCAD.so` links to `@rpath/libpython3.11.dylib`, which conflicts with external Python interpreters. Use `xmlrpc` or `socket` mode on macOS and Windows.
|
||||||
|
|
||||||
|
#### MCP Client Configuration
|
||||||
|
|
||||||
|
Add something like the following to your MCP client settings. For Claude Code, this is `~/.claude/claude_desktop_config.json` or a project `.mcp.json` file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"freecad": {
|
||||||
|
"command": "freecad-mcp",
|
||||||
|
"env": {
|
||||||
|
"FREECAD_MODE": "xmlrpc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If installed from source with mise/uv:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"freecad": {
|
||||||
|
"command": "/path/to/mise/shims/uv",
|
||||||
|
"args": ["run", "--project", "/path/to/freecad-addon-robust-mcp-server", "freecad-mcp"],
|
||||||
|
"env": {
|
||||||
|
"FREECAD_MODE": "xmlrpc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If using Docker:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"freecad": {
|
||||||
|
"command": "docker",
|
||||||
|
"args": [
|
||||||
|
"run", "--rm", "-i",
|
||||||
|
"--add-host=host.docker.internal:host-gateway",
|
||||||
|
"-e", "FREECAD_MODE=xmlrpc",
|
||||||
|
"-e", "FREECAD_SOCKET_HOST=host.docker.internal",
|
||||||
|
"spkane/freecad-robust-mcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker configuration notes:**
|
||||||
|
|
||||||
|
- `--rm` removes the container after it exits
|
||||||
|
- `-i` keeps stdin open for MCP communication
|
||||||
|
- `--add-host=host.docker.internal:host-gateway` allows the container to connect to FreeCAD on your host (Linux only; macOS/Windows have this built-in)
|
||||||
|
- `FREECAD_SOCKET_HOST=host.docker.internal` tells the Robust MCP Server to connect to FreeCAD on your host machine
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### Starting the MCP Bridge in FreeCAD
|
||||||
|
|
||||||
|
Before your AI assistant can connect, you need to start the MCP bridge inside FreeCAD:
|
||||||
|
|
||||||
|
##### Option A: Using the Workbench (Recommended)
|
||||||
|
|
||||||
|
1. Install the Robust MCP Bridge workbench via FreeCAD's Addon Manager:
|
||||||
|
|
||||||
|
- **Edit -> Preferences -> Addon Manager**
|
||||||
|
- Search for "Robust MCP Bridge"
|
||||||
|
- Install and restart FreeCAD
|
||||||
|
|
||||||
|
1. Start the bridge:
|
||||||
|
|
||||||
|
- Switch to the Robust MCP Bridge workbench
|
||||||
|
- Click the **Start MCP Bridge** button in the toolbar
|
||||||
|
- Or use the menu: **MCP Bridge -> Start Bridge**
|
||||||
|
|
||||||
|
1. You should see in the FreeCAD console:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MCP Bridge started!
|
||||||
|
- XML-RPC: localhost:9875
|
||||||
|
- Socket: localhost:9876
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Option B: Using just commands (from source)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start FreeCAD with MCP bridge auto-started
|
||||||
|
just freecad::run-gui
|
||||||
|
|
||||||
|
# Or for headless/automation mode:
|
||||||
|
just freecad::run-headless
|
||||||
|
```
|
||||||
|
|
||||||
|
After starting the bridge, start/restart your MCP client (Claude Code, etc.) - it will connect automatically
|
||||||
|
|
||||||
|
#### Uninstalling the MCP Bridge
|
||||||
|
|
||||||
|
To uninstall the Robust MCP Bridge workbench:
|
||||||
|
|
||||||
|
1. Open FreeCAD
|
||||||
|
1. Go to **Edit -> Preferences -> Addon Manager**
|
||||||
|
1. Find "Robust MCP Bridge" in the list
|
||||||
|
1. Click **Uninstall**
|
||||||
|
1. Restart FreeCAD
|
||||||
|
|
||||||
|
##### Checking for Legacy Components
|
||||||
|
|
||||||
|
If you previously used older versions of this project, you may have legacy components installed. Run this command to check what's installed and get cleanup instructions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just install::status
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Manual Cleanup (if needed)
|
||||||
|
|
||||||
|
Remove any legacy files that may conflict with the workbench:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS - remove legacy plugin and macro
|
||||||
|
rm -rf ~/Library/Application\ Support/FreeCAD/Mod/MCPBridge/
|
||||||
|
rm -f ~/Library/Application\ Support/FreeCAD/Macro/StartMCPBridge.FCMacro
|
||||||
|
|
||||||
|
# Linux - remove legacy plugin and macro
|
||||||
|
rm -rf ~/.local/share/FreeCAD/Mod/MCPBridge/
|
||||||
|
rm -f ~/.local/share/FreeCAD/Macro/StartMCPBridge.FCMacro
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Running Modes
|
||||||
|
|
||||||
|
##### XML-RPC Mode (Recommended)
|
||||||
|
|
||||||
|
Connects to a running FreeCAD instance via XML-RPC. Works on all platforms.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FREECAD_MODE=xmlrpc freecad-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Socket Mode (JSON-RPC)
|
||||||
|
|
||||||
|
Connects via JSON-RPC socket. Works on all platforms.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FREECAD_MODE=socket freecad-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Headless Mode
|
||||||
|
|
||||||
|
Run FreeCAD in console mode without GUI. Useful for automation.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If installed from source:
|
||||||
|
just freecad::run-headless
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Screenshot and view features are not available in headless mode.
|
||||||
|
|
||||||
|
##### Embedded Mode (Linux Only)
|
||||||
|
|
||||||
|
Runs FreeCAD in-process. **Only works on Linux** - crashes on macOS/Windows.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FREECAD_MODE=embedded freecad-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Tools
|
||||||
|
|
||||||
|
The Robust MCP Server provides **150+ tools** organized into categories. Tools marked with **GUI** require FreeCAD to be running in GUI mode; they will return an error in headless mode.
|
||||||
|
|
||||||
|
#### Execution & Debugging (5 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ---------------------------- | ------------------------------------------------------------- | ---- |
|
||||||
|
| `execute_python` | Execute arbitrary Python code in FreeCAD's context | All |
|
||||||
|
| `get_freecad_version` | Get FreeCAD version, build date, and Python version | All |
|
||||||
|
| `get_connection_status` | Check MCP bridge connection status and latency | All |
|
||||||
|
| `get_console_output` | Get recent FreeCAD console output (up to N lines) | All |
|
||||||
|
| `get_mcp_server_environment` | Get Robust MCP Server environment (OS, hostname, instance_id) | All |
|
||||||
|
|
||||||
|
#### Document Management (7 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| --------------------- | ----------------------------------------- | ---- |
|
||||||
|
| `list_documents` | List all open documents with metadata | All |
|
||||||
|
| `get_active_document` | Get information about the active document | All |
|
||||||
|
| `create_document` | Create a new FreeCAD document | All |
|
||||||
|
| `open_document` | Open an existing .FCStd file | All |
|
||||||
|
| `save_document` | Save a document to disk | All |
|
||||||
|
| `close_document` | Close a document (with optional save) | All |
|
||||||
|
| `recompute_document` | Force recomputation of all objects | All |
|
||||||
|
|
||||||
|
#### Object Creation - Primitives (8 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ----------------- | -------------------------------------------------- | ---- |
|
||||||
|
| `create_object` | Create a generic FreeCAD object by type ID | All |
|
||||||
|
| `create_box` | Create a Part::Box with length, width, height | All |
|
||||||
|
| `create_cylinder` | Create a Part::Cylinder with radius, height, angle | All |
|
||||||
|
| `create_sphere` | Create a Part::Sphere with radius | All |
|
||||||
|
| `create_cone` | Create a Part::Cone with two radii and height | All |
|
||||||
|
| `create_torus` | Create a Part::Torus (donut) with radii and angles | All |
|
||||||
|
| `create_wedge` | Create a Part::Wedge (tapered box) | All |
|
||||||
|
| `create_helix` | Create a Part::Helix curve for sweeps and threads | All |
|
||||||
|
|
||||||
|
#### Object Management (12 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ------------------- | -------------------------------------------------- | ---- |
|
||||||
|
| `list_objects` | List all objects in a document | All |
|
||||||
|
| `inspect_object` | Get detailed object info (properties, shape, etc.) | All |
|
||||||
|
| `edit_object` | Modify properties of an existing object | All |
|
||||||
|
| `delete_object` | Delete an object from a document | All |
|
||||||
|
| `set_placement` | Set object position and rotation | All |
|
||||||
|
| `scale_object` | Scale an object uniformly or non-uniformly | All |
|
||||||
|
| `rotate_object` | Rotate an object around an axis | All |
|
||||||
|
| `copy_object` | Create a copy of an object | All |
|
||||||
|
| `mirror_object` | Mirror an object across a plane (XY, XZ, YZ) | All |
|
||||||
|
| `boolean_operation` | Fuse, cut, or intersect objects | All |
|
||||||
|
| `get_selection` | Get currently selected objects | GUI |
|
||||||
|
| `set_selection` | Select specific objects by name | GUI |
|
||||||
|
| `clear_selection` | Clear all selections | GUI |
|
||||||
|
|
||||||
|
#### PartDesign - Sketching (14 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ------------------------ | ----------------------------------------------- | ---- |
|
||||||
|
| `create_partdesign_body` | Create a PartDesign::Body container | All |
|
||||||
|
| `create_sketch` | Create a sketch on a plane or face | All |
|
||||||
|
| `add_sketch_rectangle` | Add a rectangle to a sketch | All |
|
||||||
|
| `add_sketch_circle` | Add a circle to a sketch | All |
|
||||||
|
| `add_sketch_line` | Add a line (with optional construction flag) | All |
|
||||||
|
| `add_sketch_arc` | Add an arc by center, radius, and angles | All |
|
||||||
|
| `add_sketch_point` | Add a point (useful for hole centers) | All |
|
||||||
|
| `pad_sketch` | Extrude a sketch (additive) | All |
|
||||||
|
| `pocket_sketch` | Cut into solid using a sketch (subtractive) | All |
|
||||||
|
| `revolution_sketch` | Revolve a sketch around an axis (additive) | All |
|
||||||
|
| `groove_sketch` | Revolve a sketch around an axis (subtractive) | All |
|
||||||
|
| `create_hole` | Create parametric holes with optional threading | All |
|
||||||
|
| `loft_sketches` | Create a loft through multiple sketches | All |
|
||||||
|
| `sweep_sketch` | Sweep a profile along a spine path | All |
|
||||||
|
|
||||||
|
#### PartDesign - Patterns & Edges (5 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ------------------ | ------------------------------------------ | ---- |
|
||||||
|
| `linear_pattern` | Create linear pattern of a feature | All |
|
||||||
|
| `polar_pattern` | Create polar/circular pattern of a feature | All |
|
||||||
|
| `mirrored_feature` | Mirror a feature across a plane | All |
|
||||||
|
| `fillet_edges` | Add fillets (rounded edges) | All |
|
||||||
|
| `chamfer_edges` | Add chamfers (beveled edges) | All |
|
||||||
|
|
||||||
|
#### View & Display (11 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ----------------------- | ----------------------------------------------- | ---- |
|
||||||
|
| `get_screenshot` | Capture a screenshot of the 3D view | GUI |
|
||||||
|
| `set_view_angle` | Set camera to standard views (Front, Top, etc.) | GUI |
|
||||||
|
| `fit_all` | Zoom to fit all objects in view | GUI |
|
||||||
|
| `zoom_in` | Zoom in by a factor | GUI |
|
||||||
|
| `zoom_out` | Zoom out by a factor | GUI |
|
||||||
|
| `set_camera_position` | Set camera position and look-at point | GUI |
|
||||||
|
| `set_object_visibility` | Show/hide objects | GUI |
|
||||||
|
| `set_display_mode` | Set display mode (Shaded, Wireframe, etc.) | GUI |
|
||||||
|
| `set_object_color` | Set object color as RGB values | GUI |
|
||||||
|
| `list_workbenches` | List available FreeCAD workbenches | All |
|
||||||
|
| `activate_workbench` | Switch to a different workbench | All |
|
||||||
|
|
||||||
|
#### Undo/Redo (3 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ---------------------- | ---------------------------------- | ---- |
|
||||||
|
| `undo` | Undo the last operation | All |
|
||||||
|
| `redo` | Redo a previously undone operation | All |
|
||||||
|
| `get_undo_redo_status` | Get available undo/redo operations | All |
|
||||||
|
|
||||||
|
#### Export/Import (7 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ------------- | ------------------------------------------ | ---- |
|
||||||
|
| `export_step` | Export to STEP format (ISO CAD exchange) | All |
|
||||||
|
| `export_stl` | Export to STL format (3D printing) | All |
|
||||||
|
| `export_3mf` | Export to 3MF format (modern 3D printing) | All |
|
||||||
|
| `export_obj` | Export to OBJ format (Wavefront) | All |
|
||||||
|
| `export_iges` | Export to IGES format (older CAD exchange) | All |
|
||||||
|
| `import_step` | Import a STEP file | All |
|
||||||
|
| `import_stl` | Import an STL file as mesh | All |
|
||||||
|
|
||||||
|
#### Macro Management (6 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| ---------------------------- | ---------------------------------------------- | ---- |
|
||||||
|
| `list_macros` | List all available FreeCAD macros | All |
|
||||||
|
| `run_macro` | Execute a macro by name | All |
|
||||||
|
| `create_macro` | Create a new macro file | All |
|
||||||
|
| `read_macro` | Read macro source code | All |
|
||||||
|
| `delete_macro` | Delete a user macro | All |
|
||||||
|
| `create_macro_from_template` | Create macro from template (basic, part, etc.) | All |
|
||||||
|
|
||||||
|
#### Parts Library (2 tools)
|
||||||
|
|
||||||
|
| Tool | Description | Mode |
|
||||||
|
| -------------------------- | ------------------------------------- | ---- |
|
||||||
|
| `list_parts_library` | List parts in FreeCAD's parts library | All |
|
||||||
|
| `insert_part_from_library` | Insert a part from the library | All |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
This section covers development setup, contributing, and working with the codebase.
|
||||||
|
|
||||||
|
## Robust MCP Server Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [mise](https://mise.jdx.dev/) - Tool version manager
|
||||||
|
- [FreeCAD](https://www.freecadweb.org/) 0.21+ or 1.0+
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/spkane/freecad-addon-robust-mcp-server.git
|
||||||
|
cd freecad-addon-robust-mcp-server
|
||||||
|
|
||||||
|
# Install mise via the Official mise installer script (if not already installed)
|
||||||
|
curl https://mise.run | sh
|
||||||
|
|
||||||
|
# Install all tools (Python 3.11, uv, just, pre-commit)
|
||||||
|
mise trust
|
||||||
|
mise install
|
||||||
|
|
||||||
|
# Set up the development environment
|
||||||
|
just setup
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs:
|
||||||
|
|
||||||
|
- **Python 3.11** - Required for FreeCAD ABI compatibility
|
||||||
|
- **uv** - Fast Python package manager
|
||||||
|
- **just** - Command runner for development workflows
|
||||||
|
- **pre-commit** - Git hooks for code quality
|
||||||
|
|
||||||
|
### MCP Client Configuration (Development)
|
||||||
|
|
||||||
|
Create a `.mcp.json` file in the project directory:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"freecad": {
|
||||||
|
"command": "/path/to/mise/shims/uv",
|
||||||
|
"args": ["run", "--project", "/path/to/freecad-addon-robust-mcp-server", "freecad-mcp"],
|
||||||
|
"env": {
|
||||||
|
"FREECAD_MODE": "xmlrpc",
|
||||||
|
"FREECAD_SOCKET_HOST": "localhost",
|
||||||
|
"FREECAD_XMLRPC_PORT": "9875",
|
||||||
|
"PATH": "/path/to/mise/shims:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replace the paths with your actual paths:**
|
||||||
|
|
||||||
|
| Placeholder | Description | Example |
|
||||||
|
| ------------------------------------------- | ------------------------------- | ---------------------------------------------- |
|
||||||
|
| `/path/to/mise/shims/uv` | Full path to uv via mise shims | `~/.local/share/mise/shims/uv` |
|
||||||
|
| `/path/to/freecad-addon-robust-mcp-server` | Project directory | `/home/me/dev/freecad-addon-robust-mcp-server` |
|
||||||
|
| `/path/to/mise/shims` | mise shims directory for PATH | `~/.local/share/mise/shims` |
|
||||||
|
|
||||||
|
**Finding your mise shims path:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mise where uv | sed 's|/installs/.*|/shims|'
|
||||||
|
# Example: /home/user/.local/share/mise/shims (on Linux) or ~/.local/share/mise/shims (on macOS)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
Commands are organized into modules. Use `just` to see top-level commands, or `just list-<module>` to see module-specific commands.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show top-level commands and available modules
|
||||||
|
just
|
||||||
|
|
||||||
|
# Show commands in a specific module
|
||||||
|
just list-mcp # Robust MCP Server commands
|
||||||
|
just list-freecad # FreeCAD plugin/macro commands
|
||||||
|
just list-install # Installation commands
|
||||||
|
just list-quality # Code quality commands
|
||||||
|
just list-testing # Test commands
|
||||||
|
just list-docker # Docker commands
|
||||||
|
just list-documentation # Documentation commands
|
||||||
|
just list-dev # Development utilities
|
||||||
|
|
||||||
|
# List ALL commands from all modules
|
||||||
|
just list-all
|
||||||
|
|
||||||
|
# Install/update dependencies
|
||||||
|
just install::mcp-server
|
||||||
|
|
||||||
|
# Run all checks (linting, type checking, tests)
|
||||||
|
just all
|
||||||
|
|
||||||
|
# Quality commands
|
||||||
|
just quality::lint # Run ruff linter
|
||||||
|
just quality::typecheck # Run mypy type checker
|
||||||
|
just quality::format # Format code
|
||||||
|
just quality::check # Run all pre-commit hooks
|
||||||
|
|
||||||
|
# Testing commands
|
||||||
|
just testing::unit # Run unit tests
|
||||||
|
just testing::cov # Run tests with coverage
|
||||||
|
just testing::integration # Run integration tests
|
||||||
|
|
||||||
|
# Run the Robust MCP Server (or with debug logging)
|
||||||
|
just mcp::run
|
||||||
|
just mcp::run-debug
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
|
just docker::build # Build image for local architecture
|
||||||
|
just docker::build-multi # Build multi-arch image (amd64 + arm64)
|
||||||
|
just docker::run # Run container
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running FreeCAD with the MCP Bridge
|
||||||
|
|
||||||
|
#### GUI Mode (recommended for development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start FreeCAD with auto-started bridge
|
||||||
|
just freecad::run-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Headless Mode (for automation/CI)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just freecad::run-headless
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests only (no FreeCAD required)
|
||||||
|
just testing::unit
|
||||||
|
|
||||||
|
# Unit tests with coverage
|
||||||
|
just testing::cov
|
||||||
|
|
||||||
|
# Integration tests (requires running FreeCAD bridge)
|
||||||
|
just testing::integration
|
||||||
|
|
||||||
|
# Integration tests with automatic FreeCAD startup
|
||||||
|
just testing::integration-auto
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
The project uses strict code quality checks via pre-commit:
|
||||||
|
|
||||||
|
- **Ruff** - Linting and formatting
|
||||||
|
- **MyPy** - Type checking
|
||||||
|
- **Bandit** - Security scanning
|
||||||
|
- **Codespell** - Spell checking
|
||||||
|
- **Secrets scanning** - Gitleaks, detect-secrets, TruffleHog
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all pre-commit hooks
|
||||||
|
just quality::check
|
||||||
|
|
||||||
|
# Run security/secrets scans
|
||||||
|
just quality::security
|
||||||
|
just quality::secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
See the [detailed architecture document](docs/development/architecture-detailed.md) for design documentation covering:
|
||||||
|
|
||||||
|
- Module structure
|
||||||
|
- Bridge communication protocols
|
||||||
|
- Tool registration patterns
|
||||||
|
- FreeCAD plugin architecture
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
This project was developed after analyzing several existing FreeCAD Robust MCP implementations. We are grateful to these projects for their pioneering work and the ideas they contributed to the FreeCAD + AI ecosystem:
|
||||||
|
|
||||||
|
### Related Projects
|
||||||
|
|
||||||
|
- **[neka-nat/freecad-mcp](https://github.com/neka-nat/freecad-mcp)** (MIT License) - The queue-based thread safety pattern and XML-RPC protocol design (port 9875) were directly inspired by this project. Our implementation maintains protocol compatibility while being a complete rewrite with additional features.
|
||||||
|
|
||||||
|
- **[jango-blockchained/mcp-freecad](https://github.com/jango-blockchained/mcp-freecad)** - Inspired our connection recovery mechanisms and multi-mode architecture approach.
|
||||||
|
|
||||||
|
- **[contextform/freecad-mcp](https://github.com/contextform/freecad-mcp)** - Informed our comprehensive PartDesign and Part workbench tool coverage.
|
||||||
|
|
||||||
|
- **[ATOI-Ming/FreeCAD-MCP](https://github.com/ATOI-Ming/FreeCAD-MCP)** - Inspired our macro development toolkit including templates, validation, and automatic imports.
|
||||||
|
|
||||||
|
- **[bonninr/freecad_mcp](https://github.com/bonninr/freecad_mcp)** - Influenced our simple socket-based communication approach.
|
||||||
|
|
||||||
|
See [docs/COMPARISON.md](docs/COMPARISON.md) for a detailed analysis of these implementations and the design decisions they informed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ouranos Deployment
|
||||||
|
|
||||||
|
The FreeCAD Robust MCP Server runs on `caliban.incus` and is exposed via Titania's HAProxy with TLS termination.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
External Agent (e.g., Claude Desktop / MCP Switchboard)
|
||||||
|
│ MCP Protocol (Streamable HTTP, TLS)
|
||||||
|
│ https://freecad-mcp.ouranos.helu.ca/mcp
|
||||||
|
▼
|
||||||
|
Titania HAProxy (TLS termination, wildcard cert)
|
||||||
|
│ http://caliban.incus:22032/mcp
|
||||||
|
▼
|
||||||
|
FreeCAD Robust MCP Server (HTTP transport mode)
|
||||||
|
│ XML-RPC (localhost:9875)
|
||||||
|
▼
|
||||||
|
FreeCAD (GUI or headless)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration
|
||||||
|
|
||||||
|
The MCP URL is registered in `group_vars/all/vars.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
freecad_mcp_url: https://freecad-mcp.ouranos.helu.ca/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
The route is served via Titania's HAProxy using the existing `*.ouranos.helu.ca` Let's Encrypt wildcard certificate.
|
||||||
|
|
||||||
|
**To deploy:** `ansible-playbook ansible/haproxy/configure.yml`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) for details.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Gitea MCP Server - Red Panda Approved™
|
# Gitea MCP Server - Red Panda Approved™
|
||||||
|
|
||||||
Model Context Protocol (MCP) server providing programmatic access to Gitea repositories, issues, and pull requests. Deployed as a Docker container on Miranda (MCP Docker Host) in the Agathos sandbox.
|
Model Context Protocol (MCP) server providing programmatic access to Gitea repositories, issues, and pull requests. Deployed as a Docker container on Miranda (MCP Docker Host) in the Ouranos sandbox.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -612,7 +612,7 @@ The Gitea MCP Server exposes these resources and tools via the MCP protocol:
|
|||||||
|
|
||||||
The assistant can interact with Gitea repositories through natural language:
|
The assistant can interact with Gitea repositories through natural language:
|
||||||
- "List all repositories in the organization"
|
- "List all repositories in the organization"
|
||||||
- "Show me open issues in the agathos repository"
|
- "Show me open issues in the ouranos repository"
|
||||||
- "Create an issue about improving documentation"
|
- "Create an issue about improving documentation"
|
||||||
- "Search for 'ansible' in repository code"
|
- "Search for 'ansible' in repository code"
|
||||||
|
|
||||||
@@ -714,10 +714,10 @@ rate({job="syslog", container_name="gitea-mcp"} |= "error" [5m])
|
|||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
### Agathos Infrastructure
|
### Ouranos Infrastructure
|
||||||
- [Agathos Overview](agathos.md) - Complete infrastructure documentation
|
- [Ouranos Overview](ouranos.md) - Complete infrastructure documentation
|
||||||
- [Ansible Best Practices](ansible.md) - Deployment patterns and structure
|
- [Ansible Best Practices](ansible.md) - Deployment patterns and structure
|
||||||
- [Miranda Host](agathos.md#miranda---mcp-docker-host) - MCP Docker host details
|
- [Miranda Host](ouranos.md#miranda---mcp-docker-host) - MCP Docker host details
|
||||||
|
|
||||||
### Related Services
|
### Related Services
|
||||||
- [Gitea Service](gitea.md) - Gitea server deployment and configuration
|
- [Gitea Service](gitea.md) - Gitea server deployment and configuration
|
||||||
@@ -753,7 +753,7 @@ docker inspect gitea-mcp | jq '.[0].Config.Image'
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: February 2026
|
**Last Updated**: February 2026
|
||||||
**Project**: Agathos Infrastructure
|
**Project**: Ouranos Infrastructure
|
||||||
**Host**: Miranda (MCP Docker Host)
|
**Host**: Miranda (MCP Docker Host)
|
||||||
**Status**: Red Panda Approved™ ✓
|
**Status**: Red Panda Approved™ ✓
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ The name "act" comes from [nektos/act](https://github.com/nektos/act), an open-s
|
|||||||
4. Logs and status are streamed back to Gitea in real time
|
4. Logs and status are streamed back to Gitea in real time
|
||||||
5. The container is destroyed after the job completes
|
5. The container is destroyed after the job completes
|
||||||
|
|
||||||
### Architecture in Agathos
|
### Architecture in Ouranos
|
||||||
|
|
||||||
```
|
```
|
||||||
Gitea (Rosalind) Act Runner (Puck)
|
Gitea (Rosalind) Act Runner (Puck)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ The GitHub MCP server requires a **read-only Personal Access Token (PAT)** with
|
|||||||
|
|
||||||
1. Navigate to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
|
1. Navigate to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
|
||||||
2. Click "Generate new token (classic)"
|
2. Click "Generate new token (classic)"
|
||||||
3. Set name: `Agathos GitHub MCP - Read Only`
|
3. Set name: `Ouranos GitHub MCP - Read Only`
|
||||||
4. Set expiration: Custom or 90 days (recommended)
|
4. Set expiration: Custom or 90 days (recommended)
|
||||||
5. Select scopes: `public_repo`, `read:org`, `read:user`
|
5. Select scopes: `public_repo`, `read:org`, `read:user`
|
||||||
6. Click "Generate token"
|
6. Click "Generate token"
|
||||||
@@ -158,7 +158,7 @@ client = openai.OpenAI(
|
|||||||
### Deploy GitHub MCP Server
|
### Deploy GitHub MCP Server
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/robert/dv/agathos/ansible
|
cd /home/robert/dv/ouranos/ansible
|
||||||
ansible-playbook github_mcp/deploy.yml
|
ansible-playbook github_mcp/deploy.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ Useful Loki queries in Grafana:
|
|||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
✔ **Read-Only PAT** - Server uses minimal scopes, cannot modify repositories
|
✔ **Read-Only PAT** - Server uses minimal scopes, cannot modify repositories
|
||||||
✔ **Network Isolation** - Only accessible within Agathos network (miranda.incus)
|
✔ **Network Isolation** - Only accessible within Ouranos network (miranda.incus)
|
||||||
✔ **Vault Storage** - PAT stored encrypted in Ansible Vault
|
✔ **Vault Storage** - PAT stored encrypted in Ansible Vault
|
||||||
✔ **No Public Exposure** - MCP endpoint not exposed to internet
|
✔ **No Public Exposure** - MCP endpoint not exposed to internet
|
||||||
⚠️ **PAT Rotation** - Consider rotating PAT every 90 days
|
⚠️ **PAT Rotation** - Consider rotating PAT every 90 days
|
||||||
@@ -340,5 +340,5 @@ Useful Loki queries in Grafana:
|
|||||||
- [GitHub MCP Server Repository](https://github.com/github/github-mcp-server)
|
- [GitHub MCP Server Repository](https://github.com/github/github-mcp-server)
|
||||||
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
||||||
- [MCPO Documentation](https://github.com/open-webui/mcpo)
|
- [MCPO Documentation](https://github.com/open-webui/mcpo)
|
||||||
- [Agathos README](../../README.md)
|
- [Ouranos README](../../README.md)
|
||||||
- [Agathos Sandbox Documentation](../sandbox.html)
|
- [Ouranos Sandbox Documentation](../sandbox.html)
|
||||||
|
|||||||
@@ -419,4 +419,4 @@ If this fails, check:
|
|||||||
- [Grafana MCP Server](https://github.com/grafana/mcp-grafana) — Upstream project
|
- [Grafana MCP Server](https://github.com/grafana/mcp-grafana) — Upstream project
|
||||||
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
||||||
- [Ansible Practices](ansible.md)
|
- [Ansible Practices](ansible.md)
|
||||||
- [Agathos Overview](agathos.md)
|
- [Ouranos Overview](ouranos.md)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
[Home Assistant](https://github.com/home-assistant/core) is an open-source home automation platform. In the Agathos sandbox it runs as a native Python application inside a virtual environment, backed by PostgreSQL for state recording and fronted by HAProxy for TLS termination.
|
[Home Assistant](https://github.com/home-assistant/core) is an open-source home automation platform. In the Ouranos sandbox it runs as a native Python application inside a virtual environment, backed by PostgreSQL for state recording and fronted by HAProxy for TLS termination.
|
||||||
|
|
||||||
**Host:** Oberon
|
**Host:** Oberon
|
||||||
**Role:** container_orchestration
|
**Role:** container_orchestration
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Valid values for `pull`:
|
|||||||
|
|
||||||
They are independent mechanisms. The Ansible `pull` parameter runs a pull step before compose up, regardless of what the compose file says. Belt and suspenders.
|
They are independent mechanisms. The Ansible `pull` parameter runs a pull step before compose up, regardless of what the compose file says. Belt and suspenders.
|
||||||
|
|
||||||
# Agathos Fix
|
# Ouranos Fix
|
||||||
|
|
||||||
Applied to `ansible/gitea_mcp/` as the first instance. The same pattern should be applied to any service using mutable tags (`:latest`, `:stable`, etc.).
|
Applied to `ansible/gitea_mcp/` as the first instance. The same pattern should be applied to any service using mutable tags (`:latest`, `:stable`, etc.).
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ If you need to fix this manually (e.g., before running Terraform/Ansible):
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# On the HOST (pan.helu.ca), not in the container
|
# On the HOST (pan.helu.ca), not in the container
|
||||||
incus config set <container-name> raw.lxc "lxc.apparmor.profile=unconfined" --project agathos
|
incus config set <container-name> raw.lxc "lxc.apparmor.profile=unconfined" --project ouranos
|
||||||
incus restart <container-name> --project agathos
|
incus restart <container-name> --project ouranos
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Disable AppArmor for Docker inside the container
|
## Step 2: Disable AppArmor for Docker inside the container
|
||||||
|
|||||||
@@ -2,15 +2,6 @@
|
|||||||
|
|
||||||
HTTP-enabled MCP shell server using FastMCP. Wraps the existing `mcp-shell-server` execution logic with FastMCP's HTTP transport for remote AI agent access.
|
HTTP-enabled MCP shell server using FastMCP. Wraps the existing `mcp-shell-server` execution logic with FastMCP's HTTP transport for remote AI agent access.
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|----------|-------|
|
|
||||||
| **Host** | caliban.incus |
|
|
||||||
| **Port** | 22021 |
|
|
||||||
| **Service Type** | Systemd service (non-Docker) |
|
|
||||||
| **Repository** | `ssh://robert@clio.helu.ca:18677/mnt/dev/kernos` |
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **HTTP Transport**: Accessible via URL instead of stdio
|
- **HTTP Transport**: Accessible via URL instead of stdio
|
||||||
@@ -60,17 +51,17 @@ Deploys Kernos to caliban.incus:
|
|||||||
|
|
||||||
### Host Variables (`ansible/inventory/host_vars/caliban.incus.yml`)
|
### Host Variables (`ansible/inventory/host_vars/caliban.incus.yml`)
|
||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Description |
|
||||||
|----------|---------|-------------|
|
|----------|-------------|
|
||||||
| `kernos_user` | `kernos` | System user for the service |
|
| `kernos_user`| System user for the service |
|
||||||
| `kernos_group` | `kernos` | System group for the service |
|
| `kernos_group` | System group for the service |
|
||||||
| `kernos_directory` | `/srv/kernos` | Installation directory |
|
| `kernos_directory` | Installation directory |
|
||||||
| `kernos_port` | `22021` | HTTP server port |
|
| `kernos_port` | HTTP server port |
|
||||||
| `kernos_host` | `0.0.0.0` | Server bind address |
|
| `kernos_host` | Server bind address |
|
||||||
| `kernos_log_level` | `INFO` | Python log level |
|
| `kernos_log_level` | Python log level |
|
||||||
| `kernos_log_format` | `json` | Log format (`json` or `text`) |
|
| `kernos_log_format` | Log format (`json` or `text`) |
|
||||||
| `kernos_environment` | `production` | Environment name for logging |
|
| `kernos_environment` | Environment name for logging |
|
||||||
| `kernos_allow_commands` | (see below) | Comma-separated command whitelist |
|
| `kernos_allow_commands` |Comma-separated command whitelist |
|
||||||
|
|
||||||
### Global Variables (`ansible/inventory/group_vars/all/vars.yml`)
|
### Global Variables (`ansible/inventory/group_vars/all/vars.yml`)
|
||||||
|
|
||||||
@@ -110,12 +101,11 @@ The systemd service includes additional hardening:
|
|||||||
|
|
||||||
### Testing Health Endpoints
|
### Testing Health Endpoints
|
||||||
|
|
||||||
```bash
|
/health
|
||||||
curl http://caliban.incus:22021/health
|
/ready
|
||||||
curl http://caliban.incus:22021/ready
|
/live
|
||||||
curl http://caliban.incus:22021/live
|
/metrics
|
||||||
curl -H "Accept: text/plain" http://caliban.incus:22021/metrics
|
|
||||||
```
|
|
||||||
|
|
||||||
### MCP Client Connection
|
### MCP Client Connection
|
||||||
|
|
||||||
@@ -174,10 +164,10 @@ The `/metrics` endpoint exposes Prometheus-compatible metrics. Add to your Prome
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check service status
|
# Check service status
|
||||||
ssh caliban.incus sudo systemctl status kernos
|
sudo systemctl status kernos
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
ssh caliban.incus sudo journalctl -u kernos -f
|
sudo journalctl -u kernos -f
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
@@ -192,7 +182,7 @@ ssh caliban.incus sudo journalctl -u kernos -f
|
|||||||
### Health Check Failures
|
### Health Check Failures
|
||||||
|
|
||||||
1. Verify the service is running: `systemctl status kernos`
|
1. Verify the service is running: `systemctl status kernos`
|
||||||
2. Check if port 22021 is accessible
|
2. Check if port is accessible
|
||||||
3. Review logs for startup errors
|
3. Review logs for startup errors
|
||||||
|
|
||||||
### Command Execution Denied
|
### Command Execution Denied
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
MCPO is an OpenAI-compatible proxy that aggregates multiple Model Context Protocol (MCP) servers behind a single HTTP endpoint. It acts as the central MCP gateway for the Agathos sandbox, exposing tools from 13 MCP servers through a unified REST API with interactive Swagger documentation.
|
MCPO is an OpenAI-compatible proxy that aggregates multiple Model Context Protocol (MCP) servers behind a single HTTP endpoint. It acts as the central MCP gateway for the Ouranos sandbox, exposing tools from 13 MCP servers through a unified REST API with interactive Swagger documentation.
|
||||||
|
|
||||||
**Host:** miranda.incus
|
**Host:** miranda.incus
|
||||||
**Role:** MCP Docker Host
|
**Role:** MCP Docker Host
|
||||||
@@ -300,4 +300,4 @@ ssh miranda.incus "ss -tlnp | grep 25530"
|
|||||||
- **MCPO Repository**: https://github.com/nicobailey/mcpo
|
- **MCPO Repository**: https://github.com/nicobailey/mcpo
|
||||||
- **MCP Specification**: https://modelcontextprotocol.io/
|
- **MCP Specification**: https://modelcontextprotocol.io/
|
||||||
- [Ansible Practices](ansible.md)
|
- [Ansible Practices](ansible.md)
|
||||||
- [Agathos Overview](agathos.md)
|
- [Ouranos Overview](ouranos.md)
|
||||||
|
|||||||
@@ -280,4 +280,4 @@ See [Neo4j MCP documentation](#neo4j-mcp-servers) for deployment details.
|
|||||||
- [APOC Library Documentation](https://neo4j.com/labs/apoc/)
|
- [APOC Library Documentation](https://neo4j.com/labs/apoc/)
|
||||||
- [Terraform Practices](../terraform.md)
|
- [Terraform Practices](../terraform.md)
|
||||||
- [Ansible Practices](../ansible.md)
|
- [Ansible Practices](../ansible.md)
|
||||||
- [Sandbox Overview](../agathos.html)
|
- [Sandbox Overview](../ouranos.html)
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ Nextcloud requires a PostgreSQL database on Portia. This is automatically create
|
|||||||
resource "incus_storage_volume" "nextcloud_data" {
|
resource "incus_storage_volume" "nextcloud_data" {
|
||||||
name = "nextcloud-data"
|
name = "nextcloud-data"
|
||||||
pool = "default"
|
pool = "default"
|
||||||
project = "agathos"
|
project = "ouranos"
|
||||||
config = { size = "100GB" }
|
config = { size = "100GB" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ It acts as a reverse proxy that requires users to authenticate via Casdoor befor
|
|||||||
accessing the upstream service.
|
accessing the upstream service.
|
||||||
|
|
||||||
This document describes the generic approach for adding OAuth2-Proxy authentication
|
This document describes the generic approach for adding OAuth2-Proxy authentication
|
||||||
to any service in the Agathos infrastructure.
|
to any service in the Ouranos infrastructure.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ terraform apply
|
|||||||
|
|
||||||
# Start all containers
|
# Start all containers
|
||||||
cd ../ansible
|
cd ../ansible
|
||||||
source ~/env/agathos/bin/activate
|
source ~/env/ouranos/bin/activate
|
||||||
ansible-playbook sandbox_up.yml
|
ansible-playbook sandbox_up.yml
|
||||||
|
|
||||||
# Deploy all services
|
# Deploy all services
|
||||||
|
|||||||
106
docs/ouranos.md
106
docs/ouranos.md
@@ -32,15 +32,39 @@ All containers are named after moons of Uranus and resolved via the `.incus` DNS
|
|||||||
| **sycorax** | language_models | Arke LLM Proxy | ✔ |
|
| **sycorax** | language_models | Arke LLM Proxy | ✔ |
|
||||||
| **titania** | proxy_sso | HAProxy TLS termination + Casdoor SSO | ✔ |
|
| **titania** | proxy_sso | HAProxy TLS termination + Casdoor SSO | ✔ |
|
||||||
|
|
||||||
### oberon — Container Orchestration
|
### puck — Project Application Runtime
|
||||||
|
|
||||||
|
Shape-shifting trickster embodying Python's versatility.
|
||||||
|
This is the host that runs Python projects in the Ouranos sandbox.
|
||||||
|
It has an RDP server and is generally where application development happens.
|
||||||
|
Each project has a number that is used to determine port numbers.
|
||||||
|
|
||||||
|
- Docker engine
|
||||||
|
- JupyterLab (port 22071 via OAuth2-Proxy)
|
||||||
|
- Gitea Runner (CI/CD agent)
|
||||||
|
- Django Projects: Zelus (221), Angelia (222), Athena (224), Kairos (225), Icarlos (226), MCP Switchboard (227), Spelunker (228), Peitho (229), Mnemosyne (230)
|
||||||
|
- FastAgent Projects: Pallas (240)
|
||||||
|
- FastAPI Projects: Daedalus (200), Arke (201) Kernos (202), Stentor (203), Orpheus (204), Periplus (205), Nike (206)
|
||||||
|
|
||||||
|
### caliban — Agent Automation
|
||||||
|
|
||||||
|
Autonomous computer agent learning through environmental interaction.
|
||||||
|
|
||||||
|
- Docker engine
|
||||||
|
- Agent S MCP Server (MATE desktop, AT-SPI automation)
|
||||||
|
- Kernos MCP Shell Server (port 22062)
|
||||||
|
- Rommie MCP Server (port 22061) — agent-to-agent GUI automation via Agent S
|
||||||
|
- FreeCAD Robust MCP Server (port 22063) — CAD automation via FreeCAD XML-RPC
|
||||||
|
- GPU passthrough
|
||||||
|
- RDP access (port 25521)
|
||||||
|
|
||||||
|
### oberon — Container Orchestration & Dockerized Shared Services
|
||||||
|
|
||||||
King of the Fairies orchestrating containers and managing MCP infrastructure.
|
King of the Fairies orchestrating containers and managing MCP infrastructure.
|
||||||
|
|
||||||
- Docker engine
|
- Docker engine
|
||||||
- MCP Switchboard (port 22785) — Django app routing MCP tool calls
|
- MCP Switchboard (port 22781) — Django app routing MCP tool calls
|
||||||
- RabbitMQ message queue
|
- RabbitMQ message queue
|
||||||
- Open WebUI LLM interface (port 22088, PostgreSQL backend on Portia)
|
|
||||||
- SearXNG privacy search (port 22083, behind OAuth2-Proxy)
|
|
||||||
- smtp4dev SMTP test server (port 22025)
|
- smtp4dev SMTP test server (port 22025)
|
||||||
|
|
||||||
### portia — Relational Database
|
### portia — Relational Database
|
||||||
@@ -58,15 +82,16 @@ Air spirit — ethereal, interconnected nature mirroring graph relationships.
|
|||||||
- HTTP API: port 25584
|
- HTTP API: port 25584
|
||||||
- Bolt: port 25554
|
- Bolt: port 25554
|
||||||
|
|
||||||
### puck — Application Runtime
|
### miranda — MCP Docker Host
|
||||||
|
|
||||||
Shape-shifting trickster embodying Python's versatility.
|
Curious bridge between worlds — hosting MCP server containers.
|
||||||
|
|
||||||
- Docker engine
|
- Docker engine (API exposed on port 2375 for MCP Switchboard)
|
||||||
- JupyterLab (port 22071 via OAuth2-Proxy)
|
- MCPO OpenAI-compatible MCP proxy 22071
|
||||||
- Gitea Runner (CI/CD agent)
|
- Argos MCP Server — web search via SearXNG (port 22062)
|
||||||
- Home Assistant (port 8123)
|
- Grafana MCP Server (port 22063)
|
||||||
- Django applications: Angelia (22281), Athena (22481), Kairos (22581), Icarlos (22681), Spelunker (22881), Peitho (22981)
|
- Neo4j MCP Server (port 22064)
|
||||||
|
- Gitea MCP Server (port 22065)
|
||||||
|
|
||||||
### prospero — Observability Stack
|
### prospero — Observability Stack
|
||||||
|
|
||||||
@@ -79,16 +104,18 @@ Master magician observing all events.
|
|||||||
- Loki log aggregation via Alloy (all hosts)
|
- Loki log aggregation via Alloy (all hosts)
|
||||||
- Grafana dashboard suite with Casdoor SSO integration
|
- Grafana dashboard suite with Casdoor SSO integration
|
||||||
|
|
||||||
### miranda — MCP Docker Host
|
### rosalind — Third Party Applications for testing and evaluation
|
||||||
|
|
||||||
Curious bridge between worlds — hosting MCP server containers.
|
Witty and resourceful moon for PHP, Go, and Node.js runtimes.
|
||||||
|
|
||||||
- Docker engine (API exposed on port 2375 for MCP Switchboard)
|
- SearXNG privacy search (port 22083, behind OAuth2-Proxy)
|
||||||
- MCPO OpenAI-compatible MCP proxy
|
- Gitea self-hosted Git (port 22082, SSH on 22022)
|
||||||
- Grafana MCP Server (port 25533)
|
- LobeChat AI chat interface (port 22081)
|
||||||
- Gitea MCP Server (port 25535)
|
- Nextcloud file sharing and collaboration (port 22083)
|
||||||
- Neo4j MCP Server
|
- AnythingLLM document AI workspace (port 22084)
|
||||||
- Argos MCP Server — web search via SearXNG (port 25534)
|
- Nextcloud data on dedicated Incus storage volume
|
||||||
|
- Open WebUI LLM interface (port 22088, PostgreSQL backend on Portia
|
||||||
|
- Home Assistant (port 8123)
|
||||||
|
|
||||||
### sycorax — Language Models
|
### sycorax — Language Models
|
||||||
|
|
||||||
@@ -99,26 +126,6 @@ Original magical power wielding language magic.
|
|||||||
- Session management with Memcached
|
- Session management with Memcached
|
||||||
- Database backend on Portia
|
- Database backend on Portia
|
||||||
|
|
||||||
### caliban — Agent Automation
|
|
||||||
|
|
||||||
Autonomous computer agent learning through environmental interaction.
|
|
||||||
|
|
||||||
- Docker engine
|
|
||||||
- Agent S MCP Server (MATE desktop, AT-SPI automation)
|
|
||||||
- Kernos MCP Shell Server (port 22021)
|
|
||||||
- GPU passthrough for vision tasks
|
|
||||||
- RDP access (port 25521)
|
|
||||||
|
|
||||||
### rosalind — Collaboration Services
|
|
||||||
|
|
||||||
Witty and resourceful moon for PHP, Go, and Node.js runtimes.
|
|
||||||
|
|
||||||
- Gitea self-hosted Git (port 22082, SSH on 22022)
|
|
||||||
- LobeChat AI chat interface (port 22081)
|
|
||||||
- Nextcloud file sharing and collaboration (port 22083)
|
|
||||||
- AnythingLLM document AI workspace (port 22084)
|
|
||||||
- Nextcloud data on dedicated Incus storage volume
|
|
||||||
|
|
||||||
### titania — Proxy & SSO Services
|
### titania — Proxy & SSO Services
|
||||||
|
|
||||||
Queen of the Fairies managing access control and authentication.
|
Queen of the Fairies managing access control and authentication.
|
||||||
@@ -132,6 +139,21 @@ Queen of the Fairies managing access control and authentication.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Port Numbering
|
||||||
|
|
||||||
|
Well-known ports running as a service may be used: Postgresql 5432, Prometheus Metrics 9100.
|
||||||
|
|
||||||
|
However inside a docker project, the number plan needs to be followed to avoid port conflicts and confusion:
|
||||||
|
XXXYZ
|
||||||
|
XXX Project Number or 220 for external project
|
||||||
|
Y Service: 0 reserved, 1-4 flexible, 5 database, 6 MCP, 7 API, 8 Web App, 9 Prometheus metrics
|
||||||
|
Z Instance: The running instance of this app on the same host, starting at 1. May also be used to handle exceptions.
|
||||||
|
|
||||||
|
255 Incus port forwarding: Ports in ths range are forwarded from the Incus host to Incus containers (defined in Terraform)
|
||||||
|
|
||||||
|
514ZZ is the syslog port. Docker containers send their syslog to an Alloy syslog collector port. ZZ is the application instance, they just need to be different on the same host and increment from 01.
|
||||||
|
|
||||||
|
|
||||||
## External Access via HAProxy
|
## External Access via HAProxy
|
||||||
|
|
||||||
Titania provides TLS termination and reverse proxy for all services.
|
Titania provides TLS termination and reverse proxy for all services.
|
||||||
@@ -160,7 +182,7 @@ Titania provides TLS termination and reverse proxy for all services.
|
|||||||
| `kairos.ouranos.helu.ca` | puck.incus:22581 | Kairos (Django) |
|
| `kairos.ouranos.helu.ca` | puck.incus:22581 | Kairos (Django) |
|
||||||
| `lobechat.ouranos.helu.ca` | rosalind.incus:22081 | LobeChat |
|
| `lobechat.ouranos.helu.ca` | rosalind.incus:22081 | LobeChat |
|
||||||
| `loki.ouranos.helu.ca` | prospero.incus:443 (SSL) | Loki |
|
| `loki.ouranos.helu.ca` | prospero.incus:443 (SSL) | Loki |
|
||||||
| `mcp-switchboard.ouranos.helu.ca` | oberon.incus:22785 | MCP Switchboard |
|
| `mcp-switchboard.ouranos.helu.ca` | oberon.incus:22781 | MCP Switchboard |
|
||||||
| `nextcloud.ouranos.helu.ca` | rosalind.incus:22083 | Nextcloud |
|
| `nextcloud.ouranos.helu.ca` | rosalind.incus:22083 | Nextcloud |
|
||||||
| `openwebui.ouranos.helu.ca` | oberon.incus:22088 | Open WebUI |
|
| `openwebui.ouranos.helu.ca` | oberon.incus:22088 | Open WebUI |
|
||||||
| `peitho.ouranos.helu.ca` | puck.incus:22981 | Peitho (Django) |
|
| `peitho.ouranos.helu.ca` | puck.incus:22981 | Peitho (Django) |
|
||||||
@@ -185,7 +207,7 @@ terraform apply
|
|||||||
|
|
||||||
# Start all containers
|
# Start all containers
|
||||||
cd ../ansible
|
cd ../ansible
|
||||||
source ~/env/agathos/bin/activate
|
source ~/env/ouranos/bin/activate
|
||||||
ansible-playbook sandbox_up.yml
|
ansible-playbook sandbox_up.yml
|
||||||
|
|
||||||
# Deploy all services
|
# Deploy all services
|
||||||
@@ -280,7 +302,9 @@ Services with standalone deploy playbooks (not in `site.yml`):
|
|||||||
| `jupyterlab/deploy.yml` | Puck | JupyterLab + OAuth2-Proxy |
|
| `jupyterlab/deploy.yml` | Puck | JupyterLab + OAuth2-Proxy |
|
||||||
| `kernos/deploy.yml` | Caliban | Kernos MCP shell server |
|
| `kernos/deploy.yml` | Caliban | Kernos MCP shell server |
|
||||||
| `lobechat/deploy.yml` | Rosalind | LobeChat AI chat |
|
| `lobechat/deploy.yml` | Rosalind | LobeChat AI chat |
|
||||||
|
| `rommie/deploy.yml` | Caliban | Rommie MCP server (Agent S GUI automation) |
|
||||||
| `neo4j_mcp/deploy.yml` | Miranda | Neo4j MCP Server |
|
| `neo4j_mcp/deploy.yml` | Miranda | Neo4j MCP Server |
|
||||||
|
| `freecad_mcp/deploy.yml` | Caliban | FreeCAD Robust MCP Server |
|
||||||
| `rabbitmq/deploy.yml` | Oberon | RabbitMQ message queue |
|
| `rabbitmq/deploy.yml` | Oberon | RabbitMQ message queue |
|
||||||
|
|
||||||
### Lifecycle Playbooks
|
### Lifecycle Playbooks
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
PostgreSQL 17 serves as the primary relational database engine for the Agathos sandbox. There are **two separate deployment playbooks**, each targeting a different host with a distinct purpose:
|
PostgreSQL 17 serves as the primary relational database engine for the Ouranos sandbox. There are **two separate deployment playbooks**, each targeting a different host with a distinct purpose:
|
||||||
|
|
||||||
| Playbook | Host | Purpose |
|
| Playbook | Host | Purpose |
|
||||||
|----------|------|---------|
|
|----------|------|---------|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
RabbitMQ 3 (management-alpine) serves as the central message broker for the Agathos sandbox, providing AMQP-compliant message queuing for asynchronous communication between services. The deployment includes the management web interface for monitoring and administration.
|
RabbitMQ 3 (management-alpine) serves as the central message broker for the Ouranos sandbox, providing AMQP-compliant message queuing for asynchronous communication between services. The deployment includes the management web interface for monitoring and administration.
|
||||||
|
|
||||||
**Host:** Oberon (container_orchestration)
|
**Host:** Oberon (container_orchestration)
|
||||||
**Role:** Message broker for event-driven architectures
|
**Role:** Message broker for event-driven architectures
|
||||||
@@ -542,5 +542,5 @@ Each service operates in its own virtual host:
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: February 12, 2026
|
**Last Updated**: February 12, 2026
|
||||||
**Project**: Agathos Infrastructure
|
**Project**: Ouranos Infrastructure
|
||||||
**Approval**: Red Panda Approved™
|
**Approval**: Red Panda Approved™
|
||||||
|
|||||||
154
docs/rommie.md
Normal file
154
docs/rommie.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Ansible Deployment for Rommie
|
||||||
|
|
||||||
|
Rommie is an MCP server that wraps [Agent S](https://github.com/simular-ai/Agent-S), enabling agent-to-agent collaboration for GUI automation. It exposes three MCP tools — `execute_gui_task`, `get_screenshot`, and `get_agent_status` — over Streamable HTTP, allowing remote AI agents to delegate GUI tasks to the MATE desktop running on `caliban.incus`.
|
||||||
|
|
||||||
|
Named after the Andromeda Ascendant's AI avatar.
|
||||||
|
|
||||||
|
## Host
|
||||||
|
|
||||||
|
| Host | Group | Type |
|
||||||
|
|------|-------|------|
|
||||||
|
| `caliban.incus` | `rommie` | Incus container |
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Control node
|
||||||
|
|
||||||
|
- Staged release tarball in `~/rel/` (produced by `agent_s/stage.yml`):
|
||||||
|
- `~/rel/rommie_<rommie_rel>.tar`
|
||||||
|
|
||||||
|
### Target host
|
||||||
|
|
||||||
|
- Agent S fully deployed (`agent_s/deploy.yml`) — Rommie's `deploy.yml` imports it as a dependency
|
||||||
|
- MATE desktop and XRDP running (Agent S deployment provides this)
|
||||||
|
- Python 3.13 (Ubuntu 25.04)
|
||||||
|
- X11 display available at the configured `DISPLAY` value
|
||||||
|
|
||||||
|
> **Note**: `gui-agents` 0.3.x declares `Requires-Python <=3.12` in its PyPI metadata despite working on Python 3.13. The deploy playbook pre-installs it with `--ignore-requires-python` before installing Rommie.
|
||||||
|
|
||||||
|
## Staging
|
||||||
|
|
||||||
|
Rommie is staged from a local git checkout using `agent_s/stage.yml` (which creates the rommie tarball as part of the Agent S staging run). The release branch is controlled by `rommie_rel` in `group_vars/all/vars.yml` (default: `main`).
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook ansible/rommie/deploy.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
The playbook imports `agent_s/deploy.yml` first to ensure the MATE desktop and Agent S dependencies are in place, then:
|
||||||
|
|
||||||
|
1. Creates `~/rommie/` and extracts the staged tarball
|
||||||
|
2. Creates a Python venv at `~/env/rommie` with `--system-site-packages`
|
||||||
|
3. Pre-installs `gui-agents>=0.3.1` with `--ignore-requires-python`
|
||||||
|
4. Installs Rommie into the venv in editable mode (`pip install -e`)
|
||||||
|
5. Deploys `~/rommie/.env` from the template
|
||||||
|
6. Deploys and enables the `rommie.service` systemd unit
|
||||||
|
7. Health-checks `http://localhost:<rommie_port>/mcp` (retries 5×, 3 s apart)
|
||||||
|
|
||||||
|
## MCP Tools
|
||||||
|
|
||||||
|
| Tool | Concurrency | Description |
|
||||||
|
|------|-------------|-------------|
|
||||||
|
| `execute_gui_task` | Serialized (one at a time) | Execute a GUI automation task via Agent S |
|
||||||
|
| `get_screenshot` | Always available | Capture the current screen state |
|
||||||
|
| `get_agent_status` | Always available | Query task progress and agent state |
|
||||||
|
|
||||||
|
Read-only tools (`get_screenshot`, `get_agent_status`) remain available while a GUI task is running. A second `execute_gui_task` call while one is in-flight returns a "busy" error.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
External Agent (e.g., Claude Desktop / MCP Switchboard)
|
||||||
|
│ MCP Protocol (Streamable HTTP, TLS)
|
||||||
|
│ https://rommie.ouranos.helu.ca/mcp
|
||||||
|
▼
|
||||||
|
Titania HAProxy (TLS termination, wildcard cert)
|
||||||
|
│ http://caliban.incus:22031/mcp
|
||||||
|
▼
|
||||||
|
Rommie MCP Server
|
||||||
|
(serialized task execution, multi-client reads)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Agent S (gui-agents package)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MATE Desktop ← X11 display :10 ← XRDP session
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `rommie_port` | `22031` | HTTP listen port |
|
||||||
|
| `rommie_host` | `0.0.0.0` | Bind address |
|
||||||
|
| `rommie_display` | `:10` | X11 display for Agent S (XRDP assigns `:10` by default) |
|
||||||
|
| `rommie_allowed_hosts` | `caliban.incus` | Allowed Host header values |
|
||||||
|
| `rommie_model` | `Qwen3-VL-30B-A3B-Instruct-UD-Q5_K_XL.gguf` | Primary vision-language model |
|
||||||
|
| `rommie_model_url` | `http://nyx.helu.ca:22078` | Inference endpoint for the primary model |
|
||||||
|
| `rommie_provider` | `openai` | API provider for the primary model |
|
||||||
|
| `rommie_ground_provider` | `huggingface` | API provider for the grounding model |
|
||||||
|
| `rommie_ground_url` | `http://pan.helu.ca:22078` | Inference endpoint for the grounding model |
|
||||||
|
| `rommie_ground_model` | `UI-TARS-7B-DPO-Q6_K_L.gguf` | Grounding model (UI element localisation) |
|
||||||
|
| `rommie_grounding_width` | `1024` | Screenshot width passed to the grounding model |
|
||||||
|
| `rommie_grounding_height` | `1024` | Screenshot height passed to the grounding model |
|
||||||
|
| `rommie_rel` | `main` | Git branch/tag to stage from `~/git/rommie` |
|
||||||
|
|
||||||
|
All host-specific variables are set in `ansible/inventory/host_vars/caliban.incus.yml`. The `rommie_rel` default is in `ansible/inventory/group_vars/all/vars.yml`.
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
The MCP URL for Rommie is registered in `group_vars/all/vars.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rommie_mcp_url: https://rommie.ouranos.helu.ca/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Consumers (e.g., MCP Switchboard, Open WebUI, Claude Desktop) reference `{{ rommie_mcp_url }}`.
|
||||||
|
|
||||||
|
The route is served via Titania's HAProxy using the existing `*.ouranos.helu.ca` Let's Encrypt wildcard certificate. No additional certificate provisioning is required.
|
||||||
|
|
||||||
|
## Service Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
systemctl status rommie
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
systemctl restart rommie
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl -u rommie -f
|
||||||
|
```
|
||||||
|
|
||||||
|
The unit runs as `principal_user` (`robert`) and loads environment from `~/rommie/.env`. It restarts automatically on failure with a 10 s back-off.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `gui-agents` version conflict
|
||||||
|
|
||||||
|
`gui-agents` 0.3.x requires Python <=3.12 in its PyPI metadata but works on 3.13. The deploy playbook installs it with `--ignore-requires-python`. If the install step fails with a version conflict, confirm the pre-install task ran and check the venv Python version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/home/robert/env/rommie/bin/python --version
|
||||||
|
/home/robert/env/rommie/bin/pip show gui-agents
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health check fails
|
||||||
|
|
||||||
|
The playbook probes `http://localhost:22031/mcp` after starting the service. If it times out:
|
||||||
|
|
||||||
|
1. Check the service started: `systemctl status rommie`
|
||||||
|
2. Confirm the `DISPLAY` variable resolves — XRDP must have created the `:10` display before Rommie starts
|
||||||
|
3. Check logs: `journalctl -u rommie --since "5 min ago"`
|
||||||
|
|
||||||
|
### No X display
|
||||||
|
|
||||||
|
Rommie inherits `DISPLAY` from `.env`. If Agent S cannot connect to the display:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify XRDP created the display
|
||||||
|
ls /tmp/.X11-unix/
|
||||||
|
```
|
||||||
|
|
||||||
|
An active RDP session must exist or XRDP's `Xorg` daemon must be running for display `:10` to be present.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
smtp4dev is a fake SMTP server for development and testing. It accepts all incoming email without delivering it, capturing messages for inspection via a web UI and IMAP client. All services in the Agathos sandbox that send email (Casdoor, Gitea, etc.) are wired to smtp4dev so email flows can be tested without a real mail server.
|
smtp4dev is a fake SMTP server for development and testing. It accepts all incoming email without delivering it, capturing messages for inspection via a web UI and IMAP client. All services in the Ouranos sandbox that send email (Casdoor, Gitea, etc.) are wired to smtp4dev so email flows can be tested without a real mail server.
|
||||||
|
|
||||||
**Host:** Oberon (container_orchestration)
|
**Host:** Oberon (container_orchestration)
|
||||||
**Web UI Port:** 22085 → `https://smtp4dev.ouranos.helu.ca`
|
**Web UI Port:** 22085 → `https://smtp4dev.ouranos.helu.ca`
|
||||||
@@ -48,7 +48,7 @@ smtp4dev connection details are defined once in `ansible/inventory/group_vars/al
|
|||||||
| `smtp_host` | `oberon.incus` | SMTP server hostname |
|
| `smtp_host` | `oberon.incus` | SMTP server hostname |
|
||||||
| `smtp_port` | `22025` | SMTP server port |
|
| `smtp_port` | `22025` | SMTP server port |
|
||||||
| `smtp_from` | `noreply@ouranos.helu.ca` | Default sender address |
|
| `smtp_from` | `noreply@ouranos.helu.ca` | Default sender address |
|
||||||
| `smtp_from_name` | `Agathos` | Default sender display name |
|
| `smtp_from_name` | `Ouranos` | Default sender display name |
|
||||||
|
|
||||||
Any service that needs to send email references these shared variables rather than defining its own SMTP config. This means switching to a real SMTP server only requires changing `group_vars/all/vars.yml`.
|
Any service that needs to send email references these shared variables rather than defining its own SMTP config. This means switching to a real SMTP server only requires changing `group_vars/all/vars.yml`.
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ The Casdoor email provider is declared in `ansible/casdoor/init_data.json.j2` an
|
|||||||
"port": 22025,
|
"port": 22025,
|
||||||
"disableSsl": true,
|
"disableSsl": true,
|
||||||
"fromAddress": "noreply@ouranos.helu.ca",
|
"fromAddress": "noreply@ouranos.helu.ca",
|
||||||
"fromName": "Agathos"
|
"fromName": "Ouranos"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ Never rely solely on implicit resource ordering for critical infrastructure. Cod
|
|||||||
|
|
||||||
## Repository Strategy
|
## Repository Strategy
|
||||||
|
|
||||||
### Agathos (Sandbox)
|
### Ouranos (Sandbox)
|
||||||
|
|
||||||
Agathos is the **Sandbox repository** — isolated, safe for external demos, and uses local state.
|
Ouranos is the **Sandbox repository** — isolated, safe for external demos, and uses local state.
|
||||||
|
|
||||||
| Aspect | Decision |
|
| Aspect | Decision |
|
||||||
|--------|----------|
|
|--------|----------|
|
||||||
@@ -78,7 +78,7 @@ A pattern is a good module candidate when it meets these criteria:
|
|||||||
|
|
||||||
### The `incus_host` Module
|
### The `incus_host` Module
|
||||||
|
|
||||||
The standard container provisioning pattern extracted from Agathos:
|
The standard container provisioning pattern extracted from Ouranos:
|
||||||
|
|
||||||
**Inputs:**
|
**Inputs:**
|
||||||
- `hosts` — Map of host definitions (name, role, image, devices, config)
|
- `hosts` — Map of host definitions (name, role, image, devices, config)
|
||||||
@@ -123,7 +123,7 @@ Key differences in tfvars:
|
|||||||
|
|
||||||
## State Management
|
## State Management
|
||||||
|
|
||||||
### Sandbox (Agathos)
|
### Sandbox (Ouranos)
|
||||||
|
|
||||||
Local state is acceptable because:
|
Local state is acceptable because:
|
||||||
- Environment is ephemeral
|
- Environment is ephemeral
|
||||||
@@ -154,10 +154,10 @@ terraform {
|
|||||||
|
|
||||||
### Terraform → DHCP/DNS
|
### Terraform → DHCP/DNS
|
||||||
|
|
||||||
The `agathos_inventory` output provides host information for DHCP/DNS provisioning:
|
The `ouranos_inventory` output provides host information for DHCP/DNS provisioning:
|
||||||
|
|
||||||
1. Terraform creates containers with cloud-init
|
1. Terraform creates containers with cloud-init
|
||||||
2. `agathos_inventory` output includes hostnames and IPs
|
2. `ouranos_inventory` output includes hostnames and IPs
|
||||||
3. MAC addresses registered in DHCP server
|
3. MAC addresses registered in DHCP server
|
||||||
4. DHCP server creates DNS entries (`hostname.incus` domain)
|
4. DHCP server creates DNS entries (`hostname.incus` domain)
|
||||||
5. Ansible uses DNS names for host connectivity
|
5. Ansible uses DNS names for host connectivity
|
||||||
@@ -185,7 +185,7 @@ ubuntu:
|
|||||||
The `ssh_key_update.sh` script demonstrates proper integration:
|
The `ssh_key_update.sh` script demonstrates proper integration:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
terraform output -json agathos_inventory | jq -r \
|
terraform output -json ouranos_inventory | jq -r \
|
||||||
'.uranian_hosts.hosts | to_entries[] | "\(.key) \(.value.ipv4)"' | \
|
'.uranian_hosts.hosts | to_entries[] | "\(.key) \(.value.ipv4)"' | \
|
||||||
while read hostname ip; do
|
while read hostname ip; do
|
||||||
ssh-keyscan -H "$ip" >> ~/.ssh/known_hosts
|
ssh-keyscan -H "$ip" >> ~/.ssh/known_hosts
|
||||||
@@ -198,7 +198,7 @@ terraform output -json agathos_inventory | jq -r \
|
|||||||
All infrastructure changes flow through this pipeline:
|
All infrastructure changes flow through this pipeline:
|
||||||
|
|
||||||
```
|
```
|
||||||
Agathos (Sandbox)
|
Ouranos (Sandbox)
|
||||||
↓ Validate pattern works
|
↓ Validate pattern works
|
||||||
↓ Extract to module if reusable
|
↓ Extract to module if reusable
|
||||||
Dev
|
Dev
|
||||||
@@ -213,7 +213,7 @@ Prod
|
|||||||
↓ Deploy from tested artifacts
|
↓ Deploy from tested artifacts
|
||||||
```
|
```
|
||||||
|
|
||||||
**Critical:** Nothing starts in Prod. Every change originates in Agathos, is validated through the pipeline, and only then deployed to production.
|
**Critical:** Nothing starts in Prod. Every change originates in Ouranos, is validated through the pipeline, and only then deployed to production.
|
||||||
|
|
||||||
### Promotion Includes
|
### Promotion Includes
|
||||||
|
|
||||||
@@ -224,12 +224,12 @@ When promoting Terraform changes, always update corresponding:
|
|||||||
|
|
||||||
## Output Conventions
|
## Output Conventions
|
||||||
|
|
||||||
### `agathos_inventory`
|
### `ouranos_inventory`
|
||||||
|
|
||||||
The primary output for documentation and DNS integration:
|
The primary output for documentation and DNS integration:
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
output "agathos_inventory" {
|
output "ouranos_inventory" {
|
||||||
description = "Host inventory for documentation and DHCP/DNS provisioning"
|
description = "Host inventory for documentation and DHCP/DNS provisioning"
|
||||||
value = {
|
value = {
|
||||||
uranian_hosts = {
|
uranian_hosts = {
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ EOT
|
|||||||
name = "app_ports"
|
name = "app_ports"
|
||||||
type = "proxy"
|
type = "proxy"
|
||||||
properties = {
|
properties = {
|
||||||
listen = "tcp:0.0.0.0:25580-25599"
|
listen = "tcp:0.0.0.0:25590-25599"
|
||||||
connect = "tcp:127.0.0.1:25580-25599"
|
connect = "tcp:127.0.0.1:25590-25599"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@@ -114,15 +114,15 @@ EOT
|
|||||||
name = "puck_ports"
|
name = "puck_ports"
|
||||||
type = "proxy"
|
type = "proxy"
|
||||||
properties = {
|
properties = {
|
||||||
listen = "tcp:0.0.0.0:25570-25579"
|
listen = "tcp:0.0.0.0:25570-25589"
|
||||||
connect = "tcp:127.0.0.1:25570-25579"
|
connect = "tcp:127.0.0.1:25570-25589"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name = "puck_rdp"
|
name = "puck_rdp"
|
||||||
type = "proxy"
|
type = "proxy"
|
||||||
properties = {
|
properties = {
|
||||||
listen = "tcp:0.0.0.0:25520"
|
listen = "tcp:0.0.0.0:25589"
|
||||||
connect = "tcp:127.0.0.1:3389"
|
connect = "tcp:127.0.0.1:3389"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -145,7 +145,7 @@ EOT
|
|||||||
name = "caliban"
|
name = "caliban"
|
||||||
type = "proxy"
|
type = "proxy"
|
||||||
properties = {
|
properties = {
|
||||||
listen = "tcp:0.0.0.0:25521"
|
listen = "tcp:0.0.0.0:25519"
|
||||||
connect = "tcp:127.0.0.1:3389"
|
connect = "tcp:127.0.0.1:3389"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
resource "incus_project" "agathos" {
|
resource "incus_project" "ouranos" {
|
||||||
name = var.project_name
|
name = var.project_name
|
||||||
description = "Agathos Project"
|
description = "Ouranos Project"
|
||||||
remote = "local"
|
remote = "local"
|
||||||
config = {
|
config = {
|
||||||
"features.storage.volumes" = true
|
"features.storage.volumes" = true
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ output "uranian_hosts" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output "project_info" {
|
output "project_info" {
|
||||||
description = "Agathos project information"
|
description = "Ouranos project information"
|
||||||
value = {
|
value = {
|
||||||
name = incus_project.agathos.name
|
name = incus_project.ouranos.name
|
||||||
description = incus_project.agathos.description
|
description = incus_project.ouranos.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output "agathos_inventory" {
|
output "ouranos_inventory" {
|
||||||
description = "Host inventory for documentation (sandbox.html) and DHCP/DNS provisioning reference"
|
description = "Host inventory for documentation (sandbox.html) and DHCP/DNS provisioning reference"
|
||||||
value = {
|
value = {
|
||||||
uranian_hosts = {
|
uranian_hosts = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Storage Resources for Agathos Containers
|
# Storage Resources for Ouranos Containers
|
||||||
# Provisions Incus storage volumes and S3 buckets with access keys
|
# Provisions Incus storage volumes and S3 buckets with access keys
|
||||||
|
|
||||||
# Storage volume for Nextcloud data
|
# Storage volume for Nextcloud data
|
||||||
@@ -10,6 +10,8 @@ resource "incus_storage_volume" "nextcloud_data" {
|
|||||||
config = {
|
config = {
|
||||||
size = "100GB"
|
size = "100GB"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
}
|
}
|
||||||
|
|
||||||
# S3 bucket for Lobechat file storage
|
# S3 bucket for Lobechat file storage
|
||||||
@@ -18,6 +20,8 @@ resource "incus_storage_bucket" "lobechat" {
|
|||||||
pool = var.storage_pool
|
pool = var.storage_pool
|
||||||
project = var.project_name
|
project = var.project_name
|
||||||
description = "Lobechat file storage bucket"
|
description = "Lobechat file storage bucket"
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Access key for Lobechat S3 bucket
|
# Access key for Lobechat S3 bucket
|
||||||
@@ -35,6 +39,8 @@ resource "incus_storage_bucket" "casdoor" {
|
|||||||
pool = var.storage_pool
|
pool = var.storage_pool
|
||||||
project = var.project_name
|
project = var.project_name
|
||||||
description = "Casdoor file storage bucket"
|
description = "Casdoor file storage bucket"
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Access key for Casdoor S3 bucket
|
# Access key for Casdoor S3 bucket
|
||||||
@@ -52,6 +58,8 @@ resource "incus_storage_bucket" "spelunker" {
|
|||||||
pool = var.storage_pool
|
pool = var.storage_pool
|
||||||
project = var.project_name
|
project = var.project_name
|
||||||
description = "Spelunker file storage bucket"
|
description = "Spelunker file storage bucket"
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Access key for Spelunker S3 bucket
|
# Access key for Spelunker S3 bucket
|
||||||
@@ -69,6 +77,8 @@ resource "incus_storage_bucket" "daedalus" {
|
|||||||
pool = var.storage_pool
|
pool = var.storage_pool
|
||||||
project = var.project_name
|
project = var.project_name
|
||||||
description = "Daedalus file storage bucket"
|
description = "Daedalus file storage bucket"
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Access key for Daedalus S3 bucket
|
# Access key for Daedalus S3 bucket
|
||||||
@@ -80,6 +90,25 @@ resource "incus_storage_bucket_key" "daedalus_key" {
|
|||||||
role = "admin"
|
role = "admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# S3 bucket for Mnemosyne file storage
|
||||||
|
resource "incus_storage_bucket" "mnemosyne" {
|
||||||
|
name = "mnemosyne-content"
|
||||||
|
pool = var.storage_pool
|
||||||
|
project = var.project_name
|
||||||
|
description = "Mnemosyne content storage bucket"
|
||||||
|
|
||||||
|
depends_on = [incus_project.ouranos]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Access key for Mnemosyne S3 bucket
|
||||||
|
resource "incus_storage_bucket_key" "mnemosyne_key" {
|
||||||
|
name = "mnemosyne-access"
|
||||||
|
pool = incus_storage_bucket.mnemosyne.pool
|
||||||
|
storage_bucket = incus_storage_bucket.mnemosyne.name
|
||||||
|
project = var.project_name
|
||||||
|
role = "admin"
|
||||||
|
}
|
||||||
|
|
||||||
# Outputs for S3 credentials (to be stored in Ansible vault)
|
# Outputs for S3 credentials (to be stored in Ansible vault)
|
||||||
output "lobechat_s3_credentials" {
|
output "lobechat_s3_credentials" {
|
||||||
description = "Lobechat S3 bucket credentials - store in vault as vault_lobechat_s3_*"
|
description = "Lobechat S3 bucket credentials - store in vault as vault_lobechat_s3_*"
|
||||||
@@ -124,3 +153,14 @@ output "daedalus_s3_credentials" {
|
|||||||
}
|
}
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "mnemosyne_s3_credentials" {
|
||||||
|
description = "Mnemosyne S3 bucket credentials - store in vault as vault_mnemosyne_s3_*"
|
||||||
|
value = {
|
||||||
|
bucket = incus_storage_bucket.mnemosyne.name
|
||||||
|
access_key = incus_storage_bucket_key.mnemosyne_key.access_key
|
||||||
|
secret_key = incus_storage_bucket_key.mnemosyne_key.secret_key
|
||||||
|
endpoint = "https://${incus_storage_bucket.mnemosyne.location}"
|
||||||
|
}
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
variable "project_name" {
|
variable "project_name" {
|
||||||
description = "Name of the Incus project for sandbox environment"
|
description = "Name of the Incus project for sandbox environment"
|
||||||
type = string
|
type = string
|
||||||
default = "agathos"
|
default = "ouranos"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "profile_name" {
|
variable "profile_name" {
|
||||||
|
|||||||
Reference in New Issue
Block a user