fadenstack 0.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- faden_cli/__init__.py +16 -0
- faden_cli/__main__.py +9 -0
- faden_cli/bundle/shared/.gitignore +25 -0
- faden_cli/bundle/shared/LICENSE +187 -0
- faden_cli/bundle/shared/NOTICE +15 -0
- faden_cli/bundle/shared/OFFICIAL.md +9 -0
- faden_cli/bundle/shared/README.md +16 -0
- faden_cli/bundle/shared/agent-binaries/.gitignore +5 -0
- faden_cli/bundle/shared/alloy/config.alloy +40 -0
- faden_cli/bundle/shared/clickhouse/config.d/system-logs.xml +26 -0
- faden_cli/bundle/shared/docker-compose.gpu.yaml +34 -0
- faden_cli/bundle/shared/docker-compose.yaml +998 -0
- faden_cli/bundle/shared/grafana/dashboards/templates/vllm-runtime.json +2099 -0
- faden_cli/bundle/shared/grafana/provisioning/dashboards/dashboard.yml +10 -0
- faden_cli/bundle/shared/grafana/provisioning/dashboards/json/faden-overview.json +211 -0
- faden_cli/bundle/shared/grafana/provisioning/datasources/ds.yaml +9 -0
- faden_cli/bundle/shared/grafana/provisioning/datasources/prometheus.yaml +15 -0
- faden_cli/bundle/shared/initdb/01-init.sql +32 -0
- faden_cli/bundle/shared/loki/local-config.yaml +32 -0
- faden_cli/bundle/shared/nginx/locations.conf +245 -0
- faden_cli/bundle/shared/nginx/maintenance.html +239 -0
- faden_cli/bundle/shared/nginx/nginx-tls.conf +79 -0
- faden_cli/bundle/shared/nginx/nginx.conf +44 -0
- faden_cli/bundle/shared/nginx/rate-limits.conf +42 -0
- faden_cli/bundle/shared/nginx/security-headers.conf +22 -0
- faden_cli/bundle/shared/nginx/select-config.sh +37 -0
- faden_cli/bundle/shared/prometheus/prometheus.yml +40 -0
- faden_cli/bundle/shared/rabbitmq/definitions.json.example +37 -0
- faden_cli/bundle/shared/tls/README.md +30 -0
- faden_cli/cli.py +158 -0
- faden_cli/commands/__init__.py +1 -0
- faden_cli/commands/admin.py +161 -0
- faden_cli/commands/backup.py +159 -0
- faden_cli/commands/config.py +146 -0
- faden_cli/commands/deploy.py +949 -0
- faden_cli/commands/dev/__init__.py +1 -0
- faden_cli/commands/dev/dev_doctor.py +46 -0
- faden_cli/commands/dev/dev_down.py +241 -0
- faden_cli/commands/dev/dev_group.py +24 -0
- faden_cli/commands/dev/dev_init.py +710 -0
- faden_cli/commands/dev/dev_pull.py +297 -0
- faden_cli/commands/dev/dev_status.py +151 -0
- faden_cli/commands/dev/dev_up.py +1392 -0
- faden_cli/commands/doctor.py +225 -0
- faden_cli/commands/down.py +75 -0
- faden_cli/commands/logs_cmd.py +41 -0
- faden_cli/commands/maintenance.py +74 -0
- faden_cli/commands/module.py +113 -0
- faden_cli/commands/observe.py +95 -0
- faden_cli/commands/ports.py +91 -0
- faden_cli/commands/rebuild.py +158 -0
- faden_cli/commands/restore.py +139 -0
- faden_cli/commands/runtime_images.py +174 -0
- faden_cli/commands/status.py +71 -0
- faden_cli/commands/tls.py +722 -0
- faden_cli/commands/tune.py +37 -0
- faden_cli/commands/up.py +64 -0
- faden_cli/commands/upgrade.py +470 -0
- faden_cli/commands/version.py +78 -0
- faden_cli/core/__init__.py +1 -0
- faden_cli/core/api_client.py +77 -0
- faden_cli/core/backup.py +717 -0
- faden_cli/core/bootstrap.py +291 -0
- faden_cli/core/bundle.py +110 -0
- faden_cli/core/clickhouse.py +61 -0
- faden_cli/core/compose.py +484 -0
- faden_cli/core/console.py +106 -0
- faden_cli/core/detect.py +580 -0
- faden_cli/core/dev_modules.py +149 -0
- faden_cli/core/docker_env.py +147 -0
- faden_cli/core/edge.py +94 -0
- faden_cli/core/env_gen.py +598 -0
- faden_cli/core/git.py +219 -0
- faden_cli/core/install.py +288 -0
- faden_cli/core/installation.py +186 -0
- faden_cli/core/local_node.py +719 -0
- faden_cli/core/maintenance.py +82 -0
- faden_cli/core/private_files.py +133 -0
- faden_cli/core/registry.py +574 -0
- faden_cli/core/release_feed.py +342 -0
- faden_cli/core/rmq.py +135 -0
- faden_cli/core/runtime_images.py +309 -0
- faden_cli/core/schedule.py +278 -0
- faden_cli/core/settings.py +159 -0
- faden_cli/core/sysinfo.py +303 -0
- faden_cli/core/tls.py +790 -0
- faden_cli/core/tune.py +154 -0
- faden_cli/core/update_check.py +254 -0
- faden_cli/core/workspace.py +155 -0
- faden_cli/templates/env.j2 +33 -0
- faden_cli/tui/__init__.py +1 -0
- faden_cli/tui/widgets/__init__.py +1 -0
- faden_cli/tui/wizard/__init__.py +1 -0
- fadenstack-0.4.2.dist-info/METADATA +304 -0
- fadenstack-0.4.2.dist-info/RECORD +99 -0
- fadenstack-0.4.2.dist-info/WHEEL +4 -0
- fadenstack-0.4.2.dist-info/entry_points.txt +3 -0
- fadenstack-0.4.2.dist-info/licenses/LICENSE +187 -0
- fadenstack-0.4.2.dist-info/licenses/NOTICE +15 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
"""Central registry — single source of truth for Fadenstack metadata.
|
|
2
|
+
|
|
3
|
+
Every component (repos, ports, modules, services, databases, dev env vars)
|
|
4
|
+
is defined exactly **once** here. Consumer modules import from this file
|
|
5
|
+
instead of hard-coding values.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── GitHub organisation ───────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
GITHUB_ORG = "fadenstack"
|
|
17
|
+
CORE_REPO = "fadenstack"
|
|
18
|
+
|
|
19
|
+
# ── Container registry ───────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
DEFAULT_REGISTRY = "ghcr.io/fadenstack"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ── Repository definitions ───────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class RepoInfo:
|
|
29
|
+
"""Metadata for a single Fadenstack repository."""
|
|
30
|
+
|
|
31
|
+
github_name: str # e.g. "faden-backend"
|
|
32
|
+
local_dir: str # e.g. "backend"
|
|
33
|
+
description: str = ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
REPOS: list[RepoInfo] = [
|
|
37
|
+
# ── Core platform ─────────────────────────────────────────
|
|
38
|
+
RepoInfo("faden-backend", "backend", "Backend API server"),
|
|
39
|
+
RepoInfo("faden-frontend", "frontend", "React frontend"),
|
|
40
|
+
RepoInfo("faden-gateway", "gateway", "LLM Gateway API"),
|
|
41
|
+
RepoInfo("faden-pii", "pii", "PII redaction service"),
|
|
42
|
+
RepoInfo("faden-mcp", "mcp", "MCP Tool Registry"),
|
|
43
|
+
RepoInfo("faden-skills", "skills", "Skills Registry"),
|
|
44
|
+
RepoInfo("faden-shared", "shared", "Shared infra (compose)"),
|
|
45
|
+
RepoInfo("faden-cli", "cli", "CLI"),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
REPO_DIR_MAP: dict[str, str] = {r.github_name: r.local_dir for r in REPOS}
|
|
49
|
+
"""Map GitHub repo name → local directory name."""
|
|
50
|
+
|
|
51
|
+
REPO_NAMES: list[str] = [r.github_name for r in REPOS]
|
|
52
|
+
"""All core repository names."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ── Module definitions ────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ModuleInfo:
|
|
60
|
+
"""An optional module that can be toggled via compose profiles."""
|
|
61
|
+
|
|
62
|
+
name: str
|
|
63
|
+
profile: str
|
|
64
|
+
description: str
|
|
65
|
+
container: str = ""
|
|
66
|
+
port: int = 0
|
|
67
|
+
service_url: str = ""
|
|
68
|
+
env_vars: tuple[tuple[str, str], ...] = ()
|
|
69
|
+
# ── How ``faden dev up`` runs it: on the host, like the backend ──
|
|
70
|
+
#: The service directory it runs from; empty for a module dev mode
|
|
71
|
+
#: does not run.
|
|
72
|
+
dev_dir: str = ""
|
|
73
|
+
#: Its own database, migrated with Alembic from ``dev_dir``.
|
|
74
|
+
database: str = ""
|
|
75
|
+
#: The Redis database number it uses, when it uses Redis.
|
|
76
|
+
redis_base: int | None = None
|
|
77
|
+
#: The token the gateway and backend present to it, in dev. The shared
|
|
78
|
+
#: ``.env`` overrides it (``FADEN_<NAME>_SERVICE_TOKEN``).
|
|
79
|
+
dev_service_token: str = ""
|
|
80
|
+
#: What the backend's URL for it adds to the service root.
|
|
81
|
+
backend_url_suffix: str = ""
|
|
82
|
+
#: Settings of its own in dev, beyond host, port and credentials.
|
|
83
|
+
dev_settings: tuple[tuple[str, str], ...] = ()
|
|
84
|
+
#: Its settings that hold another module's URL: (setting, module name).
|
|
85
|
+
dev_links: tuple[tuple[str, str], ...] = ()
|
|
86
|
+
#: What it imports that its lock file cannot carry: (import name, wheel).
|
|
87
|
+
extra_wheels: tuple[tuple[str, str], ...] = ()
|
|
88
|
+
#: The secrets it needs in an install's ``.env``. Each group gets one
|
|
89
|
+
#: random value, shared by every key in it: a token the gateway, the
|
|
90
|
+
#: backend and the module itself all hold.
|
|
91
|
+
secret_groups: tuple[tuple[str, ...], ...] = ()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
MODULES: dict[str, ModuleInfo] = {
|
|
95
|
+
"pii": ModuleInfo(
|
|
96
|
+
name="pii",
|
|
97
|
+
profile="pii",
|
|
98
|
+
description="PII detection and redaction (Presidio + spaCy)",
|
|
99
|
+
container="faden-pii",
|
|
100
|
+
port=8003,
|
|
101
|
+
service_url="http://faden-pii:8000",
|
|
102
|
+
env_vars=(
|
|
103
|
+
("FADEN_GATEWAY_PII_ENABLED", "true"),
|
|
104
|
+
("FADEN_GATEWAY_PII_SERVICE_URL", "http://faden-pii:8000"),
|
|
105
|
+
# The compose file defaults it to false, which the console's
|
|
106
|
+
# module switch then cannot see past.
|
|
107
|
+
("FADEN_BACKEND_PII_ENABLED", "true"),
|
|
108
|
+
),
|
|
109
|
+
dev_dir="pii",
|
|
110
|
+
backend_url_suffix="/api",
|
|
111
|
+
# The spaCy model Presidio loads. Not a PyPI package, so the lock
|
|
112
|
+
# file cannot carry it; the image installs the same wheel.
|
|
113
|
+
extra_wheels=((
|
|
114
|
+
"en_core_web_lg",
|
|
115
|
+
"https://github.com/explosion/spacy-models/releases/download/"
|
|
116
|
+
"en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl",
|
|
117
|
+
),),
|
|
118
|
+
),
|
|
119
|
+
"mcp": ModuleInfo(
|
|
120
|
+
name="mcp",
|
|
121
|
+
profile="mcp",
|
|
122
|
+
description="MCP Tool Registry (governed MCP server broker)",
|
|
123
|
+
container="faden-mcp",
|
|
124
|
+
port=8007,
|
|
125
|
+
service_url="http://faden-mcp:8000",
|
|
126
|
+
env_vars=(
|
|
127
|
+
("FADEN_GATEWAY_MCP_ENABLED", "true"),
|
|
128
|
+
("FADEN_GATEWAY_MCP_SERVICE_URL", "http://faden-mcp:8000"),
|
|
129
|
+
("FADEN_BACKEND_MCP_ENABLED", "true"),
|
|
130
|
+
("FADEN_BACKEND_MCP_SERVICE_URL", "http://faden-mcp:8000"),
|
|
131
|
+
),
|
|
132
|
+
dev_dir="mcp",
|
|
133
|
+
database="faden_mcp",
|
|
134
|
+
redis_base=3,
|
|
135
|
+
dev_service_token="dev-mcp-service-token",
|
|
136
|
+
# Fixed, like every other dev secret: it encrypts the MCP servers'
|
|
137
|
+
# credentials in faden_mcp, which outlive a regenerated .env.
|
|
138
|
+
dev_settings=(("ENCRYPTION_KEY", "dev-mcp-encryption-key-change-me-32b"),),
|
|
139
|
+
dev_links=(("PII_SERVICE_URL", "pii"),),
|
|
140
|
+
# Its own key in an install: the compose file falls back to the dev
|
|
141
|
+
# key above, the same for every install that has none.
|
|
142
|
+
secret_groups=(
|
|
143
|
+
("FADEN_MCP_ENCRYPTION_KEY",),
|
|
144
|
+
(
|
|
145
|
+
"FADEN_GATEWAY_MCP_SERVICE_TOKEN",
|
|
146
|
+
"FADEN_BACKEND_MCP_SERVICE_TOKEN",
|
|
147
|
+
"FADEN_MCP_SERVICE_TOKEN",
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
"skills": ModuleInfo(
|
|
152
|
+
name="skills",
|
|
153
|
+
profile="skills",
|
|
154
|
+
description="Skills Registry (centralized LLM instruction sets)",
|
|
155
|
+
container="faden-skills",
|
|
156
|
+
port=8008,
|
|
157
|
+
service_url="http://faden-skills:8000",
|
|
158
|
+
env_vars=(
|
|
159
|
+
("FADEN_GATEWAY_SKILLS_ENABLED", "true"),
|
|
160
|
+
("FADEN_GATEWAY_SKILLS_SERVICE_URL", "http://faden-skills:8000"),
|
|
161
|
+
("FADEN_BACKEND_SKILLS_ENABLED", "true"),
|
|
162
|
+
("FADEN_BACKEND_SKILLS_SERVICE_URL", "http://faden-skills:8000"),
|
|
163
|
+
),
|
|
164
|
+
dev_dir="skills",
|
|
165
|
+
database="faden_skills",
|
|
166
|
+
redis_base=4,
|
|
167
|
+
dev_service_token="dev-skills-service-token",
|
|
168
|
+
secret_groups=(
|
|
169
|
+
(
|
|
170
|
+
"FADEN_GATEWAY_SKILLS_SERVICE_TOKEN",
|
|
171
|
+
"FADEN_BACKEND_SKILLS_SERVICE_TOKEN",
|
|
172
|
+
"FADEN_SKILLS_SERVICE_TOKEN",
|
|
173
|
+
),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Legacy dict-of-dicts shape consumed by commands/module.py
|
|
179
|
+
MODULES_COMPAT: dict[str, dict[str, str]] = {
|
|
180
|
+
name: {"profile": m.profile, "description": m.description}
|
|
181
|
+
for name, m in MODULES.items()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ── Port registry ────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
KNOWN_PORTS: list[tuple[int, str]] = [
|
|
188
|
+
# Core infrastructure
|
|
189
|
+
(5432, "PostgreSQL"),
|
|
190
|
+
(5672, "RabbitMQ AMQP"),
|
|
191
|
+
(15672, "RabbitMQ Management"),
|
|
192
|
+
(6379, "Redis"),
|
|
193
|
+
(9000, "MinIO API"),
|
|
194
|
+
(9001, "MinIO Console"),
|
|
195
|
+
# Observability
|
|
196
|
+
(3001, "Grafana"),
|
|
197
|
+
(3100, "Loki"),
|
|
198
|
+
(12345, "Alloy (OpenTelemetry)"),
|
|
199
|
+
# Application services
|
|
200
|
+
(8000, "Backend"),
|
|
201
|
+
(5173, "Frontend (dev)"),
|
|
202
|
+
(8001, "Gateway"),
|
|
203
|
+
(8003, "PII Service"),
|
|
204
|
+
(8007, "MCP Tool Registry"),
|
|
205
|
+
(8008, "Skills Registry"),
|
|
206
|
+
# Tools
|
|
207
|
+
(5050, "pgAdmin"),
|
|
208
|
+
(3000, "Langfuse"),
|
|
209
|
+
(8123, "ClickHouse HTTP"),
|
|
210
|
+
(9181, "ClickHouse Native"),
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ── Database registry ────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
DATABASES: list[str] = [
|
|
217
|
+
"faden_backend",
|
|
218
|
+
"faden_gateway",
|
|
219
|
+
"pii",
|
|
220
|
+
"faden_mcp",
|
|
221
|
+
"faden_skills",
|
|
222
|
+
"langfuse",
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
POSTGRES_CONTAINER = "faden-postgres"
|
|
226
|
+
|
|
227
|
+
# Services that use pre-built public images and should be started in dev mode.
|
|
228
|
+
# Application services (backend, frontend, api) are run natively on the host.
|
|
229
|
+
INFRA_SERVICES: list[str] = [
|
|
230
|
+
"postgres",
|
|
231
|
+
"redis",
|
|
232
|
+
"minio",
|
|
233
|
+
"clickhouse",
|
|
234
|
+
"langfuse-worker",
|
|
235
|
+
"langfuse-web",
|
|
236
|
+
"faden-rmq",
|
|
237
|
+
"loki",
|
|
238
|
+
"grafana",
|
|
239
|
+
"alloy",
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ── Dev backend env vars ─────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
#: Where the dev backend, gateway and frontend listen. This machine only: dev
|
|
246
|
+
#: mode signs anyone in as the administrator (POST /auth/dev-login, admin /
|
|
247
|
+
#: admin), so on every interface the whole network was an administrator.
|
|
248
|
+
#: ``faden dev up --lan`` binds :data:`DEV_LAN_HOST` instead, for a dev box
|
|
249
|
+
#: reached over a network you trust.
|
|
250
|
+
DEV_BIND_HOST = "127.0.0.1"
|
|
251
|
+
DEV_LAN_HOST = "0.0.0.0"
|
|
252
|
+
|
|
253
|
+
BACKEND_DEV_ENV: dict[str, str] = {
|
|
254
|
+
# This machine only (DEV_BIND_HOST); `faden dev up --lan` opens it to the
|
|
255
|
+
# network, rewriting this key each run.
|
|
256
|
+
"FADEN_BACKEND_HOST": DEV_BIND_HOST,
|
|
257
|
+
"FADEN_BACKEND_PORT": "8000",
|
|
258
|
+
# Session cookies must not carry the Secure flag in dev: browsers
|
|
259
|
+
# drop Secure cookies over plain HTTP, which would silently break
|
|
260
|
+
# login on a remote LAN access (HTTP, no TLS). Prod keeps the
|
|
261
|
+
# default (True) behind TLS/nginx.
|
|
262
|
+
"FADEN_BACKEND_COOKIE_SECURE": "false",
|
|
263
|
+
"FADEN_BACKEND_ENVIRONMENT": "dev",
|
|
264
|
+
# The dev API gateway runs on the host's port 8001.
|
|
265
|
+
"FADEN_BACKEND_GATEWAY_URL": "http://127.0.0.1:8001",
|
|
266
|
+
"FADEN_BACKEND_RELOAD": "true",
|
|
267
|
+
# 127.0.0.1, never "localhost": see INFRA_LOOPBACK.
|
|
268
|
+
"FADEN_BACKEND_DB_HOST": "127.0.0.1",
|
|
269
|
+
"FADEN_BACKEND_DB_PORT": "5432",
|
|
270
|
+
"FADEN_BACKEND_DB_USER": "faden_backend",
|
|
271
|
+
"FADEN_BACKEND_DB_PASS": "faden_backend",
|
|
272
|
+
"FADEN_BACKEND_DB_BASE": "faden_backend",
|
|
273
|
+
"FADEN_BACKEND_RABBIT_HOST": "127.0.0.1",
|
|
274
|
+
"FADEN_BACKEND_RABBIT_PORT": "5672",
|
|
275
|
+
# Matches the per-service AMQP user created in
|
|
276
|
+
# shared/rabbitmq/definitions.json by `faden dev init`
|
|
277
|
+
# (see backend_dev_env_for for the password).
|
|
278
|
+
"FADEN_BACKEND_RABBIT_USER": "faden-backend",
|
|
279
|
+
"FADEN_BACKEND_RABBIT_PASS": "devpassword",
|
|
280
|
+
"FADEN_BACKEND_RABBIT_VHOST": "/",
|
|
281
|
+
"FADEN_BACKEND_SETTINGS_MASTER_KEY": "dev-settings-master-key-change-me",
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
#: The address the host-run dev services reach the shared infra on.
|
|
286
|
+
#:
|
|
287
|
+
#: Postgres, Redis and RabbitMQ are published on 127.0.0.1 only
|
|
288
|
+
#: (``shared/docker-compose.yaml``). "localhost" resolves to ::1
|
|
289
|
+
#: first on Windows, where nothing listens -- and with WSL mirrored
|
|
290
|
+
#: networking the SYN to ::1 is not refused but dropped, so every new
|
|
291
|
+
#: connection waited out Windows' 21 s connect retry before falling back to
|
|
292
|
+
#: IPv4. The gateway opens connections per worker as bursts need them, so
|
|
293
|
+
#: chat requests stalled 21 s at random: measured 22,065 ms for a 4-token
|
|
294
|
+
#: reply that the engine produced in 90 ms.
|
|
295
|
+
INFRA_LOOPBACK = "127.0.0.1"
|
|
296
|
+
|
|
297
|
+
#: The keys that carry that address, in the backend and gateway env files.
|
|
298
|
+
INFRA_HOST_KEYS: tuple[str, ...] = (
|
|
299
|
+
"FADEN_BACKEND_DB_HOST",
|
|
300
|
+
"FADEN_BACKEND_RABBIT_HOST",
|
|
301
|
+
"FADEN_GATEWAY_DB_HOST",
|
|
302
|
+
"FADEN_GATEWAY_REDIS_HOST",
|
|
303
|
+
"FADEN_GATEWAY_RABBIT_HOST",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
#: Default env for the API gateway dev process (``faden dev up``).
|
|
308
|
+
#:
|
|
309
|
+
#: The gateway is the OpenAI-compatible ``/v1`` edge. In dev it runs as a
|
|
310
|
+
#: host process on port 8001 (on this machine only, like the backend; ``--lan``
|
|
311
|
+
#: opens both) and points at the shared 127.0.0.1-published infra (postgres
|
|
312
|
+
#: / redis / rabbit). Chat-file uploads go to a host-local directory
|
|
313
|
+
#: instead of the containerized ``/data/faden`` volume.
|
|
314
|
+
GATEWAY_DEV_ENV: dict[str, str] = {
|
|
315
|
+
"FADEN_GATEWAY_HOST": DEV_BIND_HOST,
|
|
316
|
+
"FADEN_GATEWAY_PORT": "8001",
|
|
317
|
+
# 127.0.0.1, never "localhost": see INFRA_LOOPBACK.
|
|
318
|
+
"FADEN_GATEWAY_DB_HOST": "127.0.0.1",
|
|
319
|
+
"FADEN_GATEWAY_DB_PORT": "5432",
|
|
320
|
+
"FADEN_GATEWAY_REDIS_HOST": "127.0.0.1",
|
|
321
|
+
"FADEN_GATEWAY_REDIS_PORT": "6379",
|
|
322
|
+
"FADEN_GATEWAY_RABBIT_HOST": "127.0.0.1",
|
|
323
|
+
"FADEN_GATEWAY_RABBIT_PORT": "5672",
|
|
324
|
+
"FADEN_GATEWAY_RAG_LITE_BACKEND_URL": "http://127.0.0.1:8000",
|
|
325
|
+
"FADEN_GATEWAY_CHAT_FILE_STORE_ROOT": "~/.faden-dev/chat-files",
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# ── Dev process patterns (for status checks) ─────────────────────
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass(frozen=True)
|
|
332
|
+
class DevProcess:
|
|
333
|
+
"""A dev-mode process pattern for ``faden dev status``."""
|
|
334
|
+
|
|
335
|
+
name: str
|
|
336
|
+
pattern: str
|
|
337
|
+
url: str = "—"
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
DEV_PROCESSES: list[DevProcess] = [
|
|
341
|
+
DevProcess("Backend", "faden_backend", "http://localhost:8000"),
|
|
342
|
+
DevProcess("Worker", "taskiq worker", "—"),
|
|
343
|
+
DevProcess("Frontend", "npm run dev", "http://localhost:5173"),
|
|
344
|
+
DevProcess("API gateway", "faden_gateway", "http://localhost:8001"),
|
|
345
|
+
# Optional modules (``faden dev up --modules``).
|
|
346
|
+
DevProcess("PII", "faden_pii", "http://127.0.0.1:8003"),
|
|
347
|
+
DevProcess("MCP", "faden_mcp", "http://127.0.0.1:8007"),
|
|
348
|
+
DevProcess("Skills", "faden_skills", "http://127.0.0.1:8008"),
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# ── Dev endpoint summary (for ``faden dev up``) ────────────────
|
|
353
|
+
|
|
354
|
+
DEV_ENDPOINTS: list[tuple[str, str]] = [
|
|
355
|
+
("Backend", "http://localhost:8000"),
|
|
356
|
+
("API Docs", "http://localhost:8000/api/docs"),
|
|
357
|
+
("Worker", "Taskiq (RabbitMQ)"),
|
|
358
|
+
("Frontend", "http://localhost:5173"),
|
|
359
|
+
("Grafana", "http://localhost:3001"),
|
|
360
|
+
("RabbitMQ", "http://localhost:15672"),
|
|
361
|
+
("LLM API", "http://localhost:8001"),
|
|
362
|
+
("PII", "http://127.0.0.1:8003"),
|
|
363
|
+
("MCP Registry", "http://127.0.0.1:8007"),
|
|
364
|
+
("Skills Registry", "http://127.0.0.1:8008"),
|
|
365
|
+
]
|
|
366
|
+
|
|
367
|
+
#: The endpoints above that exist only while their module runs.
|
|
368
|
+
DEV_MODULE_ENDPOINTS: dict[str, str] = {
|
|
369
|
+
"PII": "pii",
|
|
370
|
+
"MCP Registry": "mcp",
|
|
371
|
+
"Skills Registry": "skills",
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ── Convenience helpers ──────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def repo_clone_url(repo: str, *, method: str = "https", token: str = "") -> str:
|
|
379
|
+
"""Return the git clone URL for a repo in the Fadenstack org.
|
|
380
|
+
|
|
381
|
+
When *token* is provided and *method* is ``"https"``, the token is
|
|
382
|
+
embedded in the URL so that ``git clone`` can access private repos.
|
|
383
|
+
"""
|
|
384
|
+
if method == "ssh":
|
|
385
|
+
return f"git@github.com:{GITHUB_ORG}/{repo}.git"
|
|
386
|
+
if token:
|
|
387
|
+
return f"https://x-access-token:{token}@github.com/{GITHUB_ORG}/{repo}.git"
|
|
388
|
+
return f"https://github.com/{GITHUB_ORG}/{repo}"
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def core_repo_clone_url(*, method: str = "https", token: str = "") -> str:
|
|
392
|
+
"""Return the git clone URL for the core monorepo."""
|
|
393
|
+
return repo_clone_url(CORE_REPO, method=method, token=token)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def module_env_vars(profile: str) -> dict[str, str]:
|
|
397
|
+
"""Return the env vars needed to enable a module by profile name.
|
|
398
|
+
|
|
399
|
+
Used by ``env_gen.dev_env_vars()`` and ``env_gen.default_env_vars()``
|
|
400
|
+
to inject module-specific settings into generated ``.env`` files.
|
|
401
|
+
|
|
402
|
+
Each ``ModuleInfo`` carries its own ``env_vars`` tuple so that
|
|
403
|
+
enterprise modules registered via ``extend_modules()`` are handled
|
|
404
|
+
automatically — no hard-coded profile branches needed.
|
|
405
|
+
"""
|
|
406
|
+
mod = MODULES.get(profile)
|
|
407
|
+
if not mod:
|
|
408
|
+
return {}
|
|
409
|
+
return dict(mod.env_vars)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
# ── Extension API (used by faden-ee to layer on EE data) ────────
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _rebuild_derived() -> None:
|
|
416
|
+
"""Rebuild derived dicts/lists after extending REPOS."""
|
|
417
|
+
REPO_DIR_MAP.clear()
|
|
418
|
+
REPO_DIR_MAP.update({r.github_name: r.local_dir for r in REPOS})
|
|
419
|
+
REPO_NAMES.clear()
|
|
420
|
+
REPO_NAMES.extend(r.github_name for r in REPOS)
|
|
421
|
+
MODULES_COMPAT.clear()
|
|
422
|
+
MODULES_COMPAT.update(
|
|
423
|
+
{name: {"profile": m.profile, "description": m.description} for name, m in MODULES.items()}
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def extend_repos(extra: list[RepoInfo]) -> None:
|
|
428
|
+
"""Append repositories to the registry (idempotent)."""
|
|
429
|
+
existing = {r.github_name for r in REPOS}
|
|
430
|
+
for repo in extra:
|
|
431
|
+
if repo.github_name not in existing:
|
|
432
|
+
REPOS.append(repo)
|
|
433
|
+
existing.add(repo.github_name)
|
|
434
|
+
_rebuild_derived()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def extend_modules(extra: dict[str, ModuleInfo]) -> None:
|
|
438
|
+
"""Add modules to the registry (idempotent)."""
|
|
439
|
+
for name, mod in extra.items():
|
|
440
|
+
if name not in MODULES:
|
|
441
|
+
MODULES[name] = mod
|
|
442
|
+
_rebuild_derived()
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def extend_ports(extra: list[tuple[int, str]]) -> None:
|
|
446
|
+
"""Append ports to the known-ports list (idempotent)."""
|
|
447
|
+
existing = {p for p, _ in KNOWN_PORTS}
|
|
448
|
+
for port, label in extra:
|
|
449
|
+
if port not in existing:
|
|
450
|
+
KNOWN_PORTS.append((port, label))
|
|
451
|
+
existing.add(port)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def extend_databases(extra: list[str]) -> None:
|
|
455
|
+
"""Append database names to the registry (idempotent)."""
|
|
456
|
+
existing = set(DATABASES)
|
|
457
|
+
for db in extra:
|
|
458
|
+
if db not in existing:
|
|
459
|
+
DATABASES.append(db)
|
|
460
|
+
existing.add(db)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def extend_dev_endpoints(extra: list[tuple[str, str]]) -> None:
|
|
464
|
+
"""Append dev endpoint entries (idempotent by name)."""
|
|
465
|
+
existing = {name for name, _ in DEV_ENDPOINTS}
|
|
466
|
+
for name, url in extra:
|
|
467
|
+
if name not in existing:
|
|
468
|
+
DEV_ENDPOINTS.append((name, url))
|
|
469
|
+
existing.add(name)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def extend_dev_processes(extra: list[DevProcess]) -> None:
|
|
473
|
+
"""Append dev process patterns (idempotent by name)."""
|
|
474
|
+
existing = {p.name for p in DEV_PROCESSES}
|
|
475
|
+
for proc in extra:
|
|
476
|
+
if proc.name not in existing:
|
|
477
|
+
DEV_PROCESSES.append(proc)
|
|
478
|
+
existing.add(proc.name)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _read_env_values(path: Path) -> dict[str, str]:
|
|
482
|
+
"""Parse a simple ``KEY=VALUE`` env file into a dict."""
|
|
483
|
+
path = Path(path)
|
|
484
|
+
if not path.is_file():
|
|
485
|
+
return {}
|
|
486
|
+
values: dict[str, str] = {}
|
|
487
|
+
for raw_line in path.read_text(encoding="utf-8").splitlines():
|
|
488
|
+
line = raw_line.strip()
|
|
489
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
490
|
+
continue
|
|
491
|
+
key, _, value = line.partition("=")
|
|
492
|
+
values[key.strip()] = value.strip()
|
|
493
|
+
return values
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def backend_dev_env_for(shared_env_path: Path) -> dict[str, str]:
|
|
497
|
+
"""Build the backend dev ``.env`` with correct RabbitMQ credentials.
|
|
498
|
+
|
|
499
|
+
The broker users are loaded from
|
|
500
|
+
``shared/rabbitmq/definitions.json``, which dev init regenerates
|
|
501
|
+
from the shared ``.env`` passwords. The backend must therefore log in as
|
|
502
|
+
``faden-backend`` with ``RABBITMQ_BACKEND_PASS`` — ``BACKEND_DEV_ENV``'s
|
|
503
|
+
historical ``guest/guest`` default does not exist once definitions are
|
|
504
|
+
loaded (RabbitMQ reports ``ACCESS_REFUSED``).
|
|
505
|
+
"""
|
|
506
|
+
env = dict(BACKEND_DEV_ENV)
|
|
507
|
+
shared = _read_env_values(shared_env_path)
|
|
508
|
+
# The gateway's tables (usage, chats for the admin pages) are read with the
|
|
509
|
+
# shared Postgres login, as in an install. The backend's default was
|
|
510
|
+
# llm_user/llm_user, a login the shared initdb no longer makes.
|
|
511
|
+
if shared.get("POSTGRES_USER") and shared.get("POSTGRES_PASSWORD"):
|
|
512
|
+
env["FADEN_BACKEND_LLM_GRAPH_DB_USER"] = shared["POSTGRES_USER"]
|
|
513
|
+
env["FADEN_BACKEND_LLM_GRAPH_DB_PASS"] = shared["POSTGRES_PASSWORD"]
|
|
514
|
+
backend_pass = shared.get("RABBITMQ_BACKEND_PASS")
|
|
515
|
+
if backend_pass:
|
|
516
|
+
env["FADEN_BACKEND_RABBIT_USER"] = "faden-backend"
|
|
517
|
+
env["FADEN_BACKEND_RABBIT_PASS"] = backend_pass
|
|
518
|
+
return env
|
|
519
|
+
# Shared env predates per-service users — the only account guaranteed to
|
|
520
|
+
# exist on the broker is the one the shared env configures for it.
|
|
521
|
+
if shared.get("RABBITMQ_ADMIN_USER") and shared.get("RABBITMQ_ADMIN_PASS"):
|
|
522
|
+
env["FADEN_BACKEND_RABBIT_USER"] = shared["RABBITMQ_ADMIN_USER"]
|
|
523
|
+
env["FADEN_BACKEND_RABBIT_PASS"] = shared["RABBITMQ_ADMIN_PASS"]
|
|
524
|
+
return env
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def gateway_dev_env_for(shared_env_path: Path) -> dict[str, str]:
|
|
528
|
+
"""Build the gateway dev ``.env`` with credentials from the shared env.
|
|
529
|
+
|
|
530
|
+
Mirrors :func:`backend_dev_env_for`: DB credentials come from
|
|
531
|
+
``POSTGRES_USER`` / ``POSTGRES_PASSWORD`` in
|
|
532
|
+
``shared/.env``, the RabbitMQ broker login from
|
|
533
|
+
``RABBITMQ_API_PASS`` as user ``faden-gateway`` (the per-service user
|
|
534
|
+
that ``definitions.json`` creates; ``RABBITMQ_API_USER`` overrides
|
|
535
|
+
the name, and the admin pair is used only for pre-per-service-user
|
|
536
|
+
envs that have no ``RABBITMQ_API_PASS``), and the Redis password
|
|
537
|
+
from ``REDIS_AUTH`` (the key ``dev init`` writes; ``REDIS_PASSWORD``
|
|
538
|
+
is the prod-template alias and is kept as a fallback). Returns
|
|
539
|
+
``GATEWAY_DEV_ENV`` with the defaults when the shared env does not
|
|
540
|
+
exist.
|
|
541
|
+
"""
|
|
542
|
+
env = dict(GATEWAY_DEV_ENV)
|
|
543
|
+
shared = _read_env_values(shared_env_path)
|
|
544
|
+
if not shared:
|
|
545
|
+
return env
|
|
546
|
+
if shared.get("POSTGRES_USER"):
|
|
547
|
+
env["FADEN_GATEWAY_DB_USER"] = shared["POSTGRES_USER"]
|
|
548
|
+
if shared.get("POSTGRES_PASSWORD"):
|
|
549
|
+
env["FADEN_GATEWAY_DB_PASS"] = shared["POSTGRES_PASSWORD"]
|
|
550
|
+
api_pass = shared.get("RABBITMQ_API_PASS")
|
|
551
|
+
if api_pass:
|
|
552
|
+
# Env generation never writes RABBITMQ_API_USER — the per-service
|
|
553
|
+
# user name is fixed by the definitions.json generator, so key off
|
|
554
|
+
# the password and default the name (as backend_dev_env_for does).
|
|
555
|
+
env["FADEN_GATEWAY_RABBIT_USER"] = shared.get("RABBITMQ_API_USER", "faden-gateway")
|
|
556
|
+
env["FADEN_GATEWAY_RABBIT_PASS"] = api_pass
|
|
557
|
+
elif shared.get("RABBITMQ_ADMIN_USER") and shared.get("RABBITMQ_ADMIN_PASS"):
|
|
558
|
+
env["FADEN_GATEWAY_RABBIT_USER"] = shared["RABBITMQ_ADMIN_USER"]
|
|
559
|
+
env["FADEN_GATEWAY_RABBIT_PASS"] = shared["RABBITMQ_ADMIN_PASS"]
|
|
560
|
+
redis_pass = shared.get("REDIS_AUTH") or shared.get("REDIS_PASSWORD")
|
|
561
|
+
if redis_pass:
|
|
562
|
+
env["FADEN_GATEWAY_REDIS_PASS"] = redis_pass
|
|
563
|
+
return env
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def extend_backend_dev_env(extra: dict[str, str]) -> None:
|
|
567
|
+
"""Merge extra env vars into ``BACKEND_DEV_ENV`` (idempotent).
|
|
568
|
+
|
|
569
|
+
Existing keys are **not** overwritten. This allows the EE CLI
|
|
570
|
+
overlay to inject ``FADEN_EE_*`` variables that will be written
|
|
571
|
+
to the backend ``.env`` file by ``dev init`` and ``dev up``.
|
|
572
|
+
"""
|
|
573
|
+
for key, value in extra.items():
|
|
574
|
+
BACKEND_DEV_ENV.setdefault(key, value)
|