From 0d75d73c23cc37fdcb8d7c7c6f87e371320c4e95 Mon Sep 17 00:00:00 2001 From: Laurence Horrocks-Barlow Date: Wed, 20 May 2026 13:05:30 +0100 Subject: [PATCH 1/2] Add Docker, bash launcher, and Linux-first README Lets the project be served as a web app from a Linux host in one command: - Dockerfile (python:3.12-slim + non-root user + streamlit healthcheck) - docker-compose.yml with env_file, restart policy, configurable HOST_PORT, and an optional CA-bundle volume for self-signed GPFS GUIs - .dockerignore to keep the image lean - run.sh for native (non-Docker) runs: creates a venv on first use and launches either the Streamlit UI (default) or the REPL - .gitattributes pins LF line endings on shell/yaml/py so scripts stay executable when checked out on Linux from a Windows host - README rewritten with Linux/Docker quick starts in front, Windows behind Co-Authored-By: Claude Opus 4.7 --- .dockerignore | 19 +++++++++ .gitattributes | 9 +++++ Dockerfile | 31 +++++++++++++++ README.md | 98 ++++++++++++++++++++++++++++++---------------- docker-compose.yml | 15 +++++++ run.sh | 37 +++++++++++++++++ 6 files changed, 176 insertions(+), 33 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 run.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d1f7982 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.gitignore +.env +.env.* +!.env.example +.venv +venv +__pycache__ +*.pyc +*.pyo +.pytest_cache +.mypy_cache +.ruff_cache +.idea +.vscode +README.md +Dockerfile +docker-compose.yml +run.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..29855e6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +* text=auto eol=lf +*.sh text eol=lf +Dockerfile text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.py text eol=lf +*.md text eol=lf +*.txt text eol=lf +.env.example text eol=lf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f1699b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM python:3.12-slim AS base + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 \ + STREAMLIT_SERVER_ADDRESS=0.0.0.0 \ + STREAMLIT_SERVER_PORT=8501 \ + STREAMLIT_SERVER_HEADLESS=true \ + STREAMLIT_BROWSER_GATHER_USAGE_STATS=false + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY gpfs_agent ./gpfs_agent + +RUN useradd --create-home --uid 10001 app && chown -R app:app /app +USER app + +EXPOSE 8501 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD curl -fsS http://localhost:8501/_stcore/health || exit 1 + +CMD ["streamlit", "run", "gpfs_agent/web.py"] diff --git a/README.md b/README.md index 82c6878..764f960 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,70 @@ # gpfs-agent -Agentic chat front-end for the IBM Spectrum Scale (GPFS) REST API, driven by +Agentic chat front-end for the IBM Storage Scale (GPFS) REST API v3, driven by Claude with tool-use. Read-only by default; writes and destructive operations are gated behind explicit env flags. Ships with both a terminal REPL and a -pure-Python web UI (Streamlit). +pure-Python web UI (Streamlit). Runs natively or in Docker. ## Quick start -```powershell -cd D:\Projects\gpfs-agent -python -m venv .venv -.\.venv\Scripts\Activate.ps1 -pip install -r requirements.txt +### 1. Configure -Copy-Item .env.example .env +```bash +cp .env.example .env # edit .env: ANTHROPIC_API_KEY, GPFS_API_BASE, GPFS_USERNAME, GPFS_PASSWORD ``` -### Web UI +### 2a. Docker (recommended for Linux) -```powershell -streamlit run gpfs_agent\web.py +```bash +docker compose up -d --build +# open http://:8501 ``` -Opens http://localhost:8501 with a chat panel, a sidebar showing the mode and -system prompt, a tool-call log, and (when writes are enabled in `.env`) a -session-scoped "Auto-approve mutations" toggle. +Stop / view / update: -### Terminal REPL - -```powershell -python -m gpfs_agent +```bash +docker compose logs -f +docker compose pull && docker compose up -d +docker compose down ``` -Commands inside the REPL: `/reset`, `/system`, `/quit`. +Override the host port with `HOST_PORT=9000 docker compose up -d`. If your GPFS +GUI uses a private CA, drop the cert into `./certs/ca.pem` and uncomment the +volume + `GPFS_CA_BUNDLE` lines in `docker-compose.yml`. + +### 2b. Native Linux (no Docker) + +```bash +./run.sh # web UI on 0.0.0.0:8501 +./run.sh cli # terminal REPL +HOST=127.0.0.1 PORT=8080 ./run.sh # bind override +``` + +`run.sh` creates a `.venv` on first run and installs dependencies. + +### 2c. Windows (PowerShell) + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -r requirements.txt +streamlit run gpfs_agent\web.py # web UI +python -m gpfs_agent # REPL +``` + +## Web UI + +The Streamlit app lives at `/` on the configured port. It shows: + +- A chat panel (uses Claude with tool-use against the GPFS REST API) +- A sidebar with model, API base, mode, the live system prompt, and a + tool-call log (last 25 calls with status, reason and payload) +- A "Reset conversation" button +- When `GPFS_ALLOW_WRITES=true`: a session-scoped *"Auto-approve mutations"* + checkbox. Mutating calls are denied unless this is on. + +REPL commands: `/reset`, `/system`, `/quit`. ## How the guardrails work @@ -54,15 +85,9 @@ Guardrails are enforced in three layers: `cfg.allowed_methods`, so it cannot ask for `DELETE` when disabled. 2. **Dispatcher** — `tools.py` re-checks method and allow/deny regex and triggers the confirm hook before any mutating HTTP call. -3. **System prompt** — `agent.py` injects natural-language guardrails so the - model behaves sensibly even when a guardrail is loose. - -### Mutation confirmation - -- **REPL**: prints the planned call and waits for `y` at the terminal. -- **Web UI**: mutations are denied unless the sidebar checkbox - *"Auto-approve mutations this session"* is on. The checkbox only appears - when `GPFS_ALLOW_WRITES=true`. +3. **System prompt** — `agent.py` injects natural-language guardrails plus a + curated v3 endpoint reference (see `endpoints.py`) so the agent has + accurate context without having to guess. ## Project layout @@ -73,17 +98,24 @@ gpfs_agent/ ├── agent.py # Anthropic tool-use loop ├── cli.py # Rich REPL ├── config.py # .env loading & validation +├── endpoints.py # curated /scalemgmt/v3 reference (in-context) ├── gpfs_client.py # httpx wrapper around the GPFS REST API ├── tools.py # Tool schemas + guarded dispatcher └── web.py # Streamlit chat app +Dockerfile # python:3.12-slim + streamlit +docker-compose.yml # one-command deploy +run.sh # bash helper for native Linux ``` ## Notes on the GPFS REST API - `GPFS_API_BASE` should include the version prefix, e.g. - `https://gui:443/scalemgmt/v3`. Shipping Spectrum Scale builds currently - serve `/v2`; set whichever your cluster exposes. -- Auth is HTTP Basic against the Spectrum Scale GUI user. Use an account - scoped to the operations you intend to allow. + `https://gui:46443/scalemgmt/v3`. v3 is the current native REST API on + Storage Scale 5.2.x / 6.0.x; the typical port is 443 (CNSA / container + deploys) or 46443 (native install). +- Auth is HTTP Basic against a Storage Scale GUI user. Use an account scoped + to the operations you intend to allow. - For self-signed lab gear, set `GPFS_VERIFY_TLS=false` or point - `GPFS_CA_BUNDLE` at a PEM file. + `GPFS_CA_BUNDLE` at a PEM file (mount as a volume when running in Docker). +- The interactive Swagger UI lives at `https:///openapi/` — useful for + discovering endpoints your specific release ships. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..18aa438 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + gpfs-agent: + build: . + image: gpfs-agent:latest + container_name: gpfs-agent + restart: unless-stopped + ports: + - "${HOST_PORT:-8501}:8501" + env_file: + - .env + # Uncomment to mount a custom CA bundle and tell the app to use it. + # volumes: + # - ./certs:/certs:ro + # environment: + # GPFS_CA_BUNDLE: /certs/ca.pem diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..bfc8ed0 --- /dev/null +++ b/run.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +if [[ ! -f .env ]]; then + echo "No .env found. Copy .env.example to .env and edit it first." >&2 + exit 1 +fi + +if [[ ! -d .venv ]]; then + echo "Creating virtualenv at .venv..." + python3 -m venv .venv + .venv/bin/pip install -q --upgrade pip + .venv/bin/pip install -q -r requirements.txt +fi + +MODE="${1:-web}" +HOST="${HOST:-0.0.0.0}" +PORT="${PORT:-8501}" + +case "$MODE" in + web) + exec .venv/bin/streamlit run gpfs_agent/web.py \ + --server.address "$HOST" \ + --server.port "$PORT" \ + --server.headless true \ + --browser.gatherUsageStats false + ;; + cli|repl) + exec .venv/bin/python -m gpfs_agent + ;; + *) + echo "Usage: $0 [web|cli]" >&2 + exit 2 + ;; +esac From c6bd3d0d741e066d25ee9272c4797afde25fb5e5 Mon Sep 17 00:00:00 2001 From: Laurence Horrocks-Barlow Date: Wed, 20 May 2026 14:23:48 +0100 Subject: [PATCH 2/2] docs: add changelog for v0.1.0 and v0.2.0 --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1a99268 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,58 @@ +# Changelog + +All notable changes to this project are documented here. +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and +this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.2.0] — 2026-05-20 + +### Added +- **Docker support.** `Dockerfile` (python:3.12-slim, non-root user, Streamlit + healthcheck) and `docker-compose.yml` for one-command Linux deploys. + Configurable `HOST_PORT`, optional CA-bundle volume for self-signed GUIs. +- **Bash launcher.** `run.sh` for native (non-Docker) Linux runs. Creates a + `.venv` on first use and launches either the Streamlit UI (default) or the + REPL. Honours `HOST` and `PORT` env vars. +- **Cross-platform line endings.** `.gitattributes` pins LF on shell scripts, + YAML, Python and Markdown so checkouts on Linux from a Windows host don't + break the launcher with `^M` characters. +- **`.dockerignore`** to keep the image lean. + +### Changed +- **README rewritten** with Docker + Linux quick starts in front of the + Windows / PowerShell path. +- `.env.example` now flags v3 as the current API and documents the GUI port + conventions (443 for CNSA / containerised, 46443 for native installs) plus + the Swagger UI location. + +## [0.1.0] — 2026-05-20 + +Initial release. + +### Added +- **Agentic chat over the GPFS REST API v3.** Anthropic Claude with native + tool-use; a single `gpfs_request` tool whose verb enum is narrowed by env + config so disallowed methods can't even be requested. +- **Two front-ends.** Rich-styled terminal REPL (`python -m gpfs_agent`) and a + pure-Python Streamlit web UI (`gpfs_agent/web.py`). Shared + Agent/Dispatcher/Client stack. +- **Env-driven guardrails.** + - Read-only by default; `GPFS_ALLOW_WRITES` enables `POST/PUT`; + `GPFS_ALLOW_DESTRUCTIVE` enables `DELETE`. + - `GPFS_CONFIRM_MUTATIONS` gates each mutating call at the human. + - Optional `GPFS_PATH_ALLOW` / `GPFS_PATH_DENY` regex on the request path. + - Free-text `GPFS_INSTRUCTIONS` / `GPFS_INSTRUCTIONS_FILE` appended to the + system prompt for site-specific rules. +- **Curated `/scalemgmt/v3` endpoint reference** baked into the system prompt + (`gpfs_agent/endpoints.py`), compiled from IBM Storage Scale 5.2.3 / 6.0.0 + docs. Covers cluster, nodes, NSDs, filesystems, filesets, snapshots, + quotas, policies, jobs and perfmon — plus async-job conventions and the + `:link` / `:unlink` / `:batchDelete` custom-verb syntax. +- **TLS hardening knobs.** `GPFS_VERIFY_TLS` and `GPFS_CA_BUNDLE` for + self-signed lab gear. + +[Unreleased]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.2.0...HEAD +[0.2.0]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.1.0...v0.2.0 +[0.1.0]: https://git.discworld.casa/laurence/gpfsagent/releases/tag/v0.1.0