Compare commits

..

No commits in common. "4405c76003c7d0cd5f3a7a674559631519b445aa" and "b860b44a6d5ce77f12758bf31416cf240ad222af" have entirely different histories.

6 changed files with 74 additions and 2440 deletions

30
jeannie
View File

@ -2,39 +2,11 @@
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILDX_CONFIG="/tmp/buildx-config.toml"
HOMELAB_STATE_DIR="${HOMELAB_STATE_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/homelab}"
COSIGN_KEY_PREFIX="${COSIGN_KEY_PREFIX:-${HOMELAB_STATE_DIR}/cosign/cosign}"
COSIGN_KEY_PATH="${COSIGN_KEY_PATH:-${COSIGN_KEY_PREFIX}.key}"
COSIGN_PUBLIC_KEY_PATH="${COSIGN_PUBLIC_KEY_PATH:-${COSIGN_KEY_PREFIX}.pub}"
COSIGN_PASSWORD_FILE="${COSIGN_PASSWORD_FILE:-${HOMELAB_STATE_DIR}/cosign/cosign.password}"
COSIGN_VERSION="${COSIGN_VERSION:-2.6.3}"
COSIGN_BIN="${COSIGN_BIN:-}"
HOMELAB_COSIGN_PUBLIC_KEY_CONFIGMAP="${HOMELAB_COSIGN_PUBLIC_KEY_CONFIGMAP:-homelab-cosign-public-key}"
HOMELAB_SBOM_PREDICATE_TYPE="${HOMELAB_SBOM_PREDICATE_TYPE:-https://spdx.dev/Document}"
JEANNIE_LOG_DIR="${JEANNIE_LOG_DIR:-${HOMELAB_STATE_DIR}/logs}"
JEANNIE_LOG_FILE="${JEANNIE_LOG_FILE:-}"
JEANNIE_ERROR_LOG_FILE="${JEANNIE_ERROR_LOG_FILE:-}"
JEANNIE_STEP_INDEX=0
JEANNIE_STEP_TOTAL=0
# shellcheck disable=SC1091
source "${REPO_ROOT}/scripts/report-ui"
source "${REPO_ROOT}/lib/jeannie/core.sh"
source "${REPO_ROOT}/lib/jeannie/env.sh"
source "${REPO_ROOT}/lib/jeannie/toolchain.sh"
source "${REPO_ROOT}/lib/jeannie/pimox.sh"
source "${REPO_ROOT}/lib/jeannie/preflight.sh"
source "${REPO_ROOT}/lib/jeannie/pipeline.sh"
source "${REPO_ROOT}/lib/jeannie/security.sh"
# shellcheck disable=SC1091
source "${REPO_ROOT}/lib/jeannie/commands.sh"
trap 'rm -f "${BUILDX_CONFIG}"' EXIT
load_homelab_inventory_defaults
export_homelab_inventory_tf_vars
KUBECONFIG_PATH="${KUBECONFIG_PATH:-${TF_VAR_kubeconfig_path:-${LAB_KUBECONFIG_PATH:-/home/jv/.kube/config}}}"
print_usage() {
cat <<'EOF'
@ -396,7 +368,7 @@ case "${1:-}" in
doctor_cluster "${@:2}"
;;
preflight)
homelab_preflight "full"
homelab_preflight \"full\"
;;
doctor-preapply)
doctor_preapply

View File

