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
87 changes: 87 additions & 0 deletions AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# NetCore — Audit (2026-10-08, Stage 1)

Baseline recorded before any change. Every claim below came from a command run locally
against a clean clone.

## Baseline

| Check | Result |
|---|---|
| Clone | clean, branch `master` |
| Stack | FastAPI 0.115.6 + SQLAlchemy 2.0.36 + Alembic + Netmiko 4.4.0 + OpenAI SDK, Python **3.12** target (`pyproject.toml` `target-version = "py312"`) |
| Size | 31 Python files, ~7,254 lines |
| `import main` | **FAILED — `AttributeError: 'Settings' object has no attribute 'OPENAI_API_KEY'`** |
| Routes | 0 (app could not be constructed) |
| Tests | **none** — `tests/` contains only `mock_switch.py`; `pyproject.toml` declares `testpaths = ["tests"]`, `python_files = ["test_*.py"]` |
| `ruff check .` | **285 errors, 189 auto-fixable** |
| `gitleaks detect` | clean |

## Toolchain obstacle (real, not a defect)

The host's default interpreter is **Python 3.14.7**. Several pinned deps (`psycopg2-binary`,
`pydantic-core 2.27.2`) have no 3.14 wheels and fail to build, and Python 3.14 is far ahead
of the repo's declared 3.12 target.

Resolution: system `python3.12` exists, but `python3.12 -m venv` is unavailable on this
image (Debian splits `python3.12-venv` out) and system pip is PEP 668–blocked. Used **`uv`**
(0.11.30, already installed) to create the venv: `uv venv --python 3.12 .venv` +
`VIRTUAL_ENV=... uv pip install -r backend/requirements.txt`. All pins installed, including
`psycopg2-binary`. **This is a documented environment gap, not a repo bug** — the repo's own
target is correct.

## Findings

### N1 — CRITICAL: the backend cannot start (fixed)
`backend/config.py` read `settings.OPENAI_API_KEY` in its validation block (line 61), but
the `Settings` model **never defined that field** — nor `OPENAI_MODEL`.

Git history pins the regression precisely: commit **`5e43b24`**
("chore(maintenance): audit fixes, screenshots", authored 2026-10-07 by `openhands`)
**deleted** these two lines from `Settings`:

```python
# OpenAI / AI
OPENAI_API_KEY: Optional[str] = None
OPENAI_MODEL: str = "gpt-4o"
```

…while in the *same commit* adding the validation `if not settings.OPENAI_API_KEY:` that
reads them. Every commit before `5e43b24` defined the fields, so **the app was importable
until that commit and broken after it**.

Impact: `uvicorn main:app` dies at import. Nothing works — API, CLI, Docker, docs.

**Fix:** restored both fields. Verified: `import main` → OK, **67 routes** registered.

### N2 — CRITICAL: syntax error in `backend/routers/chat.py` (fixed)
Line 18's docstring was indented 8 spaces inside a 4-space function body →
`IndentationError: unindent does not match any outer indentation level`. This blocked
`main.py` on the line *after* N1 was cleared.

**Fix:** indentation corrected.

### N3 — syntax error in `scripts/cli.py` (fixed)
Line 151 contained a string literal broken by real newlines:
`config_text = "⏎".join(...)` → `SyntaxError: unterminated string literal`.

**Fix:** restored the `\n` escapes.

### N4 — syntax error in `scripts/nethermind.py` (fixed)
`main()` read module-global `API_BASE` at line 251 (inside an f-string for `--help`) but
declared `global API_BASE` at line 329 → `SyntaxError: name 'API_BASE' is used prior to
global declaration`.

**Fix:** moved the `global` declaration to the top of `main()`. Verified `--help` runs.

### N5 — 285 ruff errors (not fixed, reported)
Against the repo's own configured rule set (`E,F,W,I,UP,B,SIM`, line-length 120). 189 are
auto-fixable. Not auto-fixed in this pass: a 285-error sweep is a broad, low-risk-but-large
diff that should be its own reviewed PR rather than mixed into a critical-fix branch.
Recorded as the next roadmap item.

## Not verified (honest gaps)
- **No frontend build.** The UI is a separate Next.js app; it was not built or screenshotted in this pass.
- **No real switch was contacted.** All testing is against the API with an empty database; the Netmiko/proxy paths were not exercised against hardware.
- **No Docker** on this host → `docker-compose.yml` not exercised.
- N1–N4 were fixed and verified by import + live HTTP, but the app's *behaviour* on real
config pushes remains unverified.
34 changes: 34 additions & 0 deletions PERSONA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# NetCore — Persona

