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