@ -1,5 +1,4 @@
#!/usr/bin/env bash
# shellcheck disable=SC2140
pimox_ssh() {
local host="$1"
@ -494,7 +493,7 @@ cluster_control_plane_tracked() {
}
cluster_admin_kubeconfig_present() {
[[ -s "${KUBECONFIG_PATH}" ]] ||
[[ -s "${KUBECONFIG_PATH_PATH}" ]] ||
sudo test -s /etc/kubernetes/admin.conf 2>/dev/null ||
[[ -n "${KUBECONFIG_PATH:-}" && -s "${KUBECONFIG_PATH}" ]]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034,SC2043,SC2140
load_homelab_inventory_defaults() {
local inventory_file="${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}"
@ -27,7 +26,7 @@ mapping = {
"hosts.debian.lan_ip": "LAB_DEBIAN_LAN_IP",
"hosts.debian.tailscale_ip": "LAB_DEBIAN_TAILSCALE_IP",
"hosts.debian.docker_root": "LAB_DEBIAN_DOCKER_ROOT",
"hosts.debian.kubeconfig": "LAB_KUBECONFIG_PATH",
"hosts.debian.kubeconfig": "LAB_KUBECONFIG_PATH_PATH",
"hosts.rpi4.user": "LAB_RPI_USER",
"hosts.rpi4.lan_ip": "LAB_RPI_HOST",
"hosts.rpi4.tailscale_ip": "LAB_RPI_TAILSCALE_IP",
@ -158,7 +157,7 @@ export_if_unset() {
}
export_homelab_inventory_tf_vars() {
export_if_unset TF_VAR_kubeconfig_path "${LAB_KUBECONFIG_PATH:-}"
export_if_unset TF_VAR_kubeconfig_path "${LAB_KUBECONFIG_PATH_PATH:-}"
export_if_unset TF_VAR_control_plane_endpoint "${LAB_DEBIAN_LAN_IP:-}"
export_if_unset TF_VAR_registry_endpoint "${LAB_REGISTRY_ENDPOINT:-}"
export_if_unset TF_VAR_provisioning_host "${LAB_DEBIAN_LAN_IP:-}"
@ -217,7 +216,7 @@ helm_release_secret_exists() {
local release_name="$2"
local secret_name
secret_name="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" -n "${namespace}" get secrets \
secret_name="$(kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" -n "${namespace}" get secrets \
-l "owner=helm,name=${release_name}" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)"
@ -230,11 +229,11 @@ kubernetes_resource_exists() {
local resource_name="$3"
if [[ -n "${namespace}" ]]; then
kubectl --kubeconfig "${KUBECONFIG_PATH}" -n "${namespace}" get "${resource_kind}" "${resource_name}" >/dev/null 2>&1
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" -n "${namespace}" get "${resource_kind}" "${resource_name}" >/dev/null 2>&1
return $?
fi
kubectl --kubeconfig "${KUBECONFIG_PATH}" get "${resource_kind}" "${resource_name}" >/dev/null 2>&1
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get "${resource_kind}" "${resource_name}" >/dev/null 2>&1
}
adopt_tofu_helm_release() {
@ -406,7 +405,7 @@ ensure_homelab_node_labels() {
[[ -n "${node}" ]] || continue
if [[ "${node}" == "${control_plane_node}" ]]; then
kubectl --kubeconfig "${KUBECONFIG_PATH}" label node "${node}" \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" label node "${node}" \
homelab.dev/node-role=control-plane \
homelab.dev/storage=local \
homelab.dev/workload-class=control-plane \
@ -414,26 +413,26 @@ ensure_homelab_node_labels() {
continue
fi
kubectl --kubeconfig "${KUBECONFIG_PATH}" label node "${node}" \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" label node "${node}" \
node-role.kubernetes.io/worker=worker \
--overwrite
if [[ "${node}" == pimox-worker-* ]]; then
kubectl --kubeconfig "${KUBECONFIG_PATH}" label node "${node}" \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" label node "${node}" \
homelab.dev/node-role=app \
homelab.dev/storage=ssd \
homelab.dev/workload-class=platform \
--overwrite
elif [[ "${node}" == "${raspberry_node}" ]]; then
kubectl --kubeconfig "${KUBECONFIG_PATH}" label node "${node}" \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" label node "${node}" \
homelab.dev/node-role=edge-app \
homelab.dev/storage=local \
homelab.dev/workload-class=edge \
--overwrite
fi
done < <(kubectl --kubeconfig "${KUBECONFIG_PATH}" get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
done < <(kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
target_nodes="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" get nodes -l "${prometheus_selector}" -o name)"
target_nodes="$(kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get nodes -l "${prometheus_selector}" -o name)"
if [[ -z "${target_nodes}" ]]; then
echo "No nodes match ${prometheus_selector}; refusing to move prometheus-stack." >&2
exit 1
@ -446,22 +445,22 @@ delete_prometheus_stack_storage() {
local pvc_names
local pv_names
pvc_names="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" -n "${namespace}" get pvc -o name 2>/dev/null |
pvc_names="$(kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" -n "${namespace}" get pvc -o name 2>/dev/null |
awk -F/ -v pattern="${pattern}" '$2 ~ pattern {print $2}')"
pv_names="$(kubectl --kubeconfig "${KUBECONFIG_PATH}" get pv \
pv_names="$(kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get pv \
-o jsonpath='{range .items[?(@.spec.claimRef.namespace=="'"${namespace}"'")]}{.metadata.name}{"\t"}{.spec.claimRef.name}{"\n"}{end}' 2>/dev/null |
awk -v pattern="${pattern}" '$2 ~ pattern {print $1}')"
if [[ -n "${pvc_names}" ]]; then
echo "Deleting old prometheus-stack PVCs in ${namespace}; saved Prometheus, Alertmanager, and Grafana data will be discarded..."
printf '%s\n' "${pvc_names}" |
xargs -r kubectl --kubeconfig "${KUBECONFIG_PATH}" -n "${namespace}" delete pvc --wait=true --timeout=180s
xargs -r kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" -n "${namespace}" delete pvc --wait=true --timeout=180s
fi
if [[ -n "${pv_names}" ]]; then
echo "Deleting old prometheus-stack retained PV objects..."
printf '%s\n' "${pv_names}" |
xargs -r kubectl --kubeconfig "${KUBECONFIG_PATH}" delete pv --wait=false
xargs -r kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" delete pv --wait=false
fi
}
@ -580,7 +579,7 @@ EOF
exit 1
fi
export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH}}"
export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH_PATH}}"
export KUBECONFIG_PATH="${TF_VAR_kubeconfig_path}"
echo "Moving prometheus-stack off the control plane. Existing prometheus-stack PVC data will be deleted."
@ -590,7 +589,7 @@ EOF
tofu -chdir="${REPO_ROOT}/${stack}" destroy -target=helm_release.prometheus_stack "${approve_args[@]}"
delete_prometheus_stack_storage "${namespace}"
tofu -chdir="${REPO_ROOT}/${stack}" apply "${approve_args[@]}"
kubectl --kubeconfig "${KUBECONFIG_PATH}" -n "${namespace}" get pods -o wide
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" -n "${namespace}" get pods -o wide
}
doctor_versions_report() {
@ -605,12 +604,12 @@ doctor_versions_report() {
ensure_python3
api_version="$(
kubectl --kubeconfig "${KUBECONFIG_PATH}" version -o json |
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" version -o json |
python3 -c 'import json, sys; print(json.load(sys.stdin)["serverVersion"]["gitVersion"])'
)"
api_minor="$(printf '%s\n' "${api_version}" | awk -F. '{gsub(/^v/, "", $1); print $1 "." $2}')"
node_rows="$(
kubectl --kubeconfig "${KUBECONFIG_PATH}" get nodes \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get nodes \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'
)"
@ -691,12 +690,12 @@ doctor_versions_compact() {
fi
api_version="$(
kubectl --kubeconfig "${KUBECONFIG_PATH}" version -o json |
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" version -o json |
python3 -c 'import json, sys; print(json.load(sys.stdin)["serverVersion"]["gitVersion"])'
)"
api_minor="$(printf '%s\n' "${api_version}" | awk -F. '{gsub(/^v/, "", $1); print $1 "." $2}')"
node_rows="$(
kubectl --kubeconfig "${KUBECONFIG_PATH}" get nodes \
kubectl --kubeconfig "${KUBECONFIG_PATH_PATH}" get nodes \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'
)"
@ -1569,6 +1568,10 @@ fi
sudo taskset -c \"\$cpuset\" bash \"\$start_script\""
}
cleanup() {
rm -f \"\$start_script\"
}
pimox_shutdown_vm_gracefully() {
local host="$1"
local user="$2"
@ -2910,6 +2913,16 @@ fi
EOF
}
cleanup() {
if mountpoint -q "\$mnt_dir" 2>/dev/null; then
sudo umount "\$mnt_dir" || sudo umount -l "\$mnt_dir" || true
fi
if [ -n "\$loopdev" ]; then
sudo losetup -d "\$loopdev" >/dev/null 2>&1 || true
fi
rm -rf "\$tmp_dir"
}
openwrt() {
require_debian_server "openwrt"
@ -3753,6 +3766,38 @@ sudo docker compose ps
install_gitea_backup_timer
}
install_missing_packages() {
missing_packages=''
for package in \"\$@\"; do
if ! dpkg-query -W -f='\${Status}' \"\$package\" 2>/dev/null | grep -q 'install ok installed'; then
missing_packages=\"\$missing_packages \$package\"
fi
done
if [ -n \"\$missing_packages\" ]; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends \$missing_packages
fi
}
repair_docker_iptables() {
if sudo iptables -t nat -S DOCKER >/dev/null 2>&1; then
return 0
fi
echo 'Docker NAT chain is missing on the Gitea host; restarting Docker once to restore iptables state...'
sudo systemctl restart docker
sleep 3
if sudo iptables -t nat -S DOCKER >/dev/null 2>&1; then
return 0
fi
echo 'Docker NAT chain is still missing after restarting Docker.' >&2
sudo iptables -t nat -S >&2 || true
sudo systemctl status docker --no-pager -l >&2 || true
exit 1
}
install_missing_debian_packages() {
local missing_packages=()
local package
@ -5239,7 +5284,7 @@ apps() {
website_image_ref="${registry_endpoint}/php-website:${WEBSITE_IMAGE_TAG:-$(website_image_tag "${website_source_hash}")}"
export TF_VAR_registry_endpoint="${TF_VAR_registry_endpoint:-${registry_endpoint}}"
export TF_VAR_website_image_ref="${TF_VAR_website_image_ref:-${website_image_ref}}"
export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH}}"
export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH_PATH}}"
export KUBECONFIG_PATH="${TF_VAR_kubeconfig_path}"
require_argocd_application_controller
@ -5659,15 +5704,6 @@ PY
return "${status}"
}
graphiphy_map() {
if ! command -v graphiphy >/dev/null 2>&1; then
echo "graphiphy is not installed; skipping repo map generation."
return 0
fi
graphiphy "${REPO_ROOT}"
}
up_task() {
require_debian_server "__up-task"

View File

@ -122,11 +122,8 @@ main() {
# 2. Render Modular Docs
for tmpl in "${TEMPLATES_DIR}"/*.tmpl; do
[[ -e "$tmpl" ]] || continue
local filename
local output_file
filename=$(basename "$tmpl" .md.tmpl)
output_file="${DOCS_DIR}/${filename}.md"
local filename=$(basename "$tmpl" .md.tmpl)
local output_file="${DOCS_DIR}/${filename}.md"
if [[ "${mode}" == "check" ]]; then
rendered_tmp=$(mktemp)

View File

@ -22,9 +22,7 @@ run_test() {
}
# Source modules to be tested
# shellcheck disable=SC1091
source /home/jv/my-homelab-configs/lib/jeannie/core.sh
# shellcheck disable=SC1091
source /home/jv/my-homelab-configs/lib/jeannie/env.sh
# Test Core: truthy (returns 0 for true, 1 for false)