Archetype: The Wire Whisperer

One-line pitch: Configures the switches nobody wants to console into by hand.

Voice: terse · technical · unimpressed. Never says "seamless". Never anthropomorphises the hardware.

Sample sentences:
- "Twenty-nine devices, one template, no typos."
- "The running-config is the truth; the diagram is a rumour."
- "Rollback is a button, not a promise."

Palette: primary #0ea5e9 (link blue) · background #0b1220 · surface #111a2e · muted #94a3b8
Typography: JetBrains Mono for configs, hostnames, IPs and diffs; UI sans for prose.

Emoji policy: none

Banner concept: A real terminal capture of a dry-run config push, or the live dashboard switch
list — captured from the running app, never stock art.

Do:
- Show the diff before applying it.
- Say plainly which vendors and transports are actually tested.
- Keep secrets out of config backups and screenshots.

Don't:
- Claim vendor support that was never exercised against a device.
- Imply a dry run touched a switch.
- Present a mock switch as a real one.

Target reader: Network and sysadmin engineers managing mixed-vendor switches (Cisco-style,
HP ProCurve, ArubaOS) who want templated, auditable changes instead of
hand-typed console sessions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# NetCore

**AI-powered network switch management** — multi-vendor SSH and serial access, 50+ Jinja2 templates, an AI agent, and security auditing for mixed network fleets.
**AI-powered network switch management** — multi-vendor SSH and serial access, modular template system, an AI agent, and security auditing for mixed network fleets.

<a href="https://github.com/OneByJorah/NetCore/stargazers"><img src="https://img.shields.io/github/stars/OneByJorah/NetCore?style=flat-square" alt="Stars"></a>
<a href="https://github.com/OneByJorah/NetCore/commits"><img src="https://img.shields.io/github/last-commit/OneByJorah/NetCore?style=flat-square" alt="Last commit"></a>
Expand All @@ -13,7 +13,7 @@
<img src="https://img.shields.io/badge/FastAPI-009688?style=flat-square&logo=fastapi&logoColor=white" alt="FastAPI">
<img src="https://img.shields.io/badge/Electron-desktop-47848F?style=flat-square&logo=electron&logoColor=white" alt="Electron desktop">

![NetCore screenshot](docs/assets/screenshot.png)
![NetCore dashboard](docs/assets/screenshots/01-dashboard.png)

</div>

Expand All @@ -39,7 +39,7 @@ NetCore is a full-stack platform for managing network switches and routers acros

- **Multi-vendor support** — Cisco IOS/XR/NX-OS, HP ArubaOS-Switch (ProCurve), Juniper JunOS, Arista EOS, and Linux.
- **SSH and serial console** — Netmiko for SSH, pyserial for RS-232/USB out-of-band access, plus Telnet deploy.
- **50+ Jinja2 templates** — built-in configuration templates for Aruba, Cisco, and generic devices.
- **Modular template system** — extensible Jinja2 templates for Aruba, Cisco, and generic devices.
- **Config parser** — turn existing running-config text into structured data for editing and re-rendering.
- **AI chat assistant** — OpenAI-powered agent with tool calling for natural-language operations.
- **IRIS-style workflow engine** — disciplined config change management with approval gates.
Expand Down
44 changes: 42 additions & 2 deletions backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,18 @@ def database_url_safe(self) -> str:
return url

# OpenAI / AI
# These fields are read by config validation below and by
# services/nethermind_agent.py; they must remain defined or the app cannot import.
OPENAI_API_KEY: Optional[str] = None
OPENAI_MODEL: str = "gpt-4o"

# SSH
SSH_USERNAME: str = "admin"
SSH_USERNAME: str = ""
SSH_PASSWORD: str = ""
SSH_TIMEOUT: int = 30

# Security
SECRET_KEY: str = "change-me"
SECRET_KEY: str = ""

# CORS
CORS_ORIGINS: str = "http://localhost:3000,http://localhost:5173"
Expand All @@ -50,3 +52,41 @@ def cors_origins_list(self) -> list[str]:


settings = Settings()


