numnumdock 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: numnumdock
3
+ Version: 1.0.0
4
+ Summary: The LLM-aware secure deployment engine. Secure by default AI.
5
+ Author-email: Your Name <your@email.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: typer
9
+ Requires-Dist: rich
10
+ Requires-Dist: docker
11
+ Requires-Dist: fastapi
12
+ Requires-Dist: uvicorn
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pypiwin32; sys_platform == "win32"
15
+
16
+ # 🐰 NumNumDock
17
+
18
+ **The Secure-by-Default Local AI Deployment Engine.**
19
+
20
+ NumNumDock is a command-line tool that allows developers to run local Large Language Models (LLMs) securely. It wraps Ollama in a hardened Docker container with network isolation and provides a secure proxy for API key authentication.
21
+
22
+ ## 🚀 Features
23
+
24
+ - **Zero-Config Security:** Runs Ollama in an isolated Docker network (no internet access).
25
+ - **API Key Authentication:** Built-in reverse proxy that verifies API keys before reaching the LLM.
26
+ - **Advanced Secret Redaction:** Fail-closed redaction layer that masks API keys, passwords, and PEM keys from generated text.
27
+ - **Hardware Detection:** Automatically recommends the best model based on your GPU/CPU.
28
+ - **Random Port Allocation:** Prevents port squatting and local network exposure.
29
+
30
+ ## 📦 Installation
31
+
32
+ ```bash
33
+ pip install numnumdock
34
+ ```
35
+
36
+ 🛠️ Usage
37
+ Deploy a secure local model:
38
+
39
+ bash
40
+ numnumdock run llama3.2:1b
41
+ Check the status of your local deployments:
42
+
43
+ bash
44
+ numnumdock status
45
+ Run security diagnostics:
46
+
47
+ bash
48
+ numnumdock doctor
49
+ 🔒 Security Architecture
50
+ NumNumDock ensures that your local AI is never exposed to the public internet. It creates an internal Docker bridge network, downloads the model securely, disconnects the container from the internet, and serves it via an authenticated FastAPI proxy on 127.0.0.1.
51
+
52
+ 📄 License
53
+ MIT License
@@ -0,0 +1,95 @@
1
+ import typer
2
+ import subprocess
3
+ import shutil
4
+ import uvicorn
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+ from core.hardware import get_hardware_info, get_recommended_model
9
+ from core.docker_manager import deploy_secure_llm, get_free_port
10
+ from core.security import generate_api_key
11
+ from core.proxy import build_app # <-- Yahan build_app import hoga
12
+
13
+ app = typer.Typer()
14
+ console = Console()
15
+
16
+ def check_docker():
17
+ if not shutil.which("docker"):
18
+ return False, "Docker not installed."
19
+ try:
20
+ result = subprocess.run(["docker", "info"], capture_output=True, text=True, timeout=5)
21
+ return (True, "Running") if result.returncode == 0 else (False, "Not running. Start Docker Desktop.")
22
+ except Exception:
23
+ return False, "Docker check failed."
24
+
25
+ @app.command()
26
+ def run(model: str = typer.Argument(None, help="Model name (e.g., llama3.2:1b)")):
27
+ console.print(Panel.fit("[bold green]🚀 NumNumDock[/bold green]\n[dim]Secure LLM Deployment[/dim]"))
28
+
29
+ hw = get_hardware_info()
30
+ if model is None:
31
+ model = get_recommended_model(hw)
32
+ console.print(f"🤖 Auto-selected model: [cyan]{model}[/cyan]")
33
+
34
+ docker_ok, docker_msg = check_docker()
35
+
36
+ if docker_ok:
37
+ console.print("🐳 Deploying via Secure Docker Network...")
38
+ try:
39
+ # Deploy Docker Container
40
+ container_id, proxy_port, upstream_url = deploy_secure_llm(model)
41
+ api_key = generate_api_key()
42
+
43
+ # Build the Proxy App
44
+ proxy_app = build_app(api_key, upstream_url)
45
+
46
+ console.print(Panel(
47
+ f"[bold green]✅ NumNumDock Deployed securely![/bold green]\n\n"
48
+ f"Container ID: {container_id[:12]}\n"
49
+ f"🔒 Secure Local Endpoint: http://127.0.0.1:{proxy_port}\n"
50
+ f"🔑 [bold red]API KEY: {api_key}[/bold red]\n\n"
51
+ f"[yellow]NOTE: Keep this terminal open. Press Ctrl+C to stop.[/yellow]",
52
+ border_style="green"
53
+ ))
54
+
55
+ # Run proxy in FOREGROUND so it blocks and stays alive
56
+ uvicorn.run(proxy_app, host="127.0.0.1", port=proxy_port, log_level="error")
57
+
58
+ except Exception as e:
59
+ console.print(f"[bold red]❌ Deployment Failed: {e}[/bold red]")
60
+ else:
61
+ console.print(f"[bold red]❌ Docker is not available: {docker_msg}[/bold red]")
62
+
63
+ @app.command()
64
+ def status():
65
+ console.print(Panel.fit("[bold cyan]📊 NumNumDock Status[/bold cyan]"))
66
+ docker_ok, _ = check_docker()
67
+
68
+ console.print(f"Docker: {'[green]✅ Running[/green]' if docker_ok else '[red]❌ Not available[/red]'}")
69
+
70
+ if docker_ok:
71
+ try:
72
+ import docker
73
+ client = docker.from_env()
74
+ containers = client.containers.list(filters={"name": "numnum-llm"})
75
+ if containers:
76
+ table = Table(title="Running Container")
77
+ table.add_column("ID", style="cyan")
78
+ table.add_column("Name", style="green")
79
+ table.add_column("Status", style="yellow")
80
+ for c in containers:
81
+ table.add_row(c.id[:12], c.name, c.status)
82
+ console.print(table)
83
+ else:
84
+ console.print("[yellow]No NumNumDock containers running.[/yellow]")
85
+ except Exception:
86
+ pass
87
+
88
+ @app.command()
89
+ def doctor():
90
+ console.print(Panel.fit("[bold cyan]🩺 NumNumDock Doctor[/bold cyan]"))
91
+ console.print("✅ Checking Docker security configurations...")
92
+ console.print("[green]Note: NumNumDock now isolates the network by default and uses random ports.[/green]")
93
+
94
+ if __name__ == "__main__":
95
+ app()
@@ -0,0 +1,76 @@
1
+ import docker
2
+ import socket
3
+ import re
4
+
5
+ def get_free_port():
6
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
7
+ s.bind(('127.0.0.1', 0))
8
+ port = s.getsockname()[1]
9
+ s.close()
10
+ return port
11
+
12
+ def validate_model_name(model_name: str):
13
+ if not re.match(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,100}$", model_name):
14
+ raise ValueError("Invalid model name format")
15
+
16
+ def deploy_secure_llm(model_name: str):
17
+ validate_model_name(model_name)
18
+
19
+ try:
20
+ client = docker.from_env()
21
+ except Exception:
22
+ raise Exception("Docker is not running.")
23
+
24
+ internal_net_name = "numnum-internal-net"
25
+ bridge_net_name = "bridge"
26
+
27
+ # 1. Create Internal Network (No Internet)
28
+ try:
29
+ internal_net = client.networks.create(internal_net_name, driver="bridge", internal=True)
30
+ except docker.errors.APIError:
31
+ internal_net = client.networks.get(internal_net_name)
32
+
33
+ # 2. Pull Image
34
+ client.images.pull('ollama/ollama:latest')
35
+
36
+ # Cleanup old container
37
+ try:
38
+ client.containers.get('numnum-llm').remove(force=True)
39
+ except:
40
+ pass
41
+
42
+ # 3. Run Container on Default Bridge first (To download model)
43
+ container = client.containers.run(
44
+ 'ollama/ollama:latest',
45
+ command="serve",
46
+ network=bridge_net_name,
47
+ detach=True,
48
+ volumes={'numnum_data': {'bind': '/root/.ollama', 'mode': 'rw'}},
49
+ mem_limit="8g",
50
+ cap_drop=["ALL"],
51
+ security_opt=["no-new-privileges"],
52
+ name="numnum-llm",
53
+ # GPU Support (Agar system mein GPU hai toh use karega)
54
+ device_requests=[docker.types.DeviceRequest(count=-1, capabilities=[['gpu']])]
55
+ )
56
+
57
+ # 4. Download model securely
58
+ print(f"Downloading model {model_name}...")
59
+ exit_code, output = container.exec_run(["ollama", "pull", model_name])
60
+ if exit_code != 0:
61
+ container.remove(force=True)
62
+ raise Exception(f"Model pull failed: {output.decode('utf-8')}")
63
+
64
+ # 5. Disconnect from Internet, Connect to Internal Network
65
+ client.networks.get(bridge_net_name).disconnect(container)
66
+ internal_net.connect(container)
67
+
68
+ # 6. Start Proxy on Host
69
+ proxy_port = get_free_port()
70
+
71
+ # Get Container IP for proxy to connect to
72
+ container.reload()
73
+ internal_ip = container.attrs['NetworkSettings']['Networks'][internal_net_name]['IPAddress']
74
+ upstream_url = f"http://{internal_ip}:11434"
75
+
76
+ return container.id, proxy_port, upstream_url
@@ -0,0 +1,16 @@
1
+ import subprocess
2
+
3
+ def get_hardware_info():
4
+ info = {"gpu": False, "ram_gb": 8, "os": "Unknown"}
5
+ try:
6
+ subprocess.run(["nvidia-smi"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
7
+ info["gpu"] = True
8
+ except (subprocess.CalledProcessError, FileNotFoundError):
9
+ info["gpu"] = False
10
+ return info
11
+
12
+ def get_recommended_model(hardware):
13
+ if hardware["gpu"]:
14
+ return "llama3.1:8b"
15
+ else:
16
+ return "llama3.2:1b"
@@ -0,0 +1,22 @@
1
+ import subprocess
2
+ from .security import generate_api_key
3
+
4
+ def deploy_native_llm(model_name: str):
5
+ api_key = generate_api_key()
6
+ print(f"📥 Pulling model: {model_name}...")
7
+ subprocess.run(["ollama", "pull", model_name], check=True)
8
+ print(f"🏃 Starting model: {model_name}...")
9
+ subprocess.Popen(["ollama", "run", model_name], shell=True)
10
+ return api_key
11
+
12
+ def list_native_llms():
13
+ result = subprocess.run(["ollama", "list"], capture_output=True, text=True)
14
+ models = []
15
+ for line in result.stdout.strip().split("\n")[1:]:
16
+ parts = line.split()
17
+ if len(parts) >= 3:
18
+ models.append({"name": parts[0], "size": parts[2]})
19
+ return models
20
+
21
+ def stop_native_llm(model_name: str):
22
+ subprocess.run(["ollama", "stop", model_name], capture_output=True)
@@ -0,0 +1,55 @@
1
+ import hmac
2
+ import httpx
3
+ from contextlib import asynccontextmanager
4
+ from fastapi import FastAPI, HTTPException, Request
5
+ from fastapi.responses import StreamingResponse
6
+ from starlette.background import BackgroundTask
7
+
8
+ ALLOWED = {"api/chat", "api/generate", "v1/chat/completions"}
9
+ MAX_BODY = 2_000_000 # 2MB limit
10
+
11
+ def build_app(api_key: str, upstream: str) -> FastAPI:
12
+ if not api_key:
13
+ raise RuntimeError("API key required to start proxy")
14
+
15
+ key_b = api_key.encode()
16
+
17
+ @asynccontextmanager
18
+ async def lifespan(app: FastAPI):
19
+ app.state.client = httpx.AsyncClient(
20
+ base_url=upstream, timeout=httpx.Timeout(10, read=600))
21
+ yield
22
+ await app.state.client.aclose()
23
+
24
+ # Disable docs to prevent unauthenticated probing
25
+ app = FastAPI(lifespan=lifespan, docs_url=None, redoc_url=None, openapi_url=None)
26
+
27
+ @app.post("/{path:path}")
28
+ async def proxy(path: str, request: Request):
29
+ # 1. Auth Check First
30
+ scheme, _, token = request.headers.get("authorization", "").partition(" ")
31
+ if scheme.lower() != "bearer" or not hmac.compare_digest(token.encode(), key_b):
32
+ raise HTTPException(401, detail="Unauthorized")
33
+
34
+ # 2. Path Allow-List
35
+ if path not in ALLOWED:
36
+ raise HTTPException(403, detail="Endpoint not allowed")
37
+
38
+ # 3. Body Size Limit
39
+ if int(request.headers.get("content-length", 0)) > MAX_BODY:
40
+ raise HTTPException(413, detail="Payload too large")
41
+
42
+ body = await request.body()
43
+
44
+ # 4. Forward Request (Strip sensitive headers)
45
+ req = app.state.client.build_request(
46
+ "POST", f"/{path}", content=body,
47
+ headers={"content-type": request.headers.get("content-type", "application/json")})
48
+
49
+ r = await app.state.client.send(req, stream=True)
50
+ return StreamingResponse(
51
+ r.aiter_raw(), status_code=r.status_code,
52
+ media_type=r.headers.get("content-type"),
53
+ background=BackgroundTask(r.aclose))
54
+
55
+ return app
@@ -0,0 +1,54 @@
1
+ import secrets
2
+ import re
3
+ import hmac
4
+ import unicodedata
5
+
6
+ class RedactionError(Exception):
7
+ pass
8
+
9
+ def generate_api_key():
10
+ return f"sk-numnum-{secrets.token_urlsafe(32)}"
11
+
12
+ def verify_api_key(provided_key: str, actual_key: str) -> bool:
13
+ if not provided_key or not actual_key:
14
+ return False
15
+ try:
16
+ return hmac.compare_digest(provided_key.encode(), actual_key.encode())
17
+ except Exception:
18
+ return False
19
+
20
+ def redact_secrets(text: str) -> str:
21
+ if not text:
22
+ return text
23
+
24
+ try:
25
+ # Normalize text to prevent zero-width character bypasses
26
+ text = unicodedata.normalize("NFKC", text)
27
+
28
+ patterns = [
29
+ # API Keys & Tokens (Using negative lookbehind to prevent 'risk-' false positives)
30
+ (r'(?<![a-zA-Z0-9])(sk-[a-zA-Z0-9_-]{20,})', '[REDACTED_API_KEY]'),
31
+ (r'(ghp_[a-zA-Z0-9]{36})', '[REDACTED_GITHUB_TOKEN]'),
32
+ (r'(github_pat_[a-zA-Z0-9_]{82})', '[REDACTED_GITHUB_PAT]'),
33
+ (r'(AKIA[0-9A-Z]{16})', '[REDACTED_AWS_KEY]'),
34
+ (r'(?i)(aws_secret_access_key|aws_secret_key)\s*=\s*[A-Za-z0-9/+=]{40}', r'\1=[REDACTED_AWS_SECRET]'),
35
+ (r'(eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)', '[REDACTED_JWT]'),
36
+
37
+ # Databases
38
+ (r'(?i)(postgres|postgresql|mysql|mongodb|redis):\/\/[^:\s]+:[^@\s]+@', r'\1://[REDACTED_USER]:[REDACTED_PASS]@'),
39
+
40
+ # Private Keys
41
+ (r'-----BEGIN (RSA|EC|OPENSSH|PRIVATE) PRIVATE KEY-----[\s\S]*?-----END \1 PRIVATE KEY-----', '[REDACTED_PEM_KEY]'),
42
+
43
+ # Generic Passwords (JSON, YAML, CLI, and Prose)
44
+ (r'(?i)(password|passwd|pwd|secret|token|api_key)(\s*[:=]\s*|--)(?!"\[REDACTED)([^\s"\'\n]+)', r'\1\2"[REDACTED_SECRET]"')
45
+ ]
46
+
47
+ redacted = text
48
+ for pattern, replacement in patterns:
49
+ redacted = re.sub(pattern, replacement, redacted)
50
+
51
+ return redacted
52
+ except Exception as e:
53
+ # Fail-Closed: Agar redaction fail ho, toh publish mat hone do
54
+ raise RedactionError(f"Security failure during redaction: {str(e)}")
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: numnumdock
3
+ Version: 1.0.0
4
+ Summary: The LLM-aware secure deployment engine. Secure by default AI.
5
+ Author-email: Your Name <your@email.com>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: typer
9
+ Requires-Dist: rich
10
+ Requires-Dist: docker
11
+ Requires-Dist: fastapi
12
+ Requires-Dist: uvicorn
13
+ Requires-Dist: httpx
14
+ Requires-Dist: pypiwin32; sys_platform == "win32"
15
+
16
+ # 🐰 NumNumDock
17
+
18
+ **The Secure-by-Default Local AI Deployment Engine.**
19
+
20
+ NumNumDock is a command-line tool that allows developers to run local Large Language Models (LLMs) securely. It wraps Ollama in a hardened Docker container with network isolation and provides a secure proxy for API key authentication.
21
+
22
+ ## 🚀 Features
23
+
24
+ - **Zero-Config Security:** Runs Ollama in an isolated Docker network (no internet access).
25
+ - **API Key Authentication:** Built-in reverse proxy that verifies API keys before reaching the LLM.
26
+ - **Advanced Secret Redaction:** Fail-closed redaction layer that masks API keys, passwords, and PEM keys from generated text.
27
+ - **Hardware Detection:** Automatically recommends the best model based on your GPU/CPU.
28
+ - **Random Port Allocation:** Prevents port squatting and local network exposure.
29
+
30
+ ## 📦 Installation
31
+
32
+ ```bash
33
+ pip install numnumdock
34
+ ```
35
+
36
+ 🛠️ Usage
37
+ Deploy a secure local model:
38
+
39
+ bash
40
+ numnumdock run llama3.2:1b
41
+ Check the status of your local deployments:
42
+
43
+ bash
44
+ numnumdock status
45
+ Run security diagnostics:
46
+
47
+ bash
48
+ numnumdock doctor
49
+ 🔒 Security Architecture
50
+ NumNumDock ensures that your local AI is never exposed to the public internet. It creates an internal Docker bridge network, downloads the model securely, disconnects the container from the internet, and serves it via an authenticated FastAPI proxy on 127.0.0.1.
51
+
52
+ 📄 License
53
+ MIT License
@@ -0,0 +1,14 @@
1
+ cli.py
2
+ pyproject.toml
3
+ readme.md
4
+ core/docker_manager.py
5
+ core/hardware.py
6
+ core/native_manager.py
7
+ core/proxy.py
8
+ core/security.py
9
+ numnumdock.egg-info/PKG-INFO
10
+ numnumdock.egg-info/SOURCES.txt
11
+ numnumdock.egg-info/dependency_links.txt
12
+ numnumdock.egg-info/entry_points.txt
13
+ numnumdock.egg-info/requires.txt
14
+ numnumdock.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ numnumdock = cli:app
@@ -0,0 +1,9 @@
1
+ typer
2
+ rich
3
+ docker
4
+ fastapi
5
+ uvicorn
6
+ httpx
7
+
8
+ [:sys_platform == "win32"]
9
+ pypiwin32
@@ -0,0 +1,2 @@
1
+ cli
2
+ core
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "numnumdock"
7
+ version = "1.0.0"
8
+ description = "The LLM-aware secure deployment engine. Secure by default AI."
9
+ readme = "readme.md"
10
+ requires-python = ">=3.9"
11
+ authors = [
12
+ { name = "Your Name", email = "your@email.com" }
13
+ ]
14
+ dependencies = [
15
+ "typer",
16
+ "rich",
17
+ "docker",
18
+ "fastapi",
19
+ "uvicorn",
20
+ "httpx",
21
+ "pypiwin32; sys_platform == 'win32'"
22
+ ]
23
+
24
+ [project.scripts]
25
+ numnumdock = "cli:app"
26
+
27
+ [tool.setuptools]
28
+ py-modules = ["cli"]
29
+ packages = ["core"]
@@ -0,0 +1,38 @@
1
+ # 🐰 NumNumDock
2
+
3
+ **The Secure-by-Default Local AI Deployment Engine.**
4
+
5
+ NumNumDock is a command-line tool that allows developers to run local Large Language Models (LLMs) securely. It wraps Ollama in a hardened Docker container with network isolation and provides a secure proxy for API key authentication.
6
+
7
+ ## 🚀 Features
8
+
9
+ - **Zero-Config Security:** Runs Ollama in an isolated Docker network (no internet access).
10
+ - **API Key Authentication:** Built-in reverse proxy that verifies API keys before reaching the LLM.
11
+ - **Advanced Secret Redaction:** Fail-closed redaction layer that masks API keys, passwords, and PEM keys from generated text.
12
+ - **Hardware Detection:** Automatically recommends the best model based on your GPU/CPU.
13
+ - **Random Port Allocation:** Prevents port squatting and local network exposure.
14
+
15
+ ## 📦 Installation
16
+
17
+ ```bash
18
+ pip install numnumdock
19
+ ```
20
+
21
+ 🛠️ Usage
22
+ Deploy a secure local model:
23
+
24
+ bash
25
+ numnumdock run llama3.2:1b
26
+ Check the status of your local deployments:
27
+
28
+ bash
29
+ numnumdock status
30
+ Run security diagnostics:
31
+
32
+ bash
33
+ numnumdock doctor
34
+ 🔒 Security Architecture
35
+ NumNumDock ensures that your local AI is never exposed to the public internet. It creates an internal Docker bridge network, downloads the model securely, disconnects the container from the internet, and serves it via an authenticated FastAPI proxy on 127.0.0.1.
36
+
37
+ 📄 License
38
+ MIT License
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+