diff --git a/.dockerignore b/.dockerignore index 4d23261b..2b97ce1f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,47 @@ -crates/target/* +# Rust build artifacts +crates/target/ + +# Git +.git/ +.gitignore + +# Documentation & website +docs/ +!docs/requirements.txt +!docs/source/ +apps/ +packages/ + +# CI / IDE / editor files +.vscode/ +.idea/ +*.code-workspace +.github/ + +# Test & demo files +tests/ +http_tests/ +demos/ + +# CLI dev artifacts (tests, venv, cache) +cli/.venv/ +cli/.pytest_cache/ +cli/.coverage +cli/dist/ +cli/test/ +cli/__pycache__/ +cli/planoai/__pycache__/ + +# Python model server +archgw_modelserver/ +arch_tools/ + +# Misc +*.md +!cli/README.md +LICENSE +turbo.json +package.json +*.sh +!cli/build_cli.sh +arch_config.yaml_rendered diff --git a/Dockerfile b/Dockerfile index 92a335ce..2bd15377 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,54 +1,84 @@ -# build docker image for arch gateway -FROM rust:1.93.0 AS builder +# --- Dependency cache --- +FROM rust:1.93.0 AS deps RUN rustup -v target add wasm32-wasip1 WORKDIR /arch -COPY crates . + +COPY crates/Cargo.toml crates/Cargo.lock ./ +COPY crates/common/Cargo.toml common/Cargo.toml +COPY crates/hermesllm/Cargo.toml hermesllm/Cargo.toml +COPY crates/prompt_gateway/Cargo.toml prompt_gateway/Cargo.toml +COPY crates/llm_gateway/Cargo.toml llm_gateway/Cargo.toml +COPY crates/brightstaff/Cargo.toml brightstaff/Cargo.toml + +# Dummy sources to pre-compile dependencies +RUN mkdir -p common/src && echo "" > common/src/lib.rs && \ + mkdir -p hermesllm/src && echo "" > hermesllm/src/lib.rs && \ + mkdir -p hermesllm/src/bin && echo "fn main() {}" > hermesllm/src/bin/fetch_models.rs && \ + mkdir -p prompt_gateway/src && echo "#[no_mangle] pub fn _start() {}" > prompt_gateway/src/lib.rs && \ + mkdir -p llm_gateway/src && echo "#[no_mangle] pub fn _start() {}" > llm_gateway/src/lib.rs && \ + mkdir -p brightstaff/src && echo "fn main() {}" > brightstaff/src/main.rs && echo "" > brightstaff/src/lib.rs + +RUN cargo build --release --target wasm32-wasip1 -p prompt_gateway -p llm_gateway || true +RUN cargo build --release -p brightstaff || true + +# --- WASM plugins --- +FROM deps AS wasm-builder +RUN rm -rf common/src hermesllm/src prompt_gateway/src llm_gateway/src +COPY crates/common/src common/src +COPY crates/hermesllm/src hermesllm/src +COPY crates/prompt_gateway/src prompt_gateway/src +COPY crates/llm_gateway/src llm_gateway/src +RUN find common hermesllm prompt_gateway llm_gateway -name "*.rs" -exec touch {} + RUN cargo build --release --target wasm32-wasip1 -p prompt_gateway -p llm_gateway + +# --- Brightstaff binary --- +FROM deps AS brightstaff-builder +RUN rm -rf common/src hermesllm/src brightstaff/src +COPY crates/common/src common/src +COPY crates/hermesllm/src hermesllm/src +COPY crates/brightstaff/src brightstaff/src +RUN find common hermesllm brightstaff -name "*.rs" -exec touch {} + RUN cargo build --release -p brightstaff -FROM docker.io/envoyproxy/envoy:v1.37.0 AS envoy +FROM docker.io/envoyproxy/envoy:v1.37.0 AS envoy FROM python:3.13.6-slim AS arch -# Purge PAM to avoid CVE-2025-6020 and install needed tools -# 1) Install what you need while apt still works RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends supervisor gettext-base curl; \ apt-get clean; rm -rf /var/lib/apt/lists/* -# 2) Force-remove PAM packages (don’t use apt here) -# We ignore dependencies and remove files so scanners don’t find them. +# Remove PAM packages (CVE-2025-6020) RUN set -eux; \ dpkg -r --force-depends libpam-modules libpam-modules-bin libpam-runtime libpam0g || true; \ dpkg -P --force-all libpam-modules libpam-modules-bin libpam-runtime libpam0g || true; \ rm -rf /etc/pam.d /lib/*/security /usr/lib/security || true -COPY --from=builder /arch/target/wasm32-wasip1/release/prompt_gateway.wasm /etc/envoy/proxy-wasm-plugins/prompt_gateway.wasm -COPY --from=builder /arch/target/wasm32-wasip1/release/llm_gateway.wasm /etc/envoy/proxy-wasm-plugins/llm_gateway.wasm -COPY --from=builder /arch/target/release/brightstaff /app/brightstaff COPY --from=envoy /usr/local/bin/envoy /usr/local/bin/envoy WORKDIR /app -# Install uv using pip RUN pip install --no-cache-dir uv -# Copy Python dependency files COPY cli/pyproject.toml ./ COPY cli/uv.lock ./ COPY cli/README.md ./ RUN uv run pip install --no-cache-dir . -# Copy the rest of the application -COPY cli . +COPY cli/planoai planoai/ COPY config/envoy.template.yaml . COPY config/arch_config_schema.yaml . COPY config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf -RUN mkdir -p /var/log/supervisor && touch /var/log/envoy.log /var/log/supervisor/supervisord.log -RUN mkdir -p /var/log && \ - touch /var/log/access_ingress.log /var/log/access_ingress_prompt.log /var/log/access_internal.log /var/log/access_llm.log /var/log/access_agent.log +COPY --from=wasm-builder /arch/target/wasm32-wasip1/release/prompt_gateway.wasm /etc/envoy/proxy-wasm-plugins/prompt_gateway.wasm +COPY --from=wasm-builder /arch/target/wasm32-wasip1/release/llm_gateway.wasm /etc/envoy/proxy-wasm-plugins/llm_gateway.wasm +COPY --from=brightstaff-builder /arch/target/release/brightstaff /app/brightstaff -ENTRYPOINT ["sh","-c", "/usr/bin/supervisord"] +RUN mkdir -p /var/log/supervisor && \ + touch /var/log/envoy.log /var/log/supervisor/supervisord.log \ + /var/log/access_ingress.log /var/log/access_ingress_prompt.log \ + /var/log/access_internal.log /var/log/access_llm.log /var/log/access_agent.log + +ENTRYPOINT ["/usr/bin/supervisord"] diff --git a/cli/planoai/main.py b/cli/planoai/main.py index 116b54cf..3ba28d52 100644 --- a/cli/planoai/main.py +++ b/cli/planoai/main.py @@ -16,6 +16,7 @@ from planoai.utils import ( get_llm_provider_access_keys, has_ingress_listener, load_env_file_to_dict, + set_log_level, stream_access_logs, find_config_file, find_repo_root, @@ -68,6 +69,8 @@ def get_version(): @click.option("--version", is_flag=True, help="Show the plano cli version and exit.") @click.pass_context def main(ctx, version): + # Set log level from LOG_LEVEL env var only + set_log_level(os.environ.get("LOG_LEVEL", "info")) if version: click.echo(f"plano cli version: {get_version()}") ctx.exit() @@ -193,6 +196,10 @@ def up(file, path, foreground): else: env_stage[access_key] = env_file_dict[access_key] + # Pass log level to the Docker container — supervisord uses LOG_LEVEL + # to set RUST_LOG (brightstaff) and envoy component log levels + env_stage["LOG_LEVEL"] = os.environ.get("LOG_LEVEL", "info") + env.update(env_stage) start_arch(arch_config_file, env, foreground=foreground) diff --git a/cli/planoai/utils.py b/cli/planoai/utils.py index ea9b8dbd..d55774f4 100644 --- a/cli/planoai/utils.py +++ b/cli/planoai/utils.py @@ -7,15 +7,34 @@ import logging from planoai.consts import PLANO_DOCKER_NAME +# Standard env var for log level across all Plano components +LOG_LEVEL_ENV = "LOG_LEVEL" + +_env_log_level = os.environ.get(LOG_LEVEL_ENV, "info").upper() +_log_level = getattr(logging, _env_log_level, logging.INFO) + logging.basicConfig( - level=logging.INFO, + level=_log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) +def set_log_level(level: str): + """Set the log level for all loggers. Accepts: debug, info, warn, error.""" + global _log_level + numeric_level = getattr(logging, level.upper(), None) + if numeric_level is None: + raise ValueError(f"Invalid log level: {level}") + _log_level = numeric_level + logging.getLogger().setLevel(_log_level) + # Update all existing planoai loggers + for name in logging.Logger.manager.loggerDict: + logging.getLogger(name).setLevel(_log_level) + + def getLogger(name="cli"): logger = logging.getLogger(name) - logger.setLevel(logging.INFO) + logger.setLevel(_log_level) return logger diff --git a/config/supervisord.conf b/config/supervisord.conf index 35923974..a8762ef5 100644 --- a/config/supervisord.conf +++ b/config/supervisord.conf @@ -4,7 +4,7 @@ nodaemon=true [program:brightstaff] command=sh -c "\ envsubst < /app/arch_config_rendered.yaml > /app/arch_config_rendered.env_sub.yaml && \ - RUST_LOG=info \ + RUST_LOG=${LOG_LEVEL:-info} \ ARCH_CONFIG_PATH_RENDERED=/app/arch_config_rendered.env_sub.yaml \ /app/brightstaff 2>&1 | \ tee /var/log/brightstaff.log | \ @@ -18,8 +18,8 @@ stderr_logfile_maxbytes=0 command=/bin/sh -c "\ uv run python -m planoai.config_generator && \ envsubst < /etc/envoy/envoy.yaml > /etc/envoy.env_sub.yaml && \ - envoy -c /etc/envoy.env_sub.yaml \ - --component-log-level wasm:info \ + envoy -c /etc/envoy.env_sub.yaml \ + --component-log-level wasm:${LOG_LEVEL:-info} \ --log-format '[%%Y-%%m-%%d %%T.%%e][%%l] %%v' 2>&1 | \ tee /var/log/envoy.log | \ while IFS= read -r line; do echo '[plano_logs]' \"$line\"; done" diff --git a/docs/Dockerfile b/docs/Dockerfile index 1ac171d8..351e54ea 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,7 +1,7 @@ FROM sphinxdoc/sphinx WORKDIR /docs -ADD docs/requirements.txt /docs +COPY docs/requirements.txt /docs RUN python3 -m pip install -r requirements.txt RUN pip freeze diff --git a/docs/build_docs.sh b/docs/build_docs.sh index a4488b14..f36c54f6 100644 --- a/docs/build_docs.sh +++ b/docs/build_docs.sh @@ -1,3 +1,5 @@ +set -e + docker build -f docs/Dockerfile . -t sphinx # Clean build output locally