0.3.4: validate --deep, fix stale docker-compose.yaml

validate --deep pings Open WebUI API to verify connectivity, API key
validity, and that each KB ID exists. Catches config errors before
deployment. Also applies defaults and env var interpolation in
validate (was missing).

docker-compose.yaml updated from stale watch mode to production
daemon mode with healthcheck, LOG_FORMAT, and OIKB_API_KEY.
This commit is contained in:
Timothy Jaeryang Baek 2026-05-21 13:47:28 +04:00
parent fe3a94961f
commit 597bb6c9d9
7 changed files with 74 additions and 11 deletions

View file

@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.3.4] - 2025-05-21
### Added
- `oikb validate --deep` — verifies Open WebUI connectivity, API key validity, and that each KB ID exists. Catches config errors before deployment.
### Fixed
- `docker-compose.yaml` updated from stale `watch` mode to production `daemon` mode with healthcheck, `LOG_FORMAT=json`, and `OIKB_API_KEY` support.
- `validate` now applies `defaults:` and env var interpolation to entries before checking (was only doing raw yaml).
## [0.3.3] - 2025-05-21
### Added

View file

@ -6,18 +6,25 @@ services:
volumes:
- open-webui:/app/backend/data
# One-shot sync on container start, then watch for changes
oikb:
image: ghcr.io/open-webui/oikb:latest
environment:
- OPEN_WEBUI_URL=http://open-webui:8080
- OPEN_WEBUI_API_KEY=${OPEN_WEBUI_API_KEY}
- OIKB_API_KEY=${OIKB_API_KEY}
- LOG_FORMAT=json
volumes:
- ./docs:/data
command: watch /data --kb ${KB_ID}
- ./.oikb.yaml:/app/.oikb.yaml:ro
command: daemon
ports:
- "8080:8080"
depends_on:
- open-webui
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
interval: 30s
timeout: 5s
volumes:
open-webui:

View file

@ -645,6 +645,7 @@ oikb daemon --log-format json JSON logging
oikb daemon --config /path/to/yaml Custom config path
oikb diff <source> --kb-id ID Preview changes
oikb validate Check .oikb.yaml syntax
oikb validate --deep Verify API, auth, and KB existence
oikb ls --kb-id ID List files in a KB
oikb status --kb-id ID Show KB info
oikb history View sync history
@ -686,7 +687,8 @@ You're running `oikb sync` without arguments and there's no `.oikb.yaml` in the
### Daemon won't start
```bash
oikb validate # Check config syntax first
oikb validate # Check config syntax
oikb validate --deep # Verify API + KB connectivity
```
### How do I find my KB ID?

View file

@ -1,6 +1,6 @@
[project]
name = "oikb"
version = "0.3.3"
version = "0.3.4"
description = "Sync anything to Open WebUI Knowledge Bases"
readme = "README.md"
authors = [

View file

@ -1,3 +1,3 @@
"""oikb — Open WebUI Knowledge Base CLI."""
__version__ = "0.3.3"
__version__ = "0.3.4"

View file

@ -830,13 +830,23 @@ def init(force: bool):
@cli.command()
@click.option("--config", "config_file", default=None, type=click.Path(), help="Path to .oikb.yaml (default: ./.oikb.yaml).")
def validate(config_file: str | None):
"""Validate .oikb.yaml without running anything."""
@click.option("--deep", is_flag=True, help="Verify connectivity: ping Open WebUI, check API key, confirm each KB exists.")
def validate(config_file: str | None, deep: bool):
"""Validate .oikb.yaml without running anything.
By default, checks YAML structure and source syntax.
With --deep, also verifies the Open WebUI API is reachable,
the API key is valid, and each Knowledge Base ID exists.
"""
if config_file:
import yaml
with open(config_file) as f:
data = yaml.safe_load(f)
data = _interpolate_env(data) if data else data
entries = (data.get("sources") or data.get("sync", [])) if data else []
defaults = data.get("defaults", {}) if data else {}
if defaults and entries:
entries = [_deep_merge(defaults, e) for e in entries]
else:
entries = _load_oikb_yaml()
@ -844,6 +854,20 @@ def validate(config_file: str | None):
click.echo(click.style("No .oikb.yaml found or file is empty.", fg="red"), err=True)
sys.exit(1)
# Deep validation: create a client and verify connectivity first.
client = None
if deep:
click.echo(click.style("Deep validation\n", bold=True))
try:
client = _make_client(
url=entries[0].get("url"),
token=entries[0].get("token"),
)
click.echo(click.style(" ✓ API reachable", fg="green") + f" {client._http.base_url}")
except Exception as e:
click.echo(click.style(f" ✗ Cannot reach Open WebUI: {e}", fg="red"))
sys.exit(1)
has_errors = False
for i, entry in enumerate(entries, 1):
entry_name = entry.get("name", f"entry #{i}")
@ -855,13 +879,32 @@ def validate(config_file: str | None):
has_errors = True
continue
# Try resolving the connector to catch bad source strings.
# Syntax check: resolve the connector.
try:
_resolve_connector(source)
click.echo(click.style(f"{entry_name}", fg="green") + f" {source}{kb_id}")
except Exception as e:
click.echo(click.style(f"{entry_name}: {e}", fg="red"))
has_errors = True
continue
# Deep check: verify the KB exists.
if deep and client:
try:
kb = client.get_kb(kb_id)
kb_name = kb.get("name", "?")
file_count = len(kb.get("files", []))
click.echo(
click.style(f"{entry_name}", fg="green")
+ f" {source}{kb_name} ({file_count} files)"
)
except Exception as e:
click.echo(click.style(f"{entry_name}: KB {kb_id}{e}", fg="red"))
has_errors = True
else:
click.echo(click.style(f"{entry_name}", fg="green") + f" {source}{kb_id}")
if client:
client.close()
if has_errors:
sys.exit(1)

View file

@ -40,7 +40,7 @@ async def verify_api_key(
app = FastAPI(
title="oikb",
description="Sync engine for Open WebUI Knowledge Bases. Trigger syncs, check status, and query history.",
version="0.3.3",
version="0.3.4",
)
# Runtime state populated by start_daemon().