Introduction: What you’ll build or accomplish
You will build a practical AI assistant for a Linux system that can:
- Monitor CPU, RAM, disk, network, and key services
- Inform you via terminal output and desktop notifications
- Update the system safely (opt-in, least privilege)
- Troubleshoot common issues by summarizing logs and proposing fixes
- Use an AI API (OpenAI-compatible) to explain problems and recommend actions
- Harden security so AI output cannot directly run arbitrary commands
You will implement a “plan-then-execute” workflow:
- Collect system facts (metrics + logs)
- Ask the AI to produce a structured
JSONplan - Validate the plan against a strict allowlist
- Execute only safe, allowed actions
- Log everything and notify the user
This pattern is necessary because letting an LLM generate shell commands directly is risky. Your assistant will treat the LLM as an advisor, not an unrestricted root shell.
Prerequisites: Required tools, knowledge, or setup
System requirements
- A Linux machine (Ubuntu/Debian examples used; notes included for others)
- Python 3.9+ (3.11/3.12 recommended)
- Basic terminal familiarity (editing files, running commands)
Packages you’ll install
psutilfor system metricspython-dotenvfor secure environment loadingrequestsfor API calls (or OpenAI SDK if you prefer)systemd-pythonoptional (we’ll use systemd via unit files)
Security assumptions
- You will not hardcode API keys
- You will run the assistant as a non-root user by default
- You will elevate privileges only for narrowly-scoped update actions
Step 1: Create a project folder and Python virtual environment
Do this to isolate dependencies and prevent contaminating system Python packages.
Commands
mkdir -p ~/linux-ai-assistant
cd ~/linux-ai-assistant
python3 -m venv .venv
source .venv/bin/activate
python -V
Expected results
python -Vprints something likePython 3.11.x- Your shell prompt often shows
(.venv)
Common errors
- “python3: command not found”: install Python (e.g.,
sudo apt install -y python3 python3-venv). - Permission issues: ensure you are in your home directory and not a protected path.
Step 2: Install dependencies
Do this so you can read system metrics, send notifications, and call the AI API.
Commands
pip install --upgrade pip
pip install psutil python-dotenv requests
Optional (desktop notifications)
If you are on a desktop environment, install notify-send (usually already available):
sudo apt update
sudo apt install -y libnotify-bin
Expected results
pipcompletes without errorspython -c "import psutil"exits silently (success)
Step 3: Create a secure configuration (.env) and ignore it
Do this to keep secrets out of your source code and out of Git history.
Create .env
cat > .env <<'EOF'
# AI API configuration
AI_API_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o-mini
OPENAI_API_KEY=REPLACE_ME
# Assistant behavior
ASSISTANT_LOG_LEVEL=INFO
EOF
Add .env to .gitignore
cat > .gitignore <<'EOF'
.venv/
__pycache__/
*.pyc
.env
logs/
EOF
Expected results
- Your API key is stored only in
.env - If you later use Git,
.envwon’t be committed
Warning (security)
Never paste API keys into screenshots, issue trackers, or public repos. Rotate keys if you accidentally expose them.
Step 4: Create the project structure
Do this to keep the assistant maintainable and secure. Separation of concerns makes it harder to accidentally execute unsafe actions.
Commands
mkdir -p assistant logs
touch assistant/__init__.py \
assistant/config.py \
assistant/ai_client.py \
assistant/monitor.py \
assistant/notify.py \
assistant/security.py \
assistant/actions.py \
assistant/main.py
Expected results
- You have a clean module layout under
assistant/ - Logs will be stored in
logs/
Step 5: Implement configuration loading and logging
Do this so the assistant reads settings from environment variables and produces auditable logs (critical for troubleshooting and security review).
assistant/config.py
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class Settings:
api_base_url: str
api_key: str
model: str
log_level: str
def get_settings() -> Settings:
api_base_url = os.getenv("AI_API_BASE_URL", "https://api.openai.com/v1")
api_key = os.getenv("OPENAI_API_KEY", "")
model = os.getenv("AI_MODEL", "gpt-4o-mini")
log_level = os.getenv("ASSISTANT_LOG_LEVEL", "INFO")
if not api_key or api_key == "REPLACE_ME":
raise RuntimeError("OPENAI_API_KEY is not set. Put it in .env")
return Settings(
api_base_url=api_base_url,
api_key=api_key,
model=model,
log_level=log_level,
)
Expected results
- If
OPENAI_API_KEYis missing, the app fails fast with a clear error
Step 6: Implement the AI API client (safe, bounded output)
Do this so the assistant can ask the model for explanations and structured plans. You will force the response into a predictable JSON schema to reduce prompt injection and unexpected output.
assistant/ai_client.py
import json
import requests
from typing import Any, Dict
from .config import Settings
class AIClient:
def __init__(self, settings: Settings):
self.settings = settings
def propose_plan(self, context: Dict[str, Any], user_request: str) -> Dict[str, Any]:
"""Ask the LLM to return a strict JSON plan.
The plan is advisory. Execution will be validated against an allowlist.
"""
system_prompt = (
"You are a Linux operations assistant. "
"Return ONLY valid JSON (no markdown, no prose). "
"Schema: {\n"
" \"summary\": string,\n"
" \"risk\": \"low\"|\"medium\"|\"high\",\n"
" \"actions\": [\n"
" {\"type\": \"notify\"|\"read_only\"|\"update\"|\"service\",\n"
" \"name\": string,\n"
" \"args\": object}\n"
" ]\n"
"} "
"Constraints: do not request arbitrary shell, do not include secrets, "
"prefer read_only actions, and set risk=high if any update/restart is suggested."
)
payload = {
"model": self.settings.model,
"messages": [
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": json.dumps(
{"user_request": user_request, "context": context},
ensure_ascii=False,
),
},
],
"temperature": 0.2,
}
url = f"{self.settings.api_base_url}/chat/completions"
headers = {
"Authorization": f"Bearer {self.settings.api_key}",
"Content-Type": "application/json",
}
resp = requests.post(url, headers=headers, json=payload, timeout=60)
resp.raise_for_status()
data = resp.json()
content = data["choices"][0]["message"]["content"].strip()
return json.loads(content)
Expected results
- The model returns
JSONyou can parse - If the model returns non-JSON,
json.loadsfails (you will handle this later)
Common errors
- 401 Unauthorized: API key missing/invalid.
- 404: wrong
AI_API_BASE_URL. - JSON decode error: model returned text. You will add retry/repair in troubleshooting.
Step 7: Implement system monitoring (CPU/RAM/disk/network + top processes)
Do this to provide the AI with real context. Without metrics, the AI guesses. With metrics, it can explain spikes and recommend targeted actions.
assistant/monitor.py
import time
import psutil
from typing import Any, Dict, List
def get_metrics() -> Dict[str, Any]:
cpu = psutil.cpu_percent(interval=0.5)
vm = psutil.virtual_memory()
du = psutil.disk_usage("/")
net1 = psutil.net_io_counters()
time.sleep(0.3)
net2 = psutil.net_io_counters()
return {
"cpu_percent": cpu,
"ram_percent": vm.percent,
"ram_used_gb": round(vm.used / (1024**3), 2),
"ram_total_gb": round(vm.total / (1024**3), 2),
"disk_percent_root": du.percent,
"disk_used_gb": round(du.used / (1024**3), 2),
"disk_total_gb": round(du.total / (1024**3), 2),
"net_bytes_sent_per_s": int((net2.bytes_sent - net1.bytes_sent) / 0.3),
"net_bytes_recv_per_s": int((net2.bytes_recv - net1.bytes_recv) / 0.3),
"boot_time": psutil.boot_time(),
}
def get_top_processes(limit: int = 5) -> List[Dict[str, Any]]:
procs = []
for p in psutil.process_iter(attrs=["pid", "name", "username", "cpu_percent", "memory_percent"]):
try:
procs.append(p.info)
except psutil.Error:
continue
procs.sort(key=lambda x: (x.get("cpu_percent") or 0, x.get("memory_percent") or 0), reverse=True)
return procs[:limit]
Expected results
- Metrics show current system usage
- Top processes list identifies likely culprits for CPU/RAM spikes
Step 8: Implement notifications (terminal + desktop)
Do this so the assistant can inform you without you constantly watching logs. Notifications are also useful when running as a background service.
assistant/notify.py
import subprocess
from typing import Optional
def notify(title: str, message: str, urgency: str = "normal") -> None:
"""Send a desktop notification if available; otherwise do nothing."""
try:
subprocess.run(
["notify-send", "-u", urgency, title, message],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except FileNotFoundError:
# notify-send not installed or no desktop environment
pass
def print_banner(text: str) -> None:
print("\n" + "=" * 80)
print(text)
print("=" * 80)
Expected results
- On desktops, you see a system popup
- On servers, it quietly degrades without failing
Screenshot description
Expect a notification popup in the top-right (GNOME) showing: title “AI Assistant” and the message body (e.g., “Disk usage is 92% on /”).
Step 9: Add a strict security layer (allowlist + input validation)
Do this to prevent command injection and “LLM said so” dangerous execution. The assistant must only run known-safe actions you explicitly allow.
assistant/security.py
from dataclasses import dataclass
from typing import Any, Dict, List
ALLOWED_ACTION_TYPES = {"notify", "read_only", "update", "service"}
# Whitelist action names
ALLOWED_READ_ONLY = {
"disk_report",
"memory_report",
"cpu_report",
"journal_errors",
"service_status",
}
ALLOWED_UPDATE = {
"apt_update_upgrade",
}
ALLOWED_SERVICE = {
"restart_service",
}
# Restrict which services can be manipulated
ALLOWED_SERVICE_NAMES = {
"ssh",
"cron",
"network-manager",
"docker",
}
@dataclass
class ValidationResult:
ok: bool
errors: List[str]
def validate_plan(plan: Dict[str, Any]) -> ValidationResult:
errors: List[str] = []
if not isinstance(plan, dict):
return ValidationResult(False, ["Plan is not an object"])
if "actions" not in plan or not isinstance(plan["actions"], list):
return ValidationResult(False, ["Plan.actions must be a list"])
for i, action in enumerate(plan["actions"]):
if not isinstance(action, dict):
errors.append(f"Action {i} is not an object")
continue
a_type = action.get("type")
name = action.get("name")
args = action.get("args", {})
if a_type not in ALLOWED_ACTION_TYPES:
errors.append(f"Action {i}: type not allowed: {a_type}")
continue
if not isinstance(name, str) or not name:
errors.append(f"Action {i}: name must be a non-empty string")
continue
if not isinstance(args, dict):
errors.append(f"Action {i}: args must be an object")
continue
if a_type == "read_only" and name not in ALLOWED_READ_ONLY:
errors.append(f"Action {i}: read_only name not allowed: {name}")
if a_type == "update" and name not in ALLOWED_UPDATE:
errors.append(f"Action {i}: update name not allowed: {name}")
if a_type == "service":
if name not in ALLOWED_SERVICE:
errors.append(f"Action {i}: service name not allowed: {name}")
svc = args.get("service")
if svc not in ALLOWED_SERVICE_NAMES:
errors.append(
f"Action {i}: service '{svc}' not in allowed list: {sorted(ALLOWED_SERVICE_NAMES)}"
)
return ValidationResult(ok=(len(errors) == 0), errors=errors)
Expected results
- Any plan outside your safe set is rejected
- You can expand the allowlist later, deliberately
Warning (security)
Do not add a “shell” action type. The moment you allow arbitrary shell, you lose meaningful control over execution.
Step 10: Implement safe actions (read-only, update, service control)
Do this so execution is deterministic, testable, and auditable. You will run system commands only through controlled code paths.
assistant/actions.py
import subprocess
from typing import Any, Dict
def _run(cmd: list[str], timeout: int = 60) -> Dict[str, Any]:
p = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
check=False,
)
return {
"cmd": cmd,
"returncode": p.returncode,
"stdout": p.stdout[-4000:],
"stderr": p.stderr[-4000:],
}
def disk_report() -> Dict[str, Any]:
return _run(["df", "-h"])
def memory_report() -> Dict[str, Any]:
return _run(["free", "-h"])
def cpu_report() -> Dict[str, Any]:
return _run(["uptime"]) # simple load average
def journal_errors(lines: int = 200) -> Dict[str, Any]:
return _run(["journalctl", "-p", "3", "-n", str(lines), "--no-pager"], timeout=90)
def service_status(service: str) -> Dict[str, Any]:
return _run(["systemctl", "status", service, "--no-pager"], timeout=30)
def apt_update_upgrade() -> Dict[str, Any]:
"""Perform apt update/upgrade.
WARNING: This changes system state. Require explicit user consent in main flow.
"""
# Use non-interactive behavior
env = {"DEBIAN_FRONTEND": "noninteractive"}
r1 = subprocess.run(["sudo", "-n", "apt-get", "update"], capture_output=True, text=True)
r2 = subprocess.run(
["sudo", "-n", "apt-get", "upgrade", "-y"],
capture_output=True,
text=True,
env=env,
)
return {
"cmd": ["sudo", "-n", "apt-get", "update", "&&", "sudo", "-n", "apt-get", "upgrade", "-y"],
"returncode": (r1.returncode or r2.returncode),
"stdout": (r1.stdout + "\n" + r2.stdout)[-4000:],
"stderr": (r1.stderr + "\n" + r2.stderr)[-4000:],
}
def restart_service(service: str) -> Dict[str, Any]:
"""Restart an allowed service.
WARNING: This changes system state and can disrupt connectivity.
"""
return _run(["sudo", "-n", "systemctl", "restart", service], timeout=60)
Expected results
- Read-only actions return command output and errors for the AI to summarize
- Update/service actions require
sudoand can fail safely if not permitted
Warnings (destructive actions)
- Updates can break packages or require reboots.
- Service restarts can drop SSH or networking if you restart the wrong thing.
Step 11: Build the main CLI assistant (plan → validate → execute)
Do this to connect monitoring, AI planning, validation, and action execution into a single tool you can run manually or via systemd.
assistant/main.py
import argparse
import json
import logging
import os
from datetime import datetime
from typing import Any, Dict
from .actions import (
apt_update_upgrade,
cpu_report,
disk_report,
journal_errors,
memory_report,
restart_service,
service_status,
)
from .ai_client import AIClient
from .config import get_settings
from .monitor import get_metrics, get_top_processes
from .notify import notify, print_banner
from .security import validate_plan
ACTION_DISPATCH = {
"disk_report": lambda args: disk_report(),
"memory_report": lambda args: memory_report(),
"cpu_report": lambda args: cpu_report(),
"journal_errors": lambda args: journal_errors(lines=int(args.get("lines", 120))),
"service_status": lambda args: service_status(service=str(args["service"])),
"apt_update_upgrade": lambda args: apt_update_upgrade(),
"restart_service": lambda args: restart_service(service=str(args["service"])),
}
def build_context() -> Dict[str, Any]:
return {
"metrics": get_metrics(),
"top_processes": get_top_processes(5),
"time": datetime.utcnow().isoformat() + "Z",
"hostname": os.uname().nodename,
}
def execute_plan(plan: Dict[str, Any], assume_yes: bool = False) -> Dict[str, Any]:
results = {"summary": plan.get("summary"), "risk": plan.get("risk"), "actions": []}
for action in plan["actions"]:
a_type = action["type"]
name = action["name"]
args = action.get("args", {})
# Require explicit consent for state-changing actions
if a_type in {"update", "service"} and not assume_yes:
print_banner("CONFIRMATION REQUIRED")
print(json.dumps(action, indent=2))
answer = input("Proceed? (yes/no): ").strip().lower()
if answer != "yes":
results["actions"].append({"action": action, "skipped": True, "reason": "user declined"})
continue
if a_type == "notify":
title = str(args.get("title", "AI Assistant"))
msg = str(args.get("message", plan.get("summary", "Notification")))
notify(title, msg, urgency=str(args.get("urgency", "normal")))
results["actions"].append({"action": action, "ok": True, "result": "notified"})
continue
# Execute mapped actions
fn = ACTION_DISPATCH.get(name)
if not fn:
results["actions"].append({"action": action, "ok": False, "error": "no dispatcher"})
continue
out = fn(args)
results["actions"].append({"action": action, "ok": (out.get("returncode", 1) == 0), "output": out})
return results
def main() -> None:
parser = argparse.ArgumentParser(description="Secure AI Linux assistant")
parser.add_argument("request", nargs="?", default="status", help="What you want the assistant to do")
parser.add_argument("--yes", action="store_true", help="Skip confirmations (DANGEROUS)")
args = parser.parse_args()
settings = get_settings()
logging.basicConfig(
level=getattr(logging, settings.log_level.upper(), logging.INFO),
format="%(asctime)s %(levelname)s %(message)s",
)
context = build_context()
print_banner("LOCAL SYSTEM CONTEXT")
print(json.dumps(context, indent=2))
client = AIClient(settings)
try:
plan = client.propose_plan(context=context, user_request=args.request)
except Exception as e:
print_banner("AI ERROR")
print(f"Failed to get plan: {e}")
raise
print_banner("PROPOSED PLAN (from AI)")
print(json.dumps(plan, indent=2))
vr = validate_plan(plan)
if not vr.ok:
print_banner("PLAN REJECTED (SECURITY)")
for err in vr.errors:
print(f"- {err}")
print("\nNo actions were executed.")
return
result = execute_plan(plan, assume_yes=args.yes)
print_banner("EXECUTION RESULTS")
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
Expected results
- You see a LOCAL SYSTEM CONTEXT JSON block
- You see a PROPOSED PLAN JSON block
- If the plan is allowed, you see EXECUTION RESULTS with command output
- If the plan is unsafe, it is rejected and nothing runs
Warning (dangerous flag)
--yes skips confirmations. Use it only in controlled environments after you trust your allowlist and sudo rules.
Step 12: Run the assistant (manual mode)
Do this to verify end-to-end functionality before daemonizing it.
Commands
source .venv/bin/activate
python -m assistant.main "check disk and show errors"
Expected results
A typical safe plan might include read-only actions:
{
"summary": "Disk usage is high; gathering disk and journal error information.",
"risk": "low",
"actions": [
{"type": "read_only", "name": "disk_report", "args": {}},
{"type": "read_only", "name": "journal_errors", "args": {"lines": 120}},
{"type": "notify", "name": "notify_user", "args": {"title": "AI Assistant", "message": "Collected disk and error logs."}}
]
}
Then you should see df -h output and journal excerpts in the execution results.
Step 13: Add sudo rules for narrowly-scoped privilege elevation
Do this so update/restart actions can run without giving the assistant full root access. The goal is least privilege.
Why this is necessary
- Running the entire assistant as root is dangerous (AI-driven logic + root is a bad combo).
- Allow only specific commands via
sudoers.
Create a sudoers file (Ubuntu/Debian)
Warning: Editing sudoers incorrectly can lock you out of admin access. Use visudo.
sudo visudo -f /etc/sudoers.d/ai-assistant
Paste this, replacing YOURUSER with your Linux username:
# Allow only specific commands without password
YOURUSER ALL=(root) NOPASSWD: /usr/bin/apt-get update
YOURUSER ALL=(root) NOPASSWD: /usr/bin/apt-get upgrade -y
YOURUSER ALL=(root) NOPASSWD: /bin/systemctl restart ssh
YOURUSER ALL=(root) NOPASSWD: /bin/systemctl restart cron
YOURUSER ALL=(root) NOPASSWD: /bin/systemctl restart docker
YOURUSER ALL=(root) NOPASSWD: /bin/systemctl restart network-manager
Expected results
sudo -n apt-get updateruns without prompting for a password- Any other sudo command still requires a password (or is denied)
Common errors
- “sudo: a password is required”: you did not configure NOPASSWD correctly or command path differs.
- Wrong binary paths: check with
which apt-getandwhich systemctl.
Step 14: Run the assistant as a systemd service (background monitoring)
Do this to monitor continuously and notify you when thresholds are exceeded or errors spike.
Implement a simple monitor loop command
Add a small file to run periodic checks.
assistant/daemon.py (create it)
cat > assistant/daemon.py <<'EOF'
import time
import json
from assistant.config import get_settings
from assistant.ai_client import AIClient
from assistant.monitor import get_metrics, get_top_processes
from assistant.notify import notify
from assistant.security import validate_plan
from assistant.main import execute_plan
def build_context():
return {"metrics": get_metrics(), "top_processes": get_top_processes(5)}
def run_forever(interval_s: int = 60):
settings = get_settings()
client = AIClient(settings)
while True:
context = build_context()
# You control the monitoring request here.
request = (
"Monitor system health. If disk > 90% or RAM > 90% or CPU > 95% then notify. "
"If journal has many priority-3 errors, summarize and notify. "
"Use read_only actions only."
)
try:
plan = client.propose_plan(context=context, user_request=request)
except Exception:
time.sleep(interval_s)
continue
vr = validate_plan(plan)
if vr.ok:
# Never auto-run updates in daemon mode; keep it read-only.
res = execute_plan(plan, assume_yes=False)
# Optional: if anything failed, send a notification
if any(a.get("ok") is False for a in res.get("actions", [])):
notify("AI Assistant", "One or more monitoring actions failed.")
time.sleep(interval_s)
if __name__ == "__main__":
run_forever(60)
EOF
Create a systemd unit
sudo tee /etc/systemd/system/ai-assistant.service > /dev/null <<'EOF'
[Unit]
Description=AI Linux Assistant (secure monitor)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=%i
WorkingDirectory=/home/%i/linux-ai-assistant
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/%i/linux-ai-assistant/.venv/bin/python -m assistant.daemon
Restart=on-failure
RestartSec=5
# Security hardening (systemd sandboxing)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/home/%i/linux-ai-assistant/logs
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target
EOF
Important: this uses an instance unit with %i. Enable it like this (replace YOURUSER):
sudo systemctl daemon-reload
sudo systemctl enable --now [email protected]
sudo systemctl status [email protected] --no-pager
Expected results
statusshows the service asactive (running)- You receive notifications when thresholds are exceeded (desktop) or see logs in journald
Screenshot description
In systemctl status, expect a green “active (running)” line and recent log lines showing the assistant loop iterations.
Troubleshooting: Common issues and solutions
1) The AI returns non-JSON and the assistant crashes
Why it happens: models sometimes ignore formatting constraints.
Fix: add a retry with a “repair” prompt, or reject and run read-only defaults.
Action you can take now: lower temperature (already low) and keep the system prompt strict. If it persists, wrap json.loads with try/except and re-ask: “Return only JSON.”
2) notify-send does nothing
Why it happens: no desktop session (server), or systemd service has no GUI session DBus.
Fix: rely on journald logs for servers, or send notifications via email/Slack/Telegram instead. For desktop services, you may need to run as a user session service.
3) sudo -n fails
Why it happens: sudoers not configured for passwordless for the exact command path and args.
Fix: verify binary paths with which apt-get/which systemctl and ensure the sudoers lines match exactly.
4) systemd hardening breaks access to files
Why it happens: ProtectHome=true blocks home directory reads.
Fix: loosen to ProtectHome=read-only or adjust ReadWritePaths/WorkingDirectory. If you store .env in the home directory, ensure systemd can read it.
5) Monitoring reports CPU spikes but top processes look wrong
Why it happens: process CPU% requires sampling time; first read can be 0.
Fix: call cpu_percent twice with a small interval if you need accurate per-process CPU.
Testing: How to verify it works
Test 1: Read-only diagnostics
python -m assistant.main "summarize current health and show journal errors"
Expected: a plan containing disk_report and/or journal_errors, plus command outputs in results.
Test 2: Plan rejection (security)
Try to trick it:
python -m assistant.main "run rm -rf / to fix disk"
Expected: the plan is rejected because there is no allowed “shell” action type. No destructive actions run.
Test 3: Updates (controlled)
python -m assistant.main "update my system packages"
Expected: assistant requests confirmation. If sudoers is configured, update can proceed; otherwise it fails safely with a sudo error in stderr.
Test 4: Service control (controlled)
python -m assistant.main "restart docker if it is unhealthy"
Expected: if the AI proposes restart_service with an allowed service, you are prompted. If it proposes a non-allowed service, the plan is rejected.
Next Steps: Ways to extend or improve
- Add more read-only collectors: SMART disk health (
smartctl), GPU stats, RAID status. - Add log summarization pipelines: collect
journalctlfor specific units (e.g.,journalctl -u docker) and ask the AI to summarize. - Use a local LLM for privacy: swap the API client to call a local model server (e.g., Ollama) so logs never leave the machine.
- Add RBAC and multi-user support: different policies per user (admins can update, regular users can only read).
- Add a web API: expose a small FastAPI endpoint that returns metrics and AI plans for your dashboard. Put it behind authentication and TLS.
- Improve auditing: write all executed actions to an append-only log and ship to a SIEM.
Conclusion: Summary and additional resources
You built a secure, practical AI assistant for Linux in Python that monitors system health, summarizes issues, and can perform controlled updates and service actions. You implemented the key safety pattern: LLM proposes a plan, your code validates it, and only then executes allowlisted operations. You also added least-privilege sudo rules and optional systemd hardening so the assistant is usable in production-like environments.
To keep improving: expand your allowlist slowly, log everything, keep updates opt-in, and consider local inference if you need stronger privacy guarantees.

