feat: record install-time state, show CircuitForge node info in quickinfo
- lib/cf-apps.functions: cf-provision-node/install-cf-app/ provision-orchard-node now write to ~/.config/cf-node-bootstrap/state.conf (record-cf-node-state) - provisioning profile, each installed app (name:install_type:clone_dir), and orchard role/coordinator URL. The orchard role/coordinator is read back from circuitforge-orch's own generated ~/.config/circuitforge/cf-orch.env rather than tracked separately, since that's the actual source of truth for what its interactive installer decided. - 11-quickinfo.bashrc: new "CircuitForge Node" section (show_cf_node_info, default true) displaying the provisioning profile, orchard role/ coordinator if joined, and each installed app with a best-effort running/stopped status. Docker-compose apps are checked by compose project from their clone dir rather than by container name, since compose's generated container names don't match the app name directly. Section is entirely absent if the state file doesn't exist yet (i.e. the CF Apps menu has never been run on this node).
This commit is contained in:
parent
2a325b55bd
commit
e33d6bb1ad
2 changed files with 136 additions and 0 deletions
|
|
@ -8,6 +8,23 @@
|
|||
|
||||
cf_apps_manifest_dir="${cf_apps_manifest_dir:-${rundir}/cf-apps}"
|
||||
|
||||
# State file recording what this installer has set up on this node, so
|
||||
# lib/skel/.bashrc.d/11-quickinfo.bashrc can report on it. Plain
|
||||
# sourceable key=value pairs (values %q-escaped), one write-or-replace
|
||||
# per key - not a general config format, just enough for quickinfo to read.
|
||||
cf_node_state_file="${cf_node_state_file:-${HOME}/.config/cf-node-bootstrap/state.conf}"
|
||||
|
||||
record-cf-node-state() {
|
||||
local key="$1" value="$2"
|
||||
mkdir -p "$(dirname "$cf_node_state_file")"
|
||||
touch "$cf_node_state_file"
|
||||
if [ -s "$cf_node_state_file" ]; then
|
||||
grep -v "^${key}=" "$cf_node_state_file" > "${cf_node_state_file}.tmp"
|
||||
mv "${cf_node_state_file}.tmp" "$cf_node_state_file"
|
||||
fi
|
||||
printf '%s=%q\n' "$key" "$value" >> "$cf_node_state_file"
|
||||
}
|
||||
|
||||
# Resets the app_* manifest variables/functions so sourcing one manifest
|
||||
# can't leak stale values into the next.
|
||||
_cf-apps-reset-manifest-vars() {
|
||||
|
|
@ -88,6 +105,7 @@ cf-provision-node() {
|
|||
if [ -z "$cf_provision_profile" ]; then
|
||||
select-provision-profile
|
||||
fi
|
||||
record-cf-node-state "cf_node_profile" "$cf_provision_profile"
|
||||
|
||||
if [[ "$cf_provision_profile" == "orchard" ]]; then
|
||||
provision-orchard-node
|
||||
|
|
@ -212,6 +230,8 @@ install-cf-app() {
|
|||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
record-cf-node-state "cf_app_${app_name//-/_}" "${app_name}:${install_type}:${clone_dir}"
|
||||
}
|
||||
|
||||
# Narrow, single-purpose flow for adding a GPU worker node to an existing
|
||||
|
|
@ -251,4 +271,20 @@ provision-orchard-node() {
|
|||
|
||||
boxline "Handing off to circuitforge-orch's own install.sh..."
|
||||
(cd "$clone_dir" && ./install.sh)
|
||||
|
||||
# circuitforge-orch's installer writes its own resulting config to
|
||||
# ~/.config/circuitforge/cf-orch.env (CFORCH_MODE, CFORCH_COORDINATOR_URL);
|
||||
# read that back rather than trying to track which topology the user
|
||||
# picked inside its interactive prompts.
|
||||
local orch_env_file="${HOME}/.config/circuitforge/cf-orch.env"
|
||||
if [ -f "$orch_env_file" ]; then
|
||||
local cforch_mode cforch_coordinator_url
|
||||
cforch_mode=$(grep -m1 '^CFORCH_MODE=' "$orch_env_file" | cut -d= -f2-)
|
||||
cforch_coordinator_url=$(grep -m1 '^CFORCH_COORDINATOR_URL=' "$orch_env_file" | cut -d= -f2-)
|
||||
record-cf-node-state "cf_orchard_role" "${cforch_mode:-unknown}"
|
||||
record-cf-node-state "cf_orchard_coordinator_url" "$cforch_coordinator_url"
|
||||
record-cf-node-state "cf_orchard_dir" "$clone_dir"
|
||||
else
|
||||
warn "Couldn't find ${orch_env_file} after install - orchard role won't show in quickinfo until it exists"
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ show_smart_status=false # Requires root privileges or sudo config
|
|||
show_top_processes=true
|
||||
show_power_info=true # Enable power monitoring (requires appropriate hardware/sensors)
|
||||
show_power_details=false
|
||||
show_cf_node_info=true # Show installed CircuitForge apps / orchard role (requires cf-node-bootstrap's install-time state file)
|
||||
|
||||
# Services to check (space separated list)
|
||||
critical_services="sshd nginx apache2 mysqld mariadb docker containerd kubelet cron"
|
||||
|
|
@ -578,6 +579,85 @@ get_container_info() {
|
|||
# fi
|
||||
}
|
||||
|
||||
################################
|
||||
# Get CircuitForge Node Info
|
||||
################################
|
||||
# Reads the state file cf-node-bootstrap's installer writes (see
|
||||
# lib/cf-apps.functions:record-cf-node-state) - not present unless that
|
||||
# installer's CF Apps menu has been run on this node at least once.
|
||||
|
||||
# Best-effort running/stopped check for a single installed app. Docker
|
||||
# compose-based apps are checked by compose project (from the clone dir),
|
||||
# not by container name, since compose's generated container names don't
|
||||
# match the app name directly. Single-container apps (no compose file
|
||||
# present) fall back to a name match against `docker ps`.
|
||||
_cf_node_app_status() {
|
||||
local dir="$1" install_type="$2" app_name="$3"
|
||||
if [[ "$install_type" != "docker" ]]; then
|
||||
echo "n/a"
|
||||
return
|
||||
fi
|
||||
command -v docker >/dev/null 2>&1 || { echo "n/a"; return; }
|
||||
|
||||
local compose_file=""
|
||||
for f in compose.yml docker-compose.yml; do
|
||||
if [ -f "${dir}/${f}" ]; then
|
||||
compose_file="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$compose_file" ]; then
|
||||
if (cd "$dir" && docker compose -f "$compose_file" ps -q 2>/dev/null | grep -q .); then
|
||||
echo "running"
|
||||
else
|
||||
echo "stopped"
|
||||
fi
|
||||
elif docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$app_name"; then
|
||||
echo "running"
|
||||
else
|
||||
echo "stopped"
|
||||
fi
|
||||
}
|
||||
|
||||
get_cf_node_info() {
|
||||
cf_node_info_available=false
|
||||
cf_node_profile_display="N/A"
|
||||
cf_node_apps_display=()
|
||||
cf_node_orchard_display=""
|
||||
|
||||
local state_file="${HOME}/.config/cf-node-bootstrap/state.conf"
|
||||
[ -f "$state_file" ] || return
|
||||
cf_node_info_available=true
|
||||
|
||||
# State file only defines cf_node_*/cf_app_*/cf_orchard_* keys (written by
|
||||
# record-cf-node-state), so sourcing it here won't clobber other quickinfo
|
||||
# variables.
|
||||
source "$state_file"
|
||||
|
||||
cf_node_profile_display="${cf_node_profile:-unknown}"
|
||||
|
||||
local var app_value app_name install_type clone_dir status status_disp
|
||||
for var in $(compgen -v cf_app_ 2>/dev/null); do
|
||||
app_value="${!var}"
|
||||
IFS=':' read -r app_name install_type clone_dir <<< "$app_value"
|
||||
status=$(_cf_node_app_status "$clone_dir" "$install_type" "$app_name")
|
||||
case "$status" in
|
||||
running) status_disp="${grn}running${dfl}" ;;
|
||||
stopped) status_disp="${lrd}stopped${dfl}" ;;
|
||||
*) status_disp="${gry}${install_type}${dfl}" ;;
|
||||
esac
|
||||
cf_node_apps_display+=("${app_name} ${gry}(${install_type})${dfl}: ${status_disp}")
|
||||
done
|
||||
|
||||
if [ -n "$cf_orchard_role" ]; then
|
||||
cf_node_orchard_display="${bld}Role:${dfl} ${ylw}${cf_orchard_role}${dfl}"
|
||||
if [ -n "$cf_orchard_coordinator_url" ]; then
|
||||
cf_node_orchard_display="${cf_node_orchard_display} | ${bld}Coordinator:${dfl} ${cyn}${cf_orchard_coordinator_url}${dfl}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Get public IP address using multiple fallbacks
|
||||
get_wan_ip() {
|
||||
wan_ip="Unavailable"
|
||||
|
|
@ -855,6 +935,10 @@ if [ "$show_container_info" = true ]; then
|
|||
get_container_info
|
||||
fi
|
||||
|
||||
if [ "$show_cf_node_info" = true ]; then
|
||||
get_cf_node_info
|
||||
fi
|
||||
|
||||
if [ "$show_service_status" = true ]; then
|
||||
get_service_status
|
||||
fi
|
||||
|
|
@ -951,6 +1035,22 @@ if [ "$show_container_info" = true ] && [[ "$in_container" = true || "$have_cont
|
|||
fi
|
||||
fi
|
||||
|
||||
# CircuitForge node information
|
||||
if [ "$show_cf_node_info" = true ] && [ "$cf_node_info_available" = true ]; then
|
||||
boxline ""
|
||||
boxline "${bld}${unl}CircuitForge Node:${dfl}"
|
||||
boxline " ${bld}Profile:${dfl} ${mag}${cf_node_profile_display}${dfl}"
|
||||
if [ -n "$cf_node_orchard_display" ]; then
|
||||
boxline " ${bld}Orchard:${dfl} ${cf_node_orchard_display}"
|
||||
fi
|
||||
if [ "${#cf_node_apps_display[@]}" -gt 0 ]; then
|
||||
boxline " ${bld}Installed apps:${dfl}"
|
||||
for app_line in "${cf_node_apps_display[@]}"; do
|
||||
boxline " ${app_line}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Network information
|
||||
boxline ""
|
||||
boxline "${bld}${unl}Network Information:${dfl}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue