diff --git a/check_db.py b/ServerManagerWebApp/check_db.py similarity index 100% rename from check_db.py rename to ServerManagerWebApp/check_db.py diff --git a/comics_organizesteps.py b/ServerManagerWebApp/comics_organizesteps.py similarity index 100% rename from comics_organizesteps.py rename to ServerManagerWebApp/comics_organizesteps.py diff --git a/fixed_comics_manager.py b/ServerManagerWebApp/fixed_comics_manager.py similarity index 100% rename from fixed_comics_manager.py rename to ServerManagerWebApp/fixed_comics_manager.py diff --git a/gemini_projects/mcp/.env b/gemini_projects/mcp/.env new file mode 100644 index 0000000..a16f944 --- /dev/null +++ b/gemini_projects/mcp/.env @@ -0,0 +1 @@ +GOOGLE_API_KEY= diff --git a/gemini_projects/mcp/README.md b/gemini_projects/mcp/README.md new file mode 100644 index 0000000..c3d332b --- /dev/null +++ b/gemini_projects/mcp/README.md @@ -0,0 +1,47 @@ +# Gemini MCP Integration + +This directory contains a boilerplate setup for integrating Google Gemini with the Model Context Protocol (MCP). + +## Structure + +- **`simple_server.py`**: A simple MCP server using `FastMCP` that exposes two tools (`add_numbers`, `get_current_time`). +- **`gemini_client.py`**: A Python script that acts as an MCP client. It connects to `simple_server.py`, lists the available tools, and provides guidance on integrating them with the `google-genai` SDK. +- **`setup.sh`**: A provision script that sets up the Python virtual environment and installs dependencies. +- **`requirements.txt`**: List of Python dependencies. + +## Prerequisites + +- Python 3 +- Node.js & npm (for MCP Inspector) + +## Setup + +1. Run the setup script: + ```bash + ./setup.sh + ``` +2. Activate the virtual environment: + ```bash + source venv/bin/activate + ``` +3. Set your Google API Key in `.env`: + ```bash + # Edit .env file + GOOGLE_API_KEY=your_api_key_here + ``` + +## Running the Example + +Run the client script to see the connection to the server and the list of tools: + +```bash +python gemini_client.py +``` + +## Using MCP Inspector + +You can use the official MCP Inspector to interact with the `simple_server.py` visually. + +```bash +npx @modelcontextprotocol/inspector python simple_server.py +``` diff --git a/gemini_projects/mcp/gemini_client.py b/gemini_projects/mcp/gemini_client.py new file mode 100644 index 0000000..1ac42f6 --- /dev/null +++ b/gemini_projects/mcp/gemini_client.py @@ -0,0 +1,62 @@ +import asyncio +import os +import sys +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from google import genai +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +async def main(): + print("Starting MCP Client...") + + # Check for API Key + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + print("Warning: GOOGLE_API_KEY is not set in .env. Gemini calls will fail.") + else: + print("GOOGLE_API_KEY found.") + + # Define the server parameters + # We run the simple_server.py we created earlier + server_script = os.path.join(os.path.dirname(__file__), "simple_server.py") + + server_params = StdioServerParameters( + command=sys.executable, # Use the current python interpreter + args=[server_script], + env=None + ) + + print(f"Connecting to MCP server at: {server_script}") + + try: + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize the session + await session.initialize() + + # List available tools + result = await session.list_tools() + tools = result.tools + + print(f"\nSuccessfully connected! Found {len(tools)} tools:") + for tool in tools: + print(f" - {tool.name}: {tool.description}") + print(f" Schema: {tool.inputSchema}") + + print("\n--- Integration with Gemini (google-genai) ---") + print("To integrate these tools with Gemini using the new SDK, you would:") + print("1. Convert the MCP tool schemas to Gemini tool definitions.") + print("2. Initialize the Gemini client:") + print(" from google import genai") + print(" client = genai.Client(api_key=api_key)") + print("3. Pass the tools to the generate_content call.") + print("4. Handle the tool calls returned by the model.") + + except Exception as e: + print(f"Error connecting to MCP server: {e}") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/gemini_projects/mcp/requirements.txt b/gemini_projects/mcp/requirements.txt new file mode 100644 index 0000000..a9cd9b3 --- /dev/null +++ b/gemini_projects/mcp/requirements.txt @@ -0,0 +1,3 @@ +mcp +google-genai +python-dotenv diff --git a/gemini_projects/mcp/setup.sh b/gemini_projects/mcp/setup.sh new file mode 100755 index 0000000..9d3c384 --- /dev/null +++ b/gemini_projects/mcp/setup.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Exit on error +set -e + +echo "Provisioning MCP environment..." + +# Check for Python 3 +if ! command -v python3 &> /dev/null; then + echo "Python 3 could not be found. Please install Python 3." + exit 1 +fi + +# Create Virtual Environment +if [ ! -d "venv" ]; then + echo "Creating virtual environment..." + python3 -m venv venv +else + echo "Virtual environment already exists." +fi + +# Activate venv +source venv/bin/activate + +# Upgrade pip +echo "Upgrading pip..." +pip install --upgrade pip + +# Install dependencies +if [ -f "requirements.txt" ]; then + echo "Installing dependencies from requirements.txt..." + pip install -r requirements.txt +else + echo "requirements.txt not found!" + exit 1 +fi + +# Create .env from template if it doesn't exist +if [ ! -f ".env" ]; then + echo "Creating .env file..." + touch .env + echo "GOOGLE_API_KEY=" >> .env + echo "Please add your GOOGLE_API_KEY to mcp/.env" +fi + +echo "Setup complete! To start working:" +echo "cd mcp" +echo "source venv/bin/activate" diff --git a/gemini_projects/mcp/simple_server.py b/gemini_projects/mcp/simple_server.py new file mode 100644 index 0000000..08f5bfa --- /dev/null +++ b/gemini_projects/mcp/simple_server.py @@ -0,0 +1,20 @@ +from mcp.server.fastmcp import FastMCP +import asyncio + +# Initialize FastMCP server +mcp = FastMCP("Simple Demo Server") + +@mcp.tool() +def add_numbers(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b + +@mcp.tool() +def get_current_time() -> str: + """Get the current local time.""" + from datetime import datetime + return datetime.now().isoformat() + +if __name__ == "__main__": + # Run the server over stdio + mcp.run() diff --git a/gemini_projects/truenas_storagetracker/.env.template b/gemini_projects/truenas_storagetracker/.env.template new file mode 100644 index 0000000..dd84a09 --- /dev/null +++ b/gemini_projects/truenas_storagetracker/.env.template @@ -0,0 +1,2 @@ +TRUENAS_API_URL=http://your-truenas-ip/api/v2.0 +TRUENAS_API_KEY=your-api-key-here diff --git a/gemini_projects/truenas_storagetracker/README.md b/gemini_projects/truenas_storagetracker/README.md new file mode 100644 index 0000000..7017cb0 --- /dev/null +++ b/gemini_projects/truenas_storagetracker/README.md @@ -0,0 +1,83 @@ +# TrueNAS Scale Storage Tracker + +A CLI tool to list storage usage for each data pool in TrueNAS SCALE using the TrueNAS API. + +## Features + +- Lists all data pools with total size, used size, free size, and percentage used. +- Supports output in Text (default), JSON, and CSV formats. +- Configurable via `.env` file. +- Robust error handling. + +## Prerequisites + +- Python 3.x +- `requests` +- `python-dotenv` + +## Installation + +1. Clone the repository or navigate to the project directory. +2. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +## Configuration + +1. Copy the `.env.template` to `.env`: + ```bash + cp .env.template .env + ``` +2. Edit `.env` and provide your TrueNAS Scale credentials: + ```ini + TRUENAS_API_URL=http://your-truenas-ip/api/v2.0 + TRUENAS_API_KEY=your-api-key-here + ``` + + * **TRUENAS_API_URL**: The URL to your TrueNAS API (e.g., `http://192.168.1.100/api/v2.0`). + * **TRUENAS_API_KEY**: An API Key generated from the TrueNAS web interface (Settings -> API Keys). + +## Usage + +Run the tool from the command line: + +```bash +python3 src/tracker.py +``` + +### Options + +* `--format`: Specify the output format (`text`, `json`, `csv`). Default is `text`. + +### Examples + +**Default Text Output:** +```bash +python3 src/tracker.py +``` + +**JSON Output:** +```bash +python3 src/tracker.py --format json +``` + +**CSV Output:** +```bash +python3 src/tracker.py --format csv +``` + +## Testing + +Run the unit tests: + +```bash +python3 -m unittest discover tests +``` + +## Project Structure + +* `src/tracker.py`: Main application logic. +* `tests/test_tracker.py`: Unit tests. +* `.env.template`: Template for environment variables. +* `requirements.txt`: Python dependencies. diff --git a/gemini_projects/truenas_storagetracker/requirements.txt b/gemini_projects/truenas_storagetracker/requirements.txt new file mode 100644 index 0000000..3d7b914 --- /dev/null +++ b/gemini_projects/truenas_storagetracker/requirements.txt @@ -0,0 +1,3 @@ +requests +python-dotenv +argparse diff --git a/gemini_projects/truenas_storagetracker/src/__init__.py b/gemini_projects/truenas_storagetracker/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini_projects/truenas_storagetracker/src/__pycache__/tracker.cpython-313.pyc b/gemini_projects/truenas_storagetracker/src/__pycache__/tracker.cpython-313.pyc new file mode 100644 index 0000000..b0a0634 Binary files /dev/null and b/gemini_projects/truenas_storagetracker/src/__pycache__/tracker.cpython-313.pyc differ diff --git a/gemini_projects/truenas_storagetracker/src/tracker.py b/gemini_projects/truenas_storagetracker/src/tracker.py new file mode 100644 index 0000000..fd77b53 --- /dev/null +++ b/gemini_projects/truenas_storagetracker/src/tracker.py @@ -0,0 +1,109 @@ +import os +import requests +import json +import csv +import sys +import argparse +try: + from dotenv import load_dotenv + # Load environment variables + load_dotenv() +except ImportError: + print("Warning: python-dotenv not installed. Environment variables from .env file will not be loaded.", file=sys.stderr) + +def get_headers(): + TRUENAS_API_KEY = os.getenv("TRUENAS_API_KEY") + if not TRUENAS_API_KEY: + raise ValueError("TRUENAS_API_KEY not found in environment variables.") + return { + "Authorization": f"Bearer {TRUENAS_API_KEY}", + "Content-Type": "application/json" + } + +def fetch_pool_data(): + TRUENAS_API_URL = os.getenv("TRUENAS_API_URL") + if not TRUENAS_API_URL: + raise ValueError("TRUENAS_API_URL not found in environment variables.") + + url = f"{TRUENAS_API_URL}/pool" + try: + response = requests.get(url, headers=get_headers(), timeout=10) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"Error connecting to TrueNAS API: {e}", file=sys.stderr) + sys.exit(1) + +def process_pool_data(raw_data): + processed_data = [] + for pool in raw_data: + # Extract relevant metrics. + # Note: TrueNAS API structure varies slightly, but usually: + # 'name' is the pool name. + # 'size' is total size in bytes. + # 'allocated' is used size in bytes. + # 'free' is free size in bytes. + # Values might be nested differently depending on version, + # but typically top-level or under 'scan' or similar. + # We will assume standard keys based on typical v2.0 response. + + # Adjusting based on common TrueNAS Scale API response for /pool + # It often returns a list where each item has 'name' and 'scan' or standard ZFS properties. + # Let's try to find the size properties. often found in 'used', 'available', 'size' (top level for ZFS properties usually don't exist directly, usually inside 'properties' or computed). + # Actually, /pool response usually has 'name' and 'size' (total), 'allocated' (used), 'free' (available). + + # Fallback/Safe extraction + name = pool.get('name', 'Unknown') + + # Some versions use 'size', 'allocated', 'free' directly + total_size = pool.get('size', 0) + used_size = pool.get('allocated', 0) + free_size = pool.get('free', 0) + + # Calculate percentage if not present (sometimes it is not) + if total_size > 0: + percent_used = (used_size / total_size) * 100 + else: + percent_used = 0.0 + + processed_data.append({ + "pool_name": name, + "total_size_bytes": total_size, + "used_size_bytes": used_size, + "free_size_bytes": free_size, + "percent_used": round(percent_used, 2) + }) + return processed_data + +def format_output(data, output_format): + if output_format == 'json': + print(json.dumps(data, indent=4)) + elif output_format == 'csv': + writer = csv.DictWriter(sys.stdout, fieldnames=["pool_name", "total_size_bytes", "used_size_bytes", "free_size_bytes", "percent_used"]) + writer.writeheader() + writer.writerows(data) + else: + # Default text/human-readable + print(f"{'Pool Name':<20} {'Total Size':<15} {'Used Size':<15} {'Free Size':<15} {'% Used':<10}") + print("-" * 75) + for item in data: + print(f"{item['pool_name']:<20} {item['total_size_bytes']:<15} {item['used_size_bytes']:<15} {item['free_size_bytes']:<15} {item['percent_used']:<10}%") + +def main(): + parser = argparse.ArgumentParser(description="TrueNAS Scale Storage Tracker") + parser.add_argument("--format", choices=['json', 'csv', 'text'], default='text', help="Output format (json, csv, text)") + args = parser.parse_args() + + try: + raw_data = fetch_pool_data() + processed_data = process_pool_data(raw_data) + format_output(processed_data, args.format) + except ValueError as e: + print(f"Configuration Error: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/gemini_projects/truenas_storagetracker/tests/__init__.py b/gemini_projects/truenas_storagetracker/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini_projects/truenas_storagetracker/tests/__pycache__/test_tracker.cpython-313.pyc b/gemini_projects/truenas_storagetracker/tests/__pycache__/test_tracker.cpython-313.pyc new file mode 100644 index 0000000..3dcd4da Binary files /dev/null and b/gemini_projects/truenas_storagetracker/tests/__pycache__/test_tracker.cpython-313.pyc differ diff --git a/gemini_projects/truenas_storagetracker/tests/test_tracker.py b/gemini_projects/truenas_storagetracker/tests/test_tracker.py new file mode 100644 index 0000000..d32711d --- /dev/null +++ b/gemini_projects/truenas_storagetracker/tests/test_tracker.py @@ -0,0 +1,71 @@ +import unittest +from unittest.mock import patch, MagicMock +import sys +import os + +# Add src to python path to import tracker +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src')) + +import tracker + +class TestTrueNASStorageTracker(unittest.TestCase): + + def setUp(self): + self.mock_pool_data = [ + { + "name": "tank", + "size": 10995116277760, # 10 TB + "allocated": 5497558138880, # 5 TB + "free": 5497558138880 # 5 TB + }, + { + "name": "boot-pool", + "size": 256000000000, # 256 GB + "allocated": 64000000000, # 64 GB + "free": 192000000000 # 192 GB + } + ] + + @patch('tracker.requests.get') + @patch('tracker.os.getenv') + def test_fetch_pool_data_success(self, mock_getenv, mock_get): + # Configure mocks + mock_getenv.side_effect = lambda key: { + 'TRUENAS_API_URL': 'http://mock-url', + 'TRUENAS_API_KEY': 'mock-key' + }.get(key) + + mock_response = MagicMock() + mock_response.json.return_value = self.mock_pool_data + mock_response.raise_for_status.return_value = None + mock_get.return_value = mock_response + + # Call function + data = tracker.fetch_pool_data() + + # Assertions + self.assertEqual(data, self.mock_pool_data) + mock_get.assert_called_once() + + def test_process_pool_data(self): + processed = tracker.process_pool_data(self.mock_pool_data) + + self.assertEqual(len(processed), 2) + + # Check first pool + self.assertEqual(processed[0]['pool_name'], "tank") + self.assertEqual(processed[0]['total_size_bytes'], 10995116277760) + self.assertEqual(processed[0]['percent_used'], 50.0) + + # Check second pool + self.assertEqual(processed[1]['pool_name'], "boot-pool") + self.assertEqual(processed[1]['percent_used'], 25.0) + + @patch('tracker.os.getenv') + def test_missing_env_vars(self, mock_getenv): + mock_getenv.return_value = None + with self.assertRaises(ValueError): + tracker.fetch_pool_data() + +if __name__ == '__main__': + unittest.main()