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
20 changes: 20 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.venv/
__pycache__/
*.pyc
*.pyo
.env
.env.*
!.env.example

frontend/node_modules/
frontend/dist/
frontend/.vite/

.git/
.github/

tests/
docs/
design-system/
PLAN.md
*.md
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CI

on:
push:
branches:
- "**"
pull_request:

jobs:
lint-and-test:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install uv
run: python -m pip install --upgrade pip uv

- name: Install dependencies
run: uv sync --group dev

- name: Ruff lint (gating)
run: uv run ruff check tests

- name: Ruff lint src (informational)
run: uv run ruff check src --exit-zero

- name: Pytest
run: uv run pytest -q
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# ── Stage 1: install Python dependencies with uv ─────────────────────────────
FROM python:3.12.9-slim-bookworm AS deps

COPY --from=ghcr.io/astral-sh/uv:0.6.17 /uv /uvx /bin/

WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project

# ── Stage 2: final image ──────────────────────────────────────────────────────
FROM python:3.12.9-slim-bookworm

# AWS CLI v2 — required for the bash execution tool (run_bash_command)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl unzip ca-certificates \
&& curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
&& unzip -q /tmp/awscliv2.zip -d /tmp \
&& /tmp/aws/install \
&& rm -rf /tmp/aws /tmp/awscliv2.zip \
&& apt-get purge -y --auto-remove curl unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:0.6.17 /uv /uvx /bin/

WORKDIR /app

# Copy installed packages from deps stage
COPY --from=deps /app/.venv .venv

# Copy application source
COPY src/ src/
COPY migrations/ migrations/
COPY scripts/ scripts/

RUN groupadd --gid 10001 app && useradd --uid 10001 --gid app --create-home --shell /usr/sbin/nologin app
RUN chown -R app:app /app

ENV PATH="/app/.venv/bin:$PATH" \
PYTHONPATH="/app/src" \
HOME="/home/app" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

USER app

EXPOSE 8000

CMD ["uvicorn", "api.app:app", "--host", "0.0.0.0", "--port", "8000"]
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and gives actionable mitigation plans — without the AWS DevOps Agent price tag

- **LangChain DeepAgents** as the agent framework — planning, tool orchestration, and session memory out of the box
- **19 read-only AWS tools** across CloudWatch, CloudTrail, ECS, Lambda, EC2, RDS, and IAM — plain Python functions, schemas inferred automatically
- **Sandboxed bash execution tool** — agent can run whitelisted read-only AWS CLI, kubectl, and docker commands as a last resort when the structured tools fall short; every command validated against an allowlist before execution; never uses `shell=True`; hard 30-second timeout
- Includes **CloudWatch Logs Insights** (`query_logs_insights`) — full query language support: `fields`, `filter`, `stats`, `sort`, `limit`; results include scanned MB
- **Streaming responses** — FastAPI SSE endpoint streams agent tokens in real time as the LLM reasons; tool calls appear as they complete
- **Web UI** — FastAPI backend with a chat interface that shows:
Expand Down Expand Up @@ -79,12 +80,23 @@ the full schema reference.

### 5. Run

**Web UI**
**Option A — Docker Compose (recommended, AWS CLI included)**

You need two terminals running simultaneously:
```bash
docker compose up --build
# Backend: http://localhost:8000
# Frontend: http://localhost:80
```

The backend image installs AWS CLI v2 automatically — the bash execution tool
works out of the box. Host AWS credentials (`~/.aws`) are mounted read-only
into the container. For production on AWS, remove the volume mount and attach
an IAM role to the instance/task instead.

**Option B — Local dev (two terminals)**

```bash
# Terminal 1 — FastAPI backend (API + SSE streaming)
# Terminal 1 — FastAPI backend
uv run uvicorn src.api.app:app --reload
```