# Validate security-critical configuration
if not settings.SECRET_KEY:
import warnings
warnings.warn(
"SECRET_KEY is not set. Set a strong SECRET_KEY in your .env file "
"for production use.",
RuntimeWarning,
stacklevel=2,
)

if not settings.OPENAI_API_KEY:
import warnings
warnings.warn(
"OPENAI_API_KEY is not set. The AI agent functionality will not work "
"without a valid OpenAI API key.",
RuntimeWarning,
stacklevel=2,
)

if not settings.DATABASE_URL:
import warnings
warnings.warn(
"DATABASE_URL is not set. Using default SQLite. "
"For production, set a proper database connection string.",
RuntimeWarning,
stacklevel=2,
)

# Add additional warnings for production
import warnings
if settings.DEBUG:
warnings.warn(
"DEBUG mode is enabled. Do not use in production.",
RuntimeWarning,
stacklevel=2,
)
2 changes: 1 addition & 1 deletion backend/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class ConfigDiff(Base):


class ChatMessage(Base):
"""Chat history for Nethermind AI agent sessions."""
"""Chat history for NetCore AI agent sessions."""
__tablename__ = "chat_messages"

id = Column(Integer, primary_key=True)
Expand Down
2 changes: 1 addition & 1 deletion backend/routers/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# Nethermind — API Routers
# NetCore — API Routers
2 changes: 1 addition & 1 deletion backend/routers/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

@router.post("/stream")
async def chat_stream(req: ChatRequest):
"""Send a message to NetCore AI and stream the response."""
"""Send a message to NetCore AI and stream the response."""
if not req.message.strip():
raise HTTPException(status_code=400, detail="Message cannot be empty")
return await ask(req.session_id, req.message)
Expand Down
2 changes: 1 addition & 1 deletion backend/services/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from dataclasses import dataclass
from typing import List, Optional

logger = logging.getLogger("nethermind.connection")
logger = logging.getLogger("netcore.connection")


# Prompts a ProCurve/Aruba switch shows (we match the trailing '#' or '>').
Expand Down
2 changes: 1 addition & 1 deletion backend/services/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from services.connection import Connection, PRIV_PROMPT, CONFIG_PROMPT
from services.switch_config_model import SwitchConfig

logger = logging.getLogger("nethermind.deployer")
logger = logging.getLogger("netcore.deployer")

TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "templates")
ProgressCB = Callable[[str], None]
Expand Down
11 changes: 4 additions & 7 deletions desktop/build-linux.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#!/bin/bash
set -e

echo "============================================"
echo " Nethermind Desktop - Linux Build"
echo " NetCore Desktop - Linux Build"
echo "============================================"
echo ""

Expand All @@ -27,7 +24,7 @@ echo ""
echo "Output files are in: desktop/dist/"
echo ""
echo "Available packages:"
echo " - Nethermind-*-x64.AppImage (Universal Linux)"
echo " - Nethermind-*-x64.deb (Debian/Ubuntu)"
echo " - Nethermind-*-x64.rpm (Fedora/RHEL)"
echo " - NetCore-*-x64.AppImage (Universal Linux)"
echo " - NetCore-*-x64.deb (Debian/Ubuntu)"
echo " - NetCore-*-x64.rpm (Fedora/RHEL)"
echo ""
12 changes: 6 additions & 6 deletions desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "nethermind",
"name": "netcore",
"version": "1.0.0",
"description": "AI-powered network switch configuration management",
"main": "main.js",
Expand All @@ -13,8 +13,8 @@
"build:all": "electron-builder --win --linux --mac"
},
"build": {
"appId": "com.nethermind.app",
"productName": "Nethermind",
"appId": "com.netcore.app",
"productName": "NetCore",
"directories": {
"output": "dist"
},
Expand All @@ -34,14 +34,14 @@
"arch": ["x64"]
}
],
"artifactName": "Nethermind-${version}-win-${arch}.exe"
"artifactName": "NetCore-${version}-win-${arch}.exe"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Nethermind",
"shortcutName": "NetCore",
"installerLanguages": ["en_US"],
"language": "1033"
},
Expand All @@ -61,7 +61,7 @@
}
],
"category": "Network",
"artifactName": "Nethermind-${version}-linux-${arch}.${ext}"
"artifactName": "NetCore-${version}-linux-${arch}.${ext}"
},
"mac": {
"target": "dmg",
Expand Down
Loading
Loading