Compare commits
6 Commits
fb57233ce8
...
0544dba9f5
| Author | SHA1 | Date |
|---|---|---|
|
|
0544dba9f5 | |
|
|
ae84c3d259 | |
|
|
bf35a78658 | |
|
|
3c24b7fbc4 | |
|
|
af75f2c608 | |
|
|
a57c459c8a |
|
|
@ -833,10 +833,10 @@ The ARR/media stack is intentionally not deployed by the Kubernetes app
|
|||
pipeline. It is better suited to a Docker Compose stack on the Debian host with
|
||||
persistent volumes under `/data`, where large media paths, qBittorrent writes,
|
||||
and service config directories can survive cluster rebuilds without OpenEBS
|
||||
node-affinity or PVC migration concerns. The legacy `apps/arr-stack` manifests
|
||||
are retained only as migration reference. The active host-level Compose
|
||||
node-affinity or PVC migration concerns. The active host-level Compose
|
||||
definition lives under `infra/arr-stack` and stores persistent data under
|
||||
`/data/arr`.
|
||||
`/data/arr`. Historical Kubernetes manifests are archived under
|
||||
`archive/apps/arr-stack` for migration reference only.
|
||||
|
||||
## Storage
|
||||
|
||||
|
|
@ -1113,7 +1113,8 @@ Build the local homelab knowledge index separately from the main deployment:
|
|||
```
|
||||
|
||||
The index is built from the non-secret source list in
|
||||
`infra/ai/knowledge-sources.txt` and defaults to `/data/homelab-ai/index`.
|
||||
`infra/ai/knowledge-sources.txt`, skips paths in
|
||||
`infra/ai/knowledge-excludes.txt`, and defaults to `/data/homelab-ai/index`.
|
||||
Doctor commands use it as extra context when it exists, but infrastructure
|
||||
deployment does not depend on it.
|
||||
Use `ask` as the runbook and command finder. It returns the closest indexed
|
||||
|
|
|
|||
|
|
@ -833,10 +833,10 @@ The ARR/media stack is intentionally not deployed by the Kubernetes app
|
|||
pipeline. It is better suited to a Docker Compose stack on the Debian host with
|
||||
persistent volumes under `/data`, where large media paths, qBittorrent writes,
|
||||
and service config directories can survive cluster rebuilds without OpenEBS
|
||||
node-affinity or PVC migration concerns. The legacy `apps/arr-stack` manifests
|
||||
are retained only as migration reference. The active host-level Compose
|
||||
node-affinity or PVC migration concerns. The active host-level Compose
|
||||
definition lives under `infra/arr-stack` and stores persistent data under
|
||||
`/data/arr`.
|
||||
`/data/arr`. Historical Kubernetes manifests are archived under
|
||||
`archive/apps/arr-stack` for migration reference only.
|
||||
|
||||
## Storage
|
||||
|
||||
|
|
@ -1113,7 +1113,8 @@ Build the local homelab knowledge index separately from the main deployment:
|
|||
```
|
||||
|
||||
The index is built from the non-secret source list in
|
||||
`infra/ai/knowledge-sources.txt` and defaults to `/data/homelab-ai/index`.
|
||||
`infra/ai/knowledge-sources.txt`, skips paths in
|
||||
`infra/ai/knowledge-excludes.txt`, and defaults to `/data/homelab-ai/index`.
|
||||
Doctor commands use it as extra context when it exists, but infrastructure
|
||||
deployment does not depend on it.
|
||||
Use `ask` as the runbook and command finder. It returns the closest indexed
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
variable "edge_host" {
|
||||
type = string
|
||||
default = "132.145.170.74"
|
||||
default = ""
|
||||
|
||||
validation {
|
||||
condition = var.edge_host != ""
|
||||
error_message = "edge_host must be provided from homelab.yml through jeannie or an explicit tfvars/env value."
|
||||
}
|
||||
}
|
||||
|
||||
variable "edge_user" {
|
||||
|
|
@ -25,19 +30,17 @@ variable "edge_install_docker" {
|
|||
|
||||
variable "server_name" {
|
||||
type = string
|
||||
default = "lab2025.duckdns.org"
|
||||
default = ""
|
||||
|
||||
validation {
|
||||
condition = var.server_name != ""
|
||||
error_message = "server_name must be provided from homelab.yml through jeannie or an explicit tfvars/env value."
|
||||
}
|
||||
}
|
||||
|
||||
variable "additional_server_names" {
|
||||
type = list(string)
|
||||
default = [
|
||||
"demos.lab2025.duckdns.org",
|
||||
"heimdall.lab2025.duckdns.org",
|
||||
"grafana.lab2025.duckdns.org",
|
||||
"prometheus.lab2025.duckdns.org",
|
||||
"alertmanager.lab2025.duckdns.org",
|
||||
"argocd.lab2025.duckdns.org",
|
||||
]
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "enable_letsencrypt" {
|
||||
|
|
@ -57,7 +60,12 @@ variable "letsencrypt_staging" {
|
|||
|
||||
variable "backend_host" {
|
||||
type = string
|
||||
default = "192.168.100.240"
|
||||
default = ""
|
||||
|
||||
validation {
|
||||
condition = var.backend_host != ""
|
||||
error_message = "backend_host must be provided from homelab.yml through jeannie or an explicit tfvars/env value."
|
||||
}
|
||||
}
|
||||
|
||||
variable "backend_port" {
|
||||
|
|
@ -77,7 +85,12 @@ variable "gitea_backend_port" {
|
|||
|
||||
variable "gitea_backend_host" {
|
||||
type = string
|
||||
default = "100.85.138.30"
|
||||
default = ""
|
||||
|
||||
validation {
|
||||
condition = var.gitea_backend_host != ""
|
||||
error_message = "gitea_backend_host must be provided from homelab.yml through jeannie or an explicit tfvars/env value."
|
||||
}
|
||||
}
|
||||
|
||||
variable "haproxy_stats_user" {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,9 @@ plane pods off the Debian host.
|
|||
including the meaning, diagnostic commands, whether a Jeannie fix command
|
||||
already exists, risk, and auto-fix safety. This command is read-only and refuses
|
||||
mutating commands. Use `explain scorecard [--all]` for scorecard-specific
|
||||
category explanations, or pipe captured output into `explain output`.
|
||||
category explanations, use it directly with helpers such as `capacity-advisor`,
|
||||
`capacity-limits`, `impact`, and `review-last-change`, or pipe captured output
|
||||
into `explain output`.
|
||||
|
||||
`artifact-cache {status|up|down|instructions}`
|
||||
: Manage the optional Debian-host artifact cache stack for apt packages and
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
# Files and globs excluded from the local homelab AI helper.
|
||||
# Keep inactive reference material out of answers about the active lab.
|
||||
archive/**
|
||||
labs/**
|
||||
apps/arr-stack/**
|
||||
apps/mlops-platform/**
|
||||
|
|
@ -4,6 +4,9 @@ defaults:
|
|||
limit_cpu_multiplier: 4
|
||||
limit_memory_multiplier: 2
|
||||
|
||||
# Suggestion-only profiles for Kubernetes namespaces managed by the app pipeline.
|
||||
# Host-level Docker Compose stacks such as infra/arr-stack are intentionally not
|
||||
# listed here.
|
||||
namespaces:
|
||||
website-production:
|
||||
request_cpu_m: 75
|
||||
|
|
@ -20,11 +23,6 @@ namespaces:
|
|||
request_memory_mib: 128
|
||||
limit_cpu_multiplier: 4
|
||||
limit_memory_multiplier: 2
|
||||
arr-stack:
|
||||
request_cpu_m: 150
|
||||
request_memory_mib: 512
|
||||
limit_cpu_multiplier: 3
|
||||
limit_memory_multiplier: 2
|
||||
argocd:
|
||||
request_cpu_m: 100
|
||||
request_memory_mib: 256
|
||||
|
|
|
|||
60
jeannie
60
jeannie
|
|
@ -29,6 +29,7 @@ load_homelab_inventory_defaults() {
|
|||
|
||||
rendered_defaults="$(python3 - "${inventory_file}" <<'PY'
|
||||
import re
|
||||
import json
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
|
|
@ -140,6 +141,14 @@ if debian_ip and gitea_http:
|
|||
gitea_ts = values.get("hosts.debian.tailscale_ip")
|
||||
if gitea_ts:
|
||||
print(f": ${{LAB_GITEA_TAILSCALE_IP:={shlex.quote(gitea_ts)}}}")
|
||||
subdomains = values.get("domain.subdomains")
|
||||
if subdomains:
|
||||
if isinstance(subdomains, str):
|
||||
subdomain_values = [item.strip() for item in subdomains.split(",") if item.strip()]
|
||||
else:
|
||||
subdomain_values = list(subdomains)
|
||||
if subdomain_values:
|
||||
print(f": ${{LAB_ADDITIONAL_SERVER_NAMES_JSON:={shlex.quote(json.dumps(subdomain_values))}}}")
|
||||
PY
|
||||
)"
|
||||
|
||||
|
|
@ -182,6 +191,7 @@ export_homelab_inventory_tf_vars() {
|
|||
export_if_unset TF_VAR_edge_user "${LAB_EDGE_USER:-}"
|
||||
export_if_unset TF_VAR_edge_install_dir "${LAB_EDGE_INSTALL_DIR:-}"
|
||||
export_if_unset TF_VAR_server_name "${LAB_DOMAIN:-}"
|
||||
export_if_unset TF_VAR_additional_server_names "${LAB_ADDITIONAL_SERVER_NAMES_JSON:-}"
|
||||
export_if_unset TF_VAR_backend_host "${LAB_TRAEFIK_LB_IP:-}"
|
||||
export_if_unset TF_VAR_gitea_backend_host "${LAB_DEBIAN_TAILSCALE_IP:-}"
|
||||
export_if_unset TF_VAR_gitea_backend_port "${LAB_GITEA_HTTP_PORT:-}"
|
||||
|
|
@ -1363,7 +1373,7 @@ check_debian_tailscale_ip() {
|
|||
}
|
||||
|
||||
check_edge_ssh() {
|
||||
local edge_host="${LAB_EDGE_HOST:-132.145.170.74}"
|
||||
local edge_host="${LAB_EDGE_HOST:?LAB_EDGE_HOST is required from homelab.yml}"
|
||||
local edge_user="${LAB_EDGE_USER:-ubuntu}"
|
||||
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${edge_user}@${edge_host}" "true"
|
||||
|
|
@ -1466,7 +1476,7 @@ sudo rm -f \"\$root/.homelab-check/write-test\""
|
|||
}
|
||||
|
||||
check_edge_disk_free() {
|
||||
local edge_host="${LAB_EDGE_HOST:-132.145.170.74}"
|
||||
local edge_host="${LAB_EDGE_HOST:?LAB_EDGE_HOST is required from homelab.yml}"
|
||||
local edge_user="${LAB_EDGE_USER:-ubuntu}"
|
||||
local min_gib="${LAB_PREFLIGHT_EDGE_MIN_FREE_GIB:-2}"
|
||||
|
||||
|
|
@ -1536,7 +1546,7 @@ homelab_preflight() {
|
|||
fi
|
||||
preflight_warn "RPi Tailscale IP ${LAB_RPI_TAILSCALE_IP:-unset}" check_rpi_tailscale_ip
|
||||
preflight_check "Pimox storage ${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-opi5_ssd}} is active" check_pimox_storage || failures=$((failures + 1))
|
||||
preflight_check "OCI edge SSH ${LAB_EDGE_USER:-ubuntu}@${LAB_EDGE_HOST:-132.145.170.74}" check_edge_ssh || failures=$((failures + 1))
|
||||
preflight_check "OCI edge SSH ${LAB_EDGE_USER:-ubuntu}@${LAB_EDGE_HOST:?LAB_EDGE_HOST is required from homelab.yml}" check_edge_ssh || failures=$((failures + 1))
|
||||
|
||||
if ((failures > 0)); then
|
||||
echo "Preflight failed with ${failures} blocking check(s). Fix the inventory or host state before continuing." >&2
|
||||
|
|
@ -2830,8 +2840,8 @@ deploy_gitea() {
|
|||
local image="${LAB_GITEA_IMAGE:-gitea/gitea:1.21.7}"
|
||||
local http_port="${LAB_GITEA_HTTP_PORT:-3000}"
|
||||
local ssh_port="${LAB_GITEA_SSH_PORT:-32222}"
|
||||
local domain="${LAB_GITEA_DOMAIN:-${LAB_DOMAIN:-lab2025.duckdns.org}}"
|
||||
local root_url="${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}"
|
||||
local domain="${LAB_GITEA_DOMAIN:-${LAB_DOMAIN:?LAB_DOMAIN is required from homelab.yml}}"
|
||||
local root_url="${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}"
|
||||
local container_name="${LAB_GITEA_CONTAINER_NAME:-homelab-gitea}"
|
||||
local compose_file="${REPO_ROOT}/infra/gitea/docker-compose.yml"
|
||||
local install_docker="${LAB_GITEA_INSTALL_DOCKER:-false}"
|
||||
|
|
@ -3248,7 +3258,7 @@ bootstrap_gitea_repo() {
|
|||
local container_name="${LAB_GITEA_CONTAINER_NAME:-homelab-gitea}"
|
||||
local http_port="${LAB_GITEA_HTTP_PORT:-3000}"
|
||||
local ssh_port="${LAB_GITEA_SSH_PORT:-32222}"
|
||||
local root_url="${LAB_GITEA_ROOT_URL:-https://lab2025.duckdns.org/git/}"
|
||||
local root_url="${LAB_GITEA_ROOT_URL:?LAB_GITEA_ROOT_URL is required from homelab.yml}"
|
||||
local repo_owner="${LAB_GITEA_REPO_OWNER:-jv}"
|
||||
local repo_name="${LAB_GITEA_REPO_NAME:-my-homelab-configs}"
|
||||
local default_branch="${LAB_GITEA_REPO_DEFAULT_BRANCH:-main}"
|
||||
|
|
@ -4659,7 +4669,7 @@ status_http_reachable() {
|
|||
}
|
||||
|
||||
status_gitea_public_ok() {
|
||||
local root_url="${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}"
|
||||
local root_url="${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}"
|
||||
|
||||
status_http_reachable "${root_url}"
|
||||
}
|
||||
|
|
@ -4957,8 +4967,8 @@ status_cascade_report() {
|
|||
|
||||
status_section "Edge And Public URLs"
|
||||
status_cascade_check "OCI edge SSH" check_edge_ssh || failures=$((failures + 1))
|
||||
status_cascade_check "Traefik LoadBalancer HTTP" status_http_reachable "http://${LAB_TRAEFIK_LB_IP:-192.168.100.240}/" || failures=$((failures + 1))
|
||||
status_cascade_check "Website public URL" status_http_ok "${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/" || failures=$((failures + 1))
|
||||
status_cascade_check "Traefik LoadBalancer HTTP" status_http_reachable "http://${LAB_TRAEFIK_LB_IP:?LAB_TRAEFIK_LB_IP is required from homelab.yml}/" || failures=$((failures + 1))
|
||||
status_cascade_check "Website public URL" status_http_ok "${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/" || failures=$((failures + 1))
|
||||
status_cascade_check "Gitea public route" status_gitea_public_ok || failures=$((failures + 1))
|
||||
|
||||
if ((failures > 0)); then
|
||||
|
|
@ -5311,10 +5321,10 @@ status_report() {
|
|||
fi
|
||||
|
||||
status_section "HTTP"
|
||||
status_http "website public" "${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}"
|
||||
status_http "website public" "${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}"
|
||||
status_http "gitea local" "http://127.0.0.1:${LAB_GITEA_HTTP_PORT:-3000}/"
|
||||
status_http "traefik lb" "http://${LAB_TRAEFIK_LB_IP:-192.168.100.240}/"
|
||||
status_http "traefik lb" "http://${LAB_TRAEFIK_LB_IP:?LAB_TRAEFIK_LB_IP is required from homelab.yml}/"
|
||||
status_http "arr radarr" "http://127.0.0.1:7878/"
|
||||
status_http "arr sonarr" "http://127.0.0.1:8989/"
|
||||
status_http "arr prowlarr" "http://127.0.0.1:9696/"
|
||||
|
|
@ -5323,17 +5333,17 @@ status_report() {
|
|||
}
|
||||
|
||||
doctor_edge() {
|
||||
local edge_host="${LAB_EDGE_HOST:-132.145.170.74}"
|
||||
local edge_host="${LAB_EDGE_HOST:?LAB_EDGE_HOST is required from homelab.yml}"
|
||||
local edge_user="${LAB_EDGE_USER:-ubuntu}"
|
||||
local traefik_ip="${LAB_TRAEFIK_LB_IP:-192.168.100.240}"
|
||||
local gitea_ts_ip="${LAB_GITEA_TAILSCALE_IP:-100.85.138.30}"
|
||||
local traefik_ip="${LAB_TRAEFIK_LB_IP:?LAB_TRAEFIK_LB_IP is required from homelab.yml}"
|
||||
local gitea_ts_ip="${LAB_GITEA_TAILSCALE_IP:?LAB_GITEA_TAILSCALE_IP is required from homelab.yml}"
|
||||
local gitea_http_port="${LAB_GITEA_HTTP_PORT:-3000}"
|
||||
|
||||
require_debian_server "doctor-edge"
|
||||
|
||||
status_section "Doctor Edge"
|
||||
status_http "website public" "${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}"
|
||||
status_http "website public" "${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}"
|
||||
|
||||
printf '\n-- edge backend reachability\n'
|
||||
if ! ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new "${edge_user}@${edge_host}" "
|
||||
|
|
@ -5359,8 +5369,8 @@ EOF
|
|||
|
||||
backstage_brain_note "doctor-edge" <<EOF
|
||||
Doctor command: doctor-edge
|
||||
Public domain: ${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/
|
||||
Public Gitea URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}
|
||||
Public domain: ${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/
|
||||
Public Gitea URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}
|
||||
OCI edge SSH target: ${edge_user}@${edge_host}
|
||||
Traefik backend from edge: http://${traefik_ip}:80/
|
||||
Gitea backend from edge: http://${gitea_ts_ip}:${gitea_http_port}/
|
||||
|
|
@ -5369,9 +5379,9 @@ EOF
|
|||
}
|
||||
|
||||
doctor_gitea() {
|
||||
local edge_host="${LAB_EDGE_HOST:-132.145.170.74}"
|
||||
local edge_host="${LAB_EDGE_HOST:?LAB_EDGE_HOST is required from homelab.yml}"
|
||||
local edge_user="${LAB_EDGE_USER:-ubuntu}"
|
||||
local gitea_ts_ip="${LAB_GITEA_TAILSCALE_IP:-100.85.138.30}"
|
||||
local gitea_ts_ip="${LAB_GITEA_TAILSCALE_IP:?LAB_GITEA_TAILSCALE_IP is required from homelab.yml}"
|
||||
local gitea_host="${LAB_GITEA_HOST:-192.168.100.73}"
|
||||
local gitea_http_port="${LAB_GITEA_HTTP_PORT:-3000}"
|
||||
local gitea_ssh_port="${LAB_GITEA_SSH_PORT:-32222}"
|
||||
|
|
@ -5380,7 +5390,7 @@ doctor_gitea() {
|
|||
|
||||
status_section "Doctor Gitea"
|
||||
status_http "gitea local" "http://127.0.0.1:${gitea_http_port}/"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}"
|
||||
status_http "gitea public" "${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}"
|
||||
status_compose_stack "gitea" "/data/homelab-gitea"
|
||||
|
||||
status_run "gitea git remote" git ls-remote gitea main
|
||||
|
|
@ -5409,7 +5419,7 @@ Gitea local URL: http://127.0.0.1:${gitea_http_port}/
|
|||
Gitea LAN host: ${gitea_host}
|
||||
Gitea Tailscale backend: http://${gitea_ts_ip}:${gitea_http_port}/
|
||||
Gitea SSH port: ${gitea_ssh_port}
|
||||
Gitea public URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:-https://lab2025.duckdns.org}/git/}
|
||||
Gitea public URL: ${LAB_GITEA_ROOT_URL:-${LAB_PUBLIC_URL:?LAB_PUBLIC_URL is required from homelab.yml}/git/}
|
||||
Known runbook: docs/runbooks/gitea-failures.md
|
||||
EOF
|
||||
}
|
||||
|
|
@ -5445,7 +5455,7 @@ doctor_cluster() {
|
|||
status_systemd_units kubelet containerd docker
|
||||
status_kubernetes
|
||||
status_pimox_workers
|
||||
status_http "traefik lb" "http://${LAB_TRAEFIK_LB_IP:-192.168.100.240}/"
|
||||
status_http "traefik lb" "http://${LAB_TRAEFIK_LB_IP:?LAB_TRAEFIK_LB_IP is required from homelab.yml}/"
|
||||
|
||||
printf '\n-- local CRI containers\n'
|
||||
if command -v crictl >/dev/null 2>&1; then
|
||||
|
|
@ -5467,7 +5477,7 @@ EOF
|
|||
backstage_brain_note "doctor-cluster" <<EOF
|
||||
Doctor command: doctor-cluster
|
||||
Kubeconfig path: ${KUBECONFIG_PATH}
|
||||
Traefik LoadBalancer IP: ${LAB_TRAEFIK_LB_IP:-192.168.100.240}
|
||||
Traefik LoadBalancer IP: ${LAB_TRAEFIK_LB_IP:?LAB_TRAEFIK_LB_IP is required from homelab.yml}
|
||||
Pimox host: ${LAB_PIMOX_USER:-${TF_VAR_pimox_user:-jv}}@${LAB_PIMOX_HOST:-${TF_VAR_pimox_host:-192.168.100.80}}
|
||||
Worker storage: ${LAB_PIMOX_WORKER_STORAGE:-${TF_VAR_pimox_worker_storage:-opi5_ssd}}
|
||||
Known runbook: docs/runbooks/cluster-stop-start-failures.md
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Production-shaped inference demo for the portfolio site. The model is
|
|||
intentionally small: logistic regression coefficients trained with scikit-learn
|
||||
and exported to JSON so the runtime stays light enough for the homelab.
|
||||
|
||||
This directory contains the FastAPI service and model artifacts. It is not yet
|
||||
This lab directory contains the FastAPI service and model artifacts. It is not
|
||||
registered as a Kubernetes application in `bootstrap/apps`; the public website
|
||||
currently links to the reserved static placeholder under
|
||||
`apps/demos-static/public/mlops-platform/`.
|
||||
|
|
@ -24,7 +24,7 @@ currently links to the reserved static placeholder under
|
|||
## Local Smoke Test
|
||||
|
||||
```bash
|
||||
docker build -t mlops-platform:local apps/mlops-platform
|
||||
docker build -t mlops-platform:local labs/mlops-platform
|
||||
docker run --rm -p 8080:8080 mlops-platform:local
|
||||
```
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ from collections import Counter
|
|||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
DEFAULT_SOURCES_FILE = REPO_ROOT / "infra" / "ai" / "knowledge-sources.txt"
|
||||
DEFAULT_EXCLUDES_FILE = REPO_ROOT / "infra" / "ai" / "knowledge-excludes.txt"
|
||||
DEFAULT_INDEX_DIR = pathlib.Path(os.environ.get("LAB_AI_KNOWLEDGE_INDEX_DIR", "/data/homelab-ai/index"))
|
||||
TOKEN_RE = re.compile(r"[A-Za-z0-9_./:-]{2,}")
|
||||
MAX_FILE_BYTES = 512 * 1024
|
||||
|
|
@ -54,7 +55,11 @@ def path_is_secret(path: pathlib.Path) -> bool:
|
|||
return any(marker in lowered for marker in secret_markers)
|
||||
|
||||
|
||||
def candidate_files(patterns: list[str]) -> list[pathlib.Path]:
|
||||
def path_matches_any(rel_path: str, patterns: list[str]) -> bool:
|
||||
return any(fnmatch.fnmatch(rel_path, pattern) for pattern in patterns)
|
||||
|
||||
|
||||
def candidate_files(patterns: list[str], exclude_patterns: list[str]) -> list[pathlib.Path]:
|
||||
files: set[pathlib.Path] = set()
|
||||
all_files = [path for path in REPO_ROOT.rglob("*") if path.is_file()]
|
||||
|
||||
|
|
@ -69,7 +74,12 @@ def candidate_files(patterns: list[str]) -> list[pathlib.Path]:
|
|||
if path.is_file():
|
||||
files.add(path)
|
||||
|
||||
return sorted(path for path in files if not path_is_secret(path))
|
||||
return sorted(
|
||||
path
|
||||
for path in files
|
||||
if not path_is_secret(path)
|
||||
and not path_matches_any(path.relative_to(REPO_ROOT).as_posix(), exclude_patterns)
|
||||
)
|
||||
|
||||
|
||||
def read_text(path: pathlib.Path) -> str | None:
|
||||
|
|
@ -155,6 +165,7 @@ def build_index(files: list[pathlib.Path]) -> dict[str, object]:
|
|||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--sources-file", type=pathlib.Path, default=DEFAULT_SOURCES_FILE)
|
||||
parser.add_argument("--excludes-file", type=pathlib.Path, default=DEFAULT_EXCLUDES_FILE)
|
||||
parser.add_argument("--index-dir", type=pathlib.Path, default=DEFAULT_INDEX_DIR)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -163,7 +174,8 @@ def main() -> int:
|
|||
return 1
|
||||
|
||||
patterns = load_patterns(args.sources_file)
|
||||
files = candidate_files(patterns)
|
||||
exclude_patterns = load_patterns(args.excludes_file) if args.excludes_file.is_file() else []
|
||||
files = candidate_files(patterns, exclude_patterns)
|
||||
index = build_index(files)
|
||||
|
||||
args.index_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -174,6 +186,7 @@ def main() -> int:
|
|||
json.dumps(
|
||||
{
|
||||
"sources_file": str(args.sources_file.relative_to(REPO_ROOT)),
|
||||
"excludes_file": str(args.excludes_file.relative_to(REPO_ROOT)) if args.excludes_file.is_file() else None,
|
||||
"file_count": len(files),
|
||||
"chunk_count": index["chunk_count"],
|
||||
"repo_root": str(REPO_ROOT),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ worker_storage_warn = "worker storage" in lowered and "warn" in lowered
|
|||
non_system_control_plane = False
|
||||
control_plane_section = text.split("## control-plane", 1)[-1]
|
||||
for line in control_plane_section.splitlines():
|
||||
if re.match(r"\s*(argocd|website-production|demos-static|heimdall|arr-stack|security-lab|monitoring)\s+", line):
|
||||
if re.match(r"\s*(argocd|website-production|demos-static|heimdall|security-lab|monitoring)\s+", line):
|
||||
non_system_control_plane = True
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ EOF
|
|||
|
||||
is_read_only_command() {
|
||||
case "$1" in
|
||||
status|capacity|recover-plan|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|control-plane)
|
||||
status|capacity|capacity-advisor|capacity-limits|recover-plan|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|control-plane)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
|
|
|||
Loading…
Reference in New Issue