- Posted on
- • Artificial Intelligence
Artificial Intelligence Static Site Hosting
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Static Site Hosting: Build AI‑assisted docs with nothing but Bash
Static sites are fast, cheap, and secure—but what if you want AI features like “search that understands meaning” or a helper that drafts new pages? Good news: you don’t need a full backend to get most of that. In this guide, we’ll wire up a completely static site with AI‑assisted authoring and offline semantic search, using Linux-friendly tools you can automate with Bash.
You’ll learn:
Why AI on static sites is practical and valuable
How to scaffold a docs/blog with MkDocs
How to generate content with a local LLM or an API via a simple Bash script
How to add build‑time embeddings for offline semantic search
How to deploy to GitHub Pages in minutes
Why “AI static site hosting” makes sense
Speed and simplicity: Static hosts (GitHub Pages, Netlify, Cloudflare Pages, S3) are CDN‑fast and dead simple. You ship HTML/JS/CSS and you’re done.
AI without servers: Many AI features can be done at build time:
- AI generates or edits content before publishing.
- You compute text embeddings during build and ship them as JSON. The client runs tiny JavaScript to search semantically—no server required.
Cost and security: No running servers, no databases. If you do call a cloud AI API, keep secrets server-side or use a CI key at build time—never hardcode keys in the client.
Limitations to keep in mind:
- Realtime model inference in the browser is still heavy unless you use small on‑device models or remote APIs. For most docs/blogs, build‑time AI and client‑side vector search are enough.
Prerequisites (Linux)
Install the basics for Git, Python, and CLI tooling.
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y git python3 python3-pip jq curl
Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y git python3 python3-pip jq curl
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y git python3 python3-pip jq curl
MkDocs (site generator) and theme:
python3 -m pip install --user mkdocs mkdocs-material
Optional: local LLM runtime (Ollama) to generate content without an external API:
curl -fsSL https://ollama.com/install.sh | sh
# Example small-ish model:
ollama pull llama3:8b
If you prefer a cloud API, set your key for the shell session (do not commit this):
export OPENAI_API_KEY="sk-..."
Step 1 — Scaffold a static site with MkDocs
mkdocs new ai-static-site
cd ai-static-site
git init
MkDocs creates:
mkdocs.yml (site config)
docs/index.md (home page)
Serve locally:
mkdocs serve
Open http://127.0.0.1:8000 to preview. Stop with Ctrl+C.
Step 2 — AI‑assisted authoring from Bash (local model or API)
Create a small helper that drafts pages for you. It prefers a local model (Ollama) if present; otherwise uses an API if OPENAI_API_KEY is set.
Create scripts/ai-write.sh:
mkdir -p scripts
cat > scripts/ai-write.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
MODEL="${MODEL:-llama3:8b}"
PROMPT="${1:-"Write a Linux-friendly blog post about using Bash to automate AI tasks."}"
OUT="docs/ai-post-$(date +%F-%H%M%S).md"
if command -v ollama >/dev/null 2>&1; then
echo "Using local model via ollama: $MODEL" >&2
ollama run "$MODEL" <<EOM > "$OUT"
Write a concise Markdown blog post for Linux users: $PROMPT
Be practical. Show small Bash snippets in fenced code blocks.
EOM
elif [[ -n "${OPENAI_API_KEY:-}" ]]; then
echo "Using OpenAI API" >&2
curl -sS https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o-mini",
"messages":[
{"role":"system","content":"You are a technical writer who produces concise, correct Markdown for Linux users."},
{"role":"user","content":"'"$PROMPT"'"}
],
"temperature": 0.3
}' | jq -r '.choices[0].message.content' > "$OUT"
else
echo "No local model (ollama) found and OPENAI_API_KEY is not set." >&2
exit 1
fi
echo "Wrote $OUT"
EOF
chmod +x scripts/ai-write.sh
Generate a post:
./scripts/ai-write.sh "How to wire shell scripts into a static docs workflow"
mkdocs serve
Tip:
Review and edit drafts. AI is an assistant, not a source of truth.
Keep sensitive info out of prompts. If using an API, never expose the key client‑side.
Step 3 — Add offline semantic search with build‑time embeddings
We’ll embed your Markdown at build time using a small open model and ship vectors as JSON. The browser runs a tiny cosine-similarity search—no server needed.
Install Python deps:
python3 -m pip install --user sentence-transformers numpy
Create scripts/embed.py:
#!/usr/bin/env python3
import os, json, glob, re
import numpy as np
from sentence_transformers import SentenceTransformer
DOCS_DIR = "docs"
OUT = os.path.join(DOCS_DIR, "embeddings.json")
MODEL = os.environ.get("EMB_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
def read_markdown(path):
with open(path, "r", encoding="utf-8") as f:
return f.read()
def chunks(text, size=800, overlap=100):
# Simple tokenizer by chars; adjust for your content
start = 0
while start < len(text):
end = start + size
yield text[start:end]
start = end - overlap
def clean(md):
# Strip code fences and front matter for cleaner embeddings
md = re.sub(r"```.*?```", " ", md, flags=re.S)
md = re.sub(r"^---.*?---", " ", md, flags=re.S|re.M)
return re.sub(r"\s+", " ", md).strip()
def main():
model = SentenceTransformer(MODEL)
entries = []
for path in sorted(glob.glob(os.path.join(DOCS_DIR, "**/*.md"), recursive=True)):
rel = os.path.relpath(path, DOCS_DIR)
text = clean(read_markdown(path))
for i, ch in enumerate(chunks(text)):
if len(ch.strip()) < 60:
continue
vec = model.encode([ch], normalize_embeddings=True)[0].tolist()
entries.append({"doc": rel, "chunk": i, "text": ch, "vec": vec})
with open(OUT, "w", encoding="utf-8") as f:
json.dump({"model": MODEL, "entries": entries}, f)
print(f"Wrote {OUT} with {len(entries)} chunks")
if __name__ == "__main__":
main()
Run it after adding/editing docs:
python3 scripts/embed.py
Add a tiny client-side search. Create docs/javascripts/search.js:
(async function(){
const box = document.createElement('input');
box.type = 'search';
box.placeholder = 'Semantic search...';
box.style = 'width:100%;padding:0.5rem;margin:1rem 0;';
const out = document.createElement('div');
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('main') || document.body;
container.prepend(out);
container.prepend(box);
});
function cos(a,b){
let s=0;
for (let i=0;i<a.length;i++) s+=a[i]*b[i];
return s;
}
let data = null;
async function load() {
const r = await fetch('/embeddings.json');
data = await r.json();
}
load();
async function embedQuery(q){
// Cheap approximation: query-time fallback using same vector space is not available client-side.
// Simple hack: use chunk-average of top TF-ish terms. For best results, precompute queries or
// serve a tiny WASM encoder. For now, we use a naive proxy: find overlapping words.
// Practical alternative: do query encoding at build time for common queries.
q = q.toLowerCase().split(/\W+/).filter(Boolean);
// Score entries by keyword overlap as a lightweight fallback:
return data.entries.map((e, idx) => {
const w = e.text.toLowerCase();
const score = q.reduce((s,t)=> s + (w.includes(t)?1:0), 0);
return {idx, score};
});
}
async function search(q){
if (!data) await load();
// If you can ship a tiny WASM encoder, replace embedQuery() + cos() with real embeddings for q.
const scored = await embedQuery(q);
scored.sort((a,b)=> b.score - a.score);
const top = scored.slice(0, 5).map(s => data.entries[s.idx]);
out.innerHTML = top.map(t => (
'<div style="margin:0.5rem 0;padding:0.5rem;border:1px solid #ddd;">' +
'<div><strong>'+ t.doc +'</strong> — chunk '+ t.chunk +'</div>' +
'<div style="font-size:.9em;opacity:.9">'+ t.text.substring(0,300) +'...</div>' +
'</div>'
)).join('');
}
box.addEventListener('input', (e)=> {
const q = e.target.value.trim();
if (q.length < 2) { out.innerHTML=''; return; }
search(q);
});
})();
Wire the JS into MkDocs. Edit mkdocs.yml:
site_name: AI Static Site
theme:
name: material
extra_javascript:
- javascripts/search.js
Rebuild embeddings and serve:
python3 scripts/embed.py
mkdocs serve
Note:
- The snippet above uses a lightweight keyword‑overlap fallback for query vectors to keep everything static. For stronger results:
- Precompute embeddings for common queries (FAQ) at build time.
- Or load a tiny WASM encoder for true client-side embeddings.
- Or proxy to an API behind a serverless function to encode queries at runtime without exposing keys.
Step 4 — Build and deploy (GitHub Pages)
Build locally:
mkdocs build
Quick deploy to GitHub Pages (pushes to gh-pages branch):
git add .
git commit -m "AI static site: initial"
git branch -M main
git remote add origin https://github.com/<you>/ai-static-site.git
git push -u origin main
mkdocs gh-deploy --force
Automate with GitHub Actions. Create .github/workflows/build.yml:
name: build-and-deploy
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install mkdocs mkdocs-material sentence-transformers numpy
- run: python scripts/embed.py
- run: mkdocs build --strict
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
For Netlify/Cloudflare Pages:
Build command:
mkdocs buildPublish directory:
site/
Real‑world ways to use this today
Product docs with “good enough” semantic search delivered entirely statically.
Personal tech blog where
./scripts/ai-write.shdrafts posts you refine and publish.Internal knowledge base on S3+CloudFront; a nightly CI regenerates embeddings and deploys.
Release notes: pipe commits or PR titles into the AI script to draft change summaries.
Security and performance tips
Never ship API keys to the browser. Do AI work at build time or via a tiny serverless proxy.
Keep models small for local use (e.g., llama3:8b). Cache model weights and CI layers to reduce build time.
Validate AI output like any other contribution; treat it as a suggestion.
Store embeddings as compact Float32 arrays if size grows; chunk wisely.
Recap and next steps (CTA)
You don’t need a backend to ship practical AI features:
Author with AI locally or via API using a simple Bash script
Bake embeddings at build time for semantic search
Host the result on any static platform
Next steps:
Add your first AI‑generated draft:
./scripts/ai-write.sh "Topic"Run
python3 scripts/embed.pyandmkdocs serveDeploy with
mkdocs gh-deployor your preferred static host
If you want a starter repo, ask and I’ll assemble these scripts and config into a template you can fork.