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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ explicitly; ordinary startup does not require KC images or credentials.

```sh
# Everything the shaide server depends on, to run the server natively
SHAIDE_SERVER_FQDN=host.docker.internal docker compose up -d
SHAIDE_SERVER_FQDN=host.docker.internal SHAIDE_SERVER_PORT=8080 docker compose up -d
cargo run

# Every service in containers, the shaide server included
Expand All @@ -264,6 +264,11 @@ gateway (`mcp` profile) are optional: pass the profile names to the recipes,
e.g. `just dev app mcp`, or `--profile app` to compose. When running the
containerized server with the webapp, also set `WEBAPP_URL=http://webapp:8787`.

As on k8s, the containers reach the containerized server at
`http://shaide-server` (port 80). On the host it is published at
`SHAIDE_SERVER_HOST_PORT` (default `8080`). `SHAIDE_SERVER_FQDN` and
`SHAIDE_SERVER_PORT` point the containers at a natively running server instead.

The containerized server is built from the `dev` Docker target, which adds
`azure-cli`. It keeps its data in `~/.config/axem-docker`, separate from the
native server's `~/.config/axem`, and reads a copy of `~/.azure` (create the
Expand Down
8 changes: 3 additions & 5 deletions compose.kc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# Requires Compose >= 2.24.4 (!override) and operator-supplied local/private images.
# No private code, image builds, account registration or storage credentials in server.
services:
shaide_server:
shaide-server:
build: !reset null
pull_policy: never
ports: !override
- "127.0.0.1:${SHAIDE_SERVER_PORT:-8080}:${SHAIDE_SERVER_PORT:-8080}"
- "127.0.0.1:${SHAIDE_SERVER_HOST_PORT:-8080}:80"
environment:
ADMIN_PASSWORD: !reset null
JWT_SECRET: !reset null
Expand Down Expand Up @@ -71,7 +71,7 @@ services:
S3_FQDN: kc-rustfs
S3_PORT: "9000"
VECTOR_DB_URL: http://kc-qdrant:6334
SHAIDE_SERVER_URL: http://shaide_server:${SHAIDE_SERVER_PORT:-8080}
SHAIDE_SERVER_URL: http://shaide-server
KC_SHAIDE_USERNAME: ${KC_SHAIDE_USERNAME:?dedicated role=user service account required}
RUSTFS_WEBHOOK_ARN: arn:rustfs:sqs:eu-central-1:shaide:webhook
KC_CONTEXT_ISSUER: shaide-local
Expand Down Expand Up @@ -213,8 +213,6 @@ services:
pull_policy: never
user: "${KC_LOCAL_UID:?operator uid required}:${KC_LOCAL_GID:?operator gid required}"
command: [caddy, run, --config, /etc/caddy/Caddyfile, --adapter, caddyfile]
environment:
SHAIDE_SERVER_PORT: ${SHAIDE_SERVER_PORT:-8080}
volumes:
- ./scripts/kc/notifications.Caddyfile:/etc/caddy/Caddyfile:ro
networks: [kc_notify_storage, kc_notify_server]
Expand Down
23 changes: 16 additions & 7 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ name: shaide-local-infra
# - `docker compose --profile server up -d` also runs the server (`just stack`).
# Optional services: `--profile app` (webapp) and `--profile mcp` (MCP gateway).
#
# Like on k8s, the other containers reach the server at http://shaide-server
# (port 80). The server is published on the host at SHAIDE_SERVER_HOST_PORT.
#
# Images default to a local build of this repo and the released images of the
# other components. Override them with SHAIDE_SERVER_IMAGE, SHAIDE_WEBAPP_IMAGE
# and SHAIDE_CONTROL_PANEL_IMAGE, e.g. SHAIDE_WEBAPP_IMAGE=shaide_webapp:latest.

services:
shaide_server:
shaide-server:
profiles: [server]
build:
context: .
Expand All @@ -21,13 +24,13 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "${SHAIDE_SERVER_PORT:-8080}:${SHAIDE_SERVER_PORT:-8080}"
- "${SHAIDE_SERVER_HOST_PORT:-8080}:80"
environment:
# Bootstraps the initial `admin` user. JWT_SECRET signs user JWTs and
# must be at least 32 bytes -- config.rs asserts on it at startup.
ADMIN_PASSWORD: admin
JWT_SECRET: local-development-jwt-secret-change-me
PORT: ${SHAIDE_SERVER_PORT:-8080}
PORT: "80"
SHAIDE_SERVER_UI_FQDN: control-panel
SHAIDE_SERVER_UI_PORT: 3000
# Passed through only when set, since the `/app` proxy is optional. Use
Expand Down Expand Up @@ -63,6 +66,12 @@ services:
ports:
# Host port 3001 is where a natively running shaide server expects it.
- "3001:8787"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
# The shaide server the webapp signs users in to, resolved from inside
# this container. Same address as for the control panel below.
SHAIDE_SERVER_URL: http://${SHAIDE_SERVER_FQDN:-shaide-server}${SHAIDE_SERVER_PORT:+:${SHAIDE_SERVER_PORT}}

vectordb:
image: qdrant/qdrant
Expand All @@ -77,10 +86,10 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
# `shaide_server` for the containerized server; `just dev` sets it to
# `host.docker.internal` to reach the native one.
SHAIDE_SERVER_FQDN: ${SHAIDE_SERVER_FQDN:-shaide_server}
SHAIDE_SERVER_PORT: ${SHAIDE_SERVER_PORT:-8080}
# `shaide-server:80` for the containerized server; `just dev` sets
# `host.docker.internal:8080` to reach the native one.
SHAIDE_SERVER_FQDN: ${SHAIDE_SERVER_FQDN:-shaide-server}
SHAIDE_SERVER_PORT: ${SHAIDE_SERVER_PORT:-80}
# HMAC key for the signed session cookie. Must be at least 32 characters
# or the Control Panel refuses to sign any session and every login fails.
SESSION_SECRET: ${SESSION_SECRET:-local-development-session-secret-change-me}
Expand Down
3 changes: 2 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ check:

# Start what a natively running shaide server needs; optional profiles: app, mcp
[env("SHAIDE_SERVER_FQDN", "host.docker.internal")]
[env("SHAIDE_SERVER_PORT", "8080")]
services-up *profiles:
# Free the server port in case the containerized server is still running.
docker compose --profile server stop shaide_server
docker compose --profile server stop shaide-server
docker compose {{ prepend("--profile ", profiles) }} up -d --remove-orphans

# Stop every compose service, including the optional ones
Expand Down
2 changes: 1 addition & 1 deletion scripts/kc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ shell function ensures every command uses the same project, overlay and settings

```sh
export KC_CONFIG='<new-private-directory>/config.env'
export SHAIDE_SERVER_PORT=18081 # choose an unused port; do not restart another stack
export SHAIDE_SERVER_HOST_PORT=18081 # choose an unused port; do not restart another stack
kc_compose() {
docker compose --env-file "$KC_CONFIG" -p shaide-kc-local \
-f compose.yaml -f compose.kc.yaml --profile server "$@"
Expand Down
2 changes: 1 addition & 1 deletion scripts/kc/notifications.Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
handle @event {
# Fixed destination; incoming bearer token is authenticated by shaide.
reverse_proxy shaide_server:{$SHAIDE_SERVER_PORT:8080} {
reverse_proxy shaide-server:80 {
transport http {
dial_timeout 5s
response_header_timeout 120s
Expand Down
16 changes: 9 additions & 7 deletions test/kc-compose/test_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,18 @@ def test_missing_optional_settings_fail_only_overlay(self):
def test_only_server_has_host_ports(self):
m = self.model()
for name, svc in m['services'].items():
if name != 'shaide_server': self.assertFalse(svc.get('ports'), name)
self.assertEqual(m['services']['shaide_server']['ports'][0]['host_ip'], '127.0.0.1')
if name != 'shaide-server': self.assertFalse(svc.get('ports'), name)
self.assertEqual(m['services']['shaide-server']['ports'][0]['host_ip'], '127.0.0.1')
def test_storage_and_vectors_are_isolated_from_server_and_mcp(self):
m = self.model(); s = m['services']
for backend in ['kc-rustfs', 'kc-qdrant']:
for facade in ['shaide_server', 'kc-mcp']:
for facade in ['shaide-server', 'kc-mcp']:
self.assertTrue(set(s[backend]['networks']).isdisjoint(s[facade]['networks']))
self.assertNotIn('default', s['knowledge-center']['networks'])
for name, net in m['networks'].items():
if name != 'default': self.assertTrue(net['internal'], name)
def test_server_has_no_storage_credentials_or_mounts(self):
m = self.model(); s = m['services']['shaide_server']
m = self.model(); s = m['services']['shaide-server']
self.assertFalse(any(k.startswith(('S3_', 'RUSTFS_', 'AWS_')) and k != 'S3_WEBHOOK_TOKEN' for k in s['environment']))
self.assertFalse(any('storage' in x['source'] for x in s['secrets']))
def test_compose_output_contains_no_secret_values(self):
Expand All @@ -138,17 +138,19 @@ def test_compose_output_contains_no_secret_values(self):
self.assertNotIn((self.dest / n).read_text().strip(), output)
def test_named_state_and_no_build_contract(self):
m = self.model(); s = m['services']
for service in ['shaide_server', 'kc-rustfs', 'kc-qdrant']:
for service in ['shaide-server', 'kc-rustfs', 'kc-qdrant']:
self.assertTrue(any(v['type'] == 'volume' for v in s[service]['volumes']))
for name in ['knowledge-center', 'kc-mcp', 'kc-rustfs']:
self.assertNotIn('build', s[name]); self.assertEqual(s[name]['pull_policy'], 'never')
self.assertEqual(s['shaide_server']['environment']['VECTOR_DB_URL'], 'http://vectordb:6334')
self.assertEqual(s['shaide-server']['environment']['VECTOR_DB_URL'], 'http://vectordb:6334')
def test_signed_context_and_notification_trust(self):
s = self.model()['services']; env = s['shaide_server']['environment']
s = self.model()['services']; env = s['shaide-server']['environment']
targets = json.loads(env['MCP_COLLECTION_CONTEXT_TARGETS'])
self.assertEqual(targets[0]['url'], 'http://kc-mcp:8080')
self.assertEqual(env['KNOWLEDGE_CENTER_URL'], 'http://knowledge-center:8080')
self.assertIn('kc-notifications:8080/v1/object-storage/event', s['kc-rustfs']['environment']['RUSTFS_NOTIFY_WEBHOOK_ENDPOINT_SHAIDE'])
self.assertEqual(s['knowledge-center']['environment']['RUSTFS_WEBHOOK_ARN'], 'arn:rustfs:sqs:eu-central-1:shaide:webhook')
self.assertEqual(s['knowledge-center']['environment']['SHAIDE_SERVER_URL'], 'http://shaide-server')
self.assertEqual(s['shaide-server']['ports'][0]['target'], 80)

if __name__ == '__main__': unittest.main()
6 changes: 3 additions & 3 deletions test/platform-discovery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ For example:

```sh
printf '%s\n' '{"scenario":"missing-metadata"}' > fixtures/scenario.json
docker compose restart shaide_server
docker compose restart shaide-server
```

Then click **Refresh available models**. Restarting clears the server's
Expand All @@ -77,7 +77,7 @@ The fixture definitions and network aliases are in `mock.py` and `compose.yaml`.
```sh
python3 smoke.py
docker compose ps
docker compose logs --tail=40 platform-mock shaide_server control-panel
docker compose logs --tail=40 platform-mock shaide-server control-panel
docker compose down
```

Expand All @@ -90,5 +90,5 @@ removes the models it added, and leaves an ordinary test user in the isolated
database with a one-day expiry. `down` preserves data for the next start.

The mock's self-signed certificate is regenerated at mock startup. If you
restart or recreate `platform-mock` alone, also restart `shaide_server` so it
restart or recreate `platform-mock` alone, also restart `shaide-server` so it
reloads the CA. None of these commands affect the existing Compose project.
11 changes: 6 additions & 5 deletions test/platform-discovery/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ services:
timeout: 2s
retries: 20

shaide_server:
shaide-server:
init: true
build:
context: ../..
target: runtime
image: shaide-server:discovery-demo
ports: ["127.0.0.1:18080:8080"]
ports: ["127.0.0.1:18080:80"]
environment:
PORT: "80"
ADMIN_PASSWORD: admin
JWT_SECRET: discovery-demo-only-jwt-secret-change-me
shaide_ROOT: /data
Expand All @@ -51,10 +52,10 @@ services:
image: shaide-control-panel:discovery-demo
ports: ["127.0.0.1:13000:3000"]
environment:
SHAIDE_SERVER_FQDN: shaide_server
SHAIDE_SERVER_PORT: "8080"
SHAIDE_SERVER_FQDN: shaide-server
SHAIDE_SERVER_PORT: "80"
SESSION_SECRET: discovery-demo-only-session-secret-change-me
depends_on: [shaide_server]
depends_on: [shaide-server]

vectordb:
image: qdrant/qdrant:latest
Expand Down
2 changes: 1 addition & 1 deletion test/platform-discovery/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def ok(url, **kwargs):

def scenario(name):
(DEMO / "fixtures/scenario.json").write_text(json.dumps({"scenario": name}) + "\n")
subprocess.run(COMPOSE + ["restart", "shaide_server"], check=True, stdout=subprocess.DEVNULL)
subprocess.run(COMPOSE + ["restart", "shaide-server"], check=True, stdout=subprocess.DEVNULL)
for _ in range(60):
try:
if request(SERVER + "/v1/platform-models", session=ANON)[0] == 401:
Expand Down
Loading