- Posted on
- • Artificial Intelligence
Creating an Artificial Intelligence Linux Knowledge Base
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Creating an Artificial Intelligence Linux Knowledge Base (From Your Terminal)
Ever lost 30 minutes hunting for that one command flag you used last quarter? Your knowledge is everywhere—man pages, /usr/share/doc, wikis, tickets, personal notes, and a dozen Git repos. What if your terminal had a local, privacy-preserving “brain” that lets you ask questions in natural language and instantly retrieves the most relevant snippets?
That’s exactly what we’ll build: an AI-powered, grep-like knowledge base for Linux that runs locally, indexes your docs, and answers questions using semantic search (vector embeddings). No cloud dependency, no “vendor lock-in,” just Bash and a small Python toolchain.
Why this matters
Reduce context switching: Stop paging through browser tabs and docs.
Stay private: Your data never leaves your machine.
Make tacit knowledge discoverable: Surface runbooks, readmes, and tribal know-how.
Works with what you already have: man pages, Markdown, logs, and docs.
We’ll use retrieval-augmented techniques: convert your content to text, embed it with a small open-source model, and search using a vector index—all locally.
What you’ll build
A content pipeline to gather docs from man pages,
/usr/share/doc, wikis/READMEs.A Python-based embedding + FAISS index.
A tiny CLI (
kb) to index and query:kb indexandkb query "how do I...".Optional automation to keep the index fresh with systemd timers.
Prerequisites: Install system packages
We’ll need Python, Git, ripgrep, pandoc, and build tools.
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y \
python3 python3-venv python3-pip \
git ripgrep pandoc \
build-essential jq curl
Fedora/RHEL (dnf):
sudo dnf install -y \
python3 python3-pip python3-virtualenv \
git ripgrep pandoc \
gcc gcc-c++ make jq curl
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y \
python3 python3-pip python3-virtualenv \
git ripgrep pandoc \
gcc gcc-c++ make jq curl
Step 1: Gather and normalize your sources
Create a workspace:
mkdir -p ~/kb/{raw,txt,index,bin}
1) Export man pages to plain text:
# This can take a few minutes; trims formatting with 'col -b'
man -k . | awk -F ' - ' '{print $1}' | awk '{print $1}' | sort -u | \
while read -r topic; do
man "$topic" | col -b > "$HOME/kb/txt/man_${topic}.txt" 2>/dev/null || true
done
2) Convert docs from /usr/share/doc:
find /usr/share/doc -type f \( -name '*.gz' -o -name '*.txt' -o -name '*.md' -o -name '*.rst' \) -print0 |
while IFS= read -r -d '' f; do
base="$(basename "${f%.gz}")"
if [[ "$f" == *.gz ]]; then
zcat "$f" 2>/dev/null | col -b > "$HOME/kb/txt/doc_${base}.txt" || true
else
cat "$f" | col -b > "$HOME/kb/txt/doc_${base}.txt"
fi
done
3) Convert Markdown/HTML/PDF from your repos or wiki exports with pandoc:
# Example: Convert a project README to plain text
pandoc -f markdown -t plain -o "$HOME/kb/txt/repo_readme.txt" /path/to/repo/README.md
# HTML export (e.g., from Confluence or a wiki dump)
pandoc -f html -t plain -o "$HOME/kb/txt/wiki_export.txt" ~/Downloads/wiki_export.html
4) Optionally, add your notes and runbooks:
# Copy all .md and .txt from a notes directory
find ~/notes -type f \( -name '*.md' -o -name '*.txt' \) -print0 |
xargs -0 -I{} sh -c 'pandoc -f markdown -t plain -o "$HOME/kb/txt/notes_$(basename "{}").txt" "{}"'
Tip: If you have secrets in your notes, keep a separate, sanitized directory for indexing.
Step 2: Create a Python virtual environment and install libs
We’ll use a small Sentence-Transformers model and FAISS (CPU).
python3 -m venv ~/kb/venv
source ~/kb/venv/bin/activate
python -m pip install --upgrade pip
pip install "sentence-transformers==2.*" faiss-cpu tqdm
If you ever need to leave the venv:
deactivate
Step 3: Build the index (embeddings + FAISS)
Save this as ~/kb/bin/build_index.py:
#!/usr/bin/env python3
import argparse, json, os, sys
from pathlib import Path
from tqdm import tqdm
# Embeddings
from sentence_transformers import SentenceTransformer
import numpy as np
# FAISS
import faiss # type: ignore
def iter_text_files(root):
root = Path(root)
for p in root.rglob("*.txt"):
if p.is_file():
yield p
def chunk_text(text, max_words=500, overlap=50):
words = text.split()
chunks = []
i = 0
n = len(words)
while i < n:
j = min(i + max_words, n)
chunk = " ".join(words[i:j]).strip()
if chunk:
chunks.append(chunk)
i = j - overlap if (j - overlap) > i else j
return chunks
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--txt-dir", default=str(Path.home()/ "kb" / "txt"))
ap.add_argument("--out-dir", default=str(Path.home()/ "kb" / "index"))
ap.add_argument("--model", default="sentence-transformers/all-MiniLM-L6-v2")
ap.add_argument("--max-words", type=int, default=500)
ap.add_argument("--overlap", type=int, default=50)
args = ap.parse_args()
txt_dir = Path(args.txt_dir)
out_dir = Path(args.out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
print(f"[+] Loading model: {args.model}")
model = SentenceTransformer(args.model)
texts = []
metas = []
for path in tqdm(list(iter_text_files(txt_dir)), desc="Collecting"):
try:
content = Path(path).read_text(errors="ignore")
except Exception:
continue
chunks = chunk_text(content, max_words=args.max_words, overlap=args.overlap)
for idx, ch in enumerate(chunks):
texts.append(ch)
metas.append({"source": str(path), "chunk": idx})
if not texts:
print("[-] No text found to index.", file=sys.stderr)
sys.exit(1)
print(f"[+] Encoding {len(texts)} chunks...")
embs = model.encode(texts, show_progress_bar=True, normalize_embeddings=True)
embs = np.asarray(embs).astype("float32")
dim = embs.shape[1]
index = faiss.IndexFlatIP(dim) # cosine similarity via normalized vectors
index.add(embs)
faiss.write_index(index, str(out_dir / "kb.faiss"))
with open(out_dir / "meta.jsonl", "w") as f:
for m in metas:
f.write(json.dumps(m) + "\n")
with open(out_dir / "model.txt", "w") as f:
f.write(args.model + "\n")
print(f"[+] Wrote {len(texts)} vectors to {out_dir/'kb.faiss'}")
print(f"[+] Metadata saved to {out_dir/'meta.jsonl'}")
if __name__ == "__main__":
main()
Make it executable:
chmod +x ~/kb/bin/build_index.py
Build the index:
source ~/kb/venv/bin/activate
~/kb/bin/build_index.py --txt-dir ~/kb/txt --out-dir ~/kb/index
Step 4: Query the knowledge base from Bash
Create ~/kb/bin/query_kb.py:
#!/usr/bin/env python3
import argparse, json
from pathlib import Path
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss # type: ignore
def load_meta(meta_path):
metas = []
with open(meta_path) as f:
for line in f:
metas.append(json.loads(line))
return metas
def main():
ap = argparse.ArgumentParser()
ap.add_argument("query", help="question or keywords")
ap.add_argument("-k", "--top-k", type=int, default=5)
ap.add_argument("--index-dir", default=str(Path.home()/ "kb" / "index"))
args = ap.parse_args()
index_dir = Path(args.index_dir)
index = faiss.read_index(str(index_dir / "kb.faiss"))
metas = load_meta(index_dir / "meta.jsonl")
model_name = (index_dir / "model.txt").read_text().strip()
model = SentenceTransformer(model_name)
q = model.encode([args.query], normalize_embeddings=True)
D, I = index.search(np.asarray(q).astype("float32"), args.top_k)
for rank, (score, idx) in enumerate(zip(D[0], I[0]), start=1):
meta = metas[idx]
src = meta.get("source", "unknown")
chunk_id = meta.get("chunk", 0)
# Read a small preview
try:
with open(src, "r", errors="ignore") as f:
lines = f.read().splitlines()
preview = " ".join(lines[max(0, chunk_id*30): (chunk_id*30)+20])[:300]
except Exception:
preview = "(preview not available)"
print(f"{rank}. {src} [score={score:.3f}]")
print(f" {preview}\n")
if __name__ == "__main__":
main()
Make it executable:
chmod +x ~/kb/bin/query_kb.py
Test a query:
source ~/kb/venv/bin/activate
~/kb/bin/query_kb.py "how to add a user to a group" -k 5
Step 5: A friendly CLI wrapper
Make a simple kb script so you don’t have to remember Python paths. Save as ~/kb/bin/kb:
#!/usr/bin/env bash
set -euo pipefail
KB_DIR="${KB_DIR:-$HOME/kb}"
VENV="$KB_DIR/venv"
TXT="$KB_DIR/txt"
IDX="$KB_DIR/index"
BIN="$KB_DIR/bin"
usage() {
cat <<EOF
Usage:
kb index # (re)build the index from \$HOME/kb/txt
kb query "question" # search the knowledge base
kb help # show this help
EOF
}
ensure_venv() {
if [[ ! -d "$VENV" ]]; then
python3 -m venv "$VENV"
source "$VENV/bin/activate"
python -m pip install --upgrade pip
pip install "sentence-transformers==2.*" faiss-cpu tqdm
else
source "$VENV/bin/activate"
fi
}
cmd="${1:-help}"
shift || true
case "$cmd" in
index)
ensure_venv
"$BIN/build_index.py" --txt-dir "$TXT" --out-dir "$IDX"
;;
query)
ensure_venv
if [[ $# -lt 1 ]]; then
echo "Need a query string."
exit 1
fi
"$BIN/query_kb.py" "$@"
;;
help|--help|-h)
usage
;;
*)
echo "Unknown command: $cmd"
usage
exit 1
;;
esac
Make it executable and add to PATH:
chmod +x ~/kb/bin/kb
echo 'export PATH="$HOME/kb/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Now try:
kb query "find and replace text with sed"
kb query "systemd timer example"
kb index # after you add new docs
Keep it fresh: automate re-indexing
Use a user-level systemd timer to rebuild the index daily.
Create ~/.config/systemd/user/kb-index.service:
[Unit]
Description=Rebuild local AI KB index
[Service]
Type=oneshot
Environment=KB_DIR=%h/kb
ExecStart=%h/kb/bin/kb index
Create ~/.config/systemd/user/kb-index.timer:
[Unit]
Description=Rebuild AI KB daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer:
systemctl --user daemon-reload
systemctl --user enable --now kb-index.timer
systemctl --user list-timers | grep kb-index
Real-world examples
SRE runbooks: Index on-call guides, incident postmortems, and dashboards docs. Query “rotate TLS cert on service X” and jump to the exact steps.
Platform engineering: Index Helm chart READMEs and Kubernetes manifests. Ask “ingress annotations for sticky sessions.”
Security ops: Index hardening guides and CIS benchmarks. Query “auditd rules for file integrity.”
Tips, extensions, and safety
Source selection: Keep public and private corpora separate if needed.
Sensitive data: Exclude secrets with find/grep rules or maintain a sanitized copy.
Speed: The default MiniLM model is fast on CPU. For larger corpora, consider a stronger model or a vector DB daemon (Qdrant, Milvus) later.
Quality: Chunk size matters. Too big and results get fuzzy; too small and context is lost. Start with 300–600 words.
Shell ergonomics: Pipe results into
fzfor open files in$EDITOR:
kb query "iptables allow outbound https" | fzf
Conclusion and next steps (CTA)
You now have a local, AI-powered knowledge base that turns your Linux box into a searchable brain. Start small: index man pages and /usr/share/doc, then grow with runbooks, READMEs, and wiki exports. Make it part of your workflow:
Run
kb indexweekly (or enable the systemd timer).Add new sources to
~/kb/txt.Use
kb query "..."whenever you reach for a browser.
Want to go further? Add a lightweight answer generator on top (e.g., call a local LLM via llama.cpp) to summarize the retrieved snippets. But even as-is, you’ll save time, reduce context-switching, and keep your know-how on your own machine.
If this helped, share it with your team and turn your collective docs into a first-class, terminal-native knowledge base.