Expand All @@ -95,6 +107,9 @@ npm run dev
# Open http://localhost:5173
```

> **Note:** local dev requires `aws` CLI installed on your machine for the bash
> tool to work. Install it from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html

**CLI**

```bash
Expand Down Expand Up @@ -192,6 +207,7 @@ docs/
- [ ] **Knowledge base** — attach internal runbooks, post-mortems, and architecture docs so the agent grounds answers in org-specific context
- [ ] **Multi-account AWS** — support multiple AWS profiles per org via `aws_profiles` table (schema already in place)
- [ ] **Multi-cloud support** — extend tooling to GCP (Cloud Monitoring, Cloud Logging, GKE) and Azure (Monitor, Log Analytics, AKS); unified incident investigation across providers
- [ ] **Bash sandbox Phase 2 — isolated Docker container** — each `run_bash_command` call runs inside a throwaway container: `--network none`, `--read-only` filesystem, `--memory 256m`, `--cpus 0.5`, non-root user; container destroyed immediately after the command completes; IAM read-only role remains the last line of defense

### Product (SaaS)
- [ ] **Redis cache** — replace in-process `cachetools` with Redis; shared across workers, survives restarts, per-org cache namespacing to prevent data leakage between tenants
Expand All @@ -200,6 +216,27 @@ docs/
- [ ] **Per-org AWS credential store** — encrypted credential vault per organization; agents use org-scoped profiles instead of a single global `AWS_PROFILE`
- [ ] **Billing & usage metering** — track token usage and tool calls per org/user; expose cost dashboards; integrate with Stripe for usage-based billing

## Security & Sandboxing

The bash execution tool runs whitelisted read-only commands only.
Every command is validated against an allowlist before execution —
anything not on the list is rejected immediately and logged.

**Current (Phase 1):** allowlist validation + subprocess with hard timeout.
No write commands permitted under any circumstances. `shell=True` is never used.

**Phase 2 — Isolated sandbox (planned):**
- Every bash command runs inside a throwaway Docker container
- `--network none` — no internet access from inside the sandbox
- `--read-only` filesystem — container cannot write to disk
- `--memory 256m` and `--cpus 0.5` — resource caps
- Non-root user inside the container
- Container is destroyed immediately after the command completes
- Even if the LLM misbehaves, the IAM read-only role is the last line of defense

The agent never executes fixes automatically. It investigates, suggests,
and waits for human approval before anything changes.

## Development

```bash
Expand Down
68 changes: 68 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
services:

# ── PostgreSQL ──────────────────────────────────────────────────────────────
db:
image: postgres:16.4
environment:
POSTGRES_DB: opendevops
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev -d opendevops"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped

# ── FastAPI backend ─────────────────────────────────────────────────────────
backend:
build: .
ports:
- "8000:8000"
env_file: .env
environment:
# Keep one canonical variable name; container uses docker-internal host.
DATABASE_URL: postgresql://dev:dev@db:5432/opendevops
volumes:
# Mount host AWS credentials read-only so the bash tool can call the CLI.
# For production on AWS (ECS/EC2), remove this and use an IAM role instead.
- ${USERPROFILE}/.aws:/home/app/.aws:ro
depends_on:
db:
condition: service_healthy
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
tmpfs:
- /tmp
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/openapi.json', timeout=5)"]
interval: 15s
timeout: 5s
retries: 5
restart: unless-stopped

# ── React frontend (production build served by nginx) ──────────────────────
frontend:
build: ./frontend
ports:
- "80:8080"
depends_on:
- backend
security_opt:
- no-new-privileges:true
healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/ || exit 1"]
interval: 15s
timeout: 5s
retries: 5
restart: unless-stopped

volumes:
postgres_data:
20 changes: 20 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Production build — React app served by nginx
FROM node:20-bookworm-slim AS build

WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN mkdir -p /var/cache/nginx /var/run /var/log/nginx /tmp/nginx \
&& chown -R nginx:nginx /usr/share/nginx/html /etc/nginx/conf.d /var/cache/nginx /var/run /var/log/nginx /tmp/nginx \
&& sed -ri 's@^\s*pid\s+[^;]+;@pid /tmp/nginx.pid;@' /etc/nginx/nginx.conf

USER nginx
EXPOSE 8080

CMD ["nginx", "-g", "daemon off;"]
21 changes: 21 additions & 0 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
server {
listen 8080;

root /usr/share/nginx/html;
index index.html;

# Proxy API + SSE calls to the backend
location ~ ^/(chat|sessions|history|stats) {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # required for SSE streaming
proxy_cache off;
proxy_read_timeout 300s;
}

# SPA fallback — all other routes serve index.html
location / {
try_files $uri $uri/ /index.html;
}
}
Loading
Loading