Skip to content

Commit 5791706

Browse files
committed
extend and refactor dev mode
1 parent 2e79cec commit 5791706

3 files changed

Lines changed: 198 additions & 97 deletions

File tree

shared/build.func

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,30 @@ _cs_remote_url() {
144144
fi
145145
}
146146

147+
# dev_mode=net records every engine fetch. Read from $dev_mode directly, not
148+
# from DEV_MODE_NET: parse_dev_mode runs much later, and the first fetches --
149+
# the ones worth seeing -- happen before it.
150+
_cs_net_log() {
151+
[[ ",${dev_mode:-}," == *,net,* ]] || return 0
152+
printf ' [net] %-3s %6s ms %s
153+
' "$1" "$2" "$3" >&2
154+
}
155+
147156
_cs_download() {
148157
local url="${1:?url}"
158+
local t0 code
159+
if [[ ",${dev_mode:-}," == *,net,* ]] && command -v curl >/dev/null 2>&1; then
160+
t0=${EPOCHREALTIME/./}
161+
local body
162+
body="$(curl -fsSL -w '
163+
%{http_code}' "$url" 2>/dev/null)"
164+
code="${body##*$'
165+
'}"
166+
_cs_net_log "$code" "$((( ${EPOCHREALTIME/./} - t0 ) / 1000))" "$url"
167+
printf '%s' "${body%$'
168+
'*}"
169+
return 0
170+
fi
149171
if command -v curl >/dev/null 2>&1; then
150172
curl -fsSL "$url"
151173
else
@@ -226,6 +248,7 @@ _cs_prefetch_engine() {
226248
done
227249
[[ -z "$dir" ]] && return 1
228250

251+
local _cs_t0=${EPOCHREALTIME/./}
229252
local args=() rel
230253
for rel in "${_CS_ENGINE_FILES[@]}"; do
231254
mkdir -p "${dir}/${rel%/*}" 2>/dev/null || true
@@ -236,14 +259,16 @@ _cs_prefetch_engine() {
236259
# sourced as shell.
237260
curl -fsSL --parallel --parallel-immediate --parallel-max 16 --connect-timeout 10 --max-time 60 "${args[@]}" 2>/dev/null
238261

239-
local kept=0
262+
local kept=0 missing=0
240263
for rel in "${_CS_ENGINE_FILES[@]}"; do
241264
if [[ -s "${dir}/${rel}" ]]; then
242265
kept=$((kept + 1))
243266
else
244267
rm -f "${dir}/${rel}" 2>/dev/null
268+
missing=$((missing + 1))
245269
fi
246270
done
271+
_cs_net_log "200" "$(( ( ${EPOCHREALTIME/./} - _cs_t0 ) / 1000 ))" "prefetch: ${kept} files, ${missing} missing -> ${dir}"
247272
if ((kept == 0)); then
248273
rm -rf "$dir" 2>/dev/null
249274
return 1

shared/core.func

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,8 @@ msg_info() {
759759
[[ -n "${MSG_INFO_SHOWN["$sanitized_key"]+x}" ]] && return
760760
MSG_INFO_SHOWN["$sanitized_key"]=1
761761

762+
_dev_step_start "$msg"
763+
762764
# Log to file
763765
log_msg "[INFO] $msg"
764766

@@ -804,7 +806,7 @@ msg_ok() {
804806
[[ -z "$msg" ]] && return
805807
stop_spinner
806808
clear_line
807-
echo -e "$CM${GN}${msg}${CL}"
809+
echo -e "$CM${GN}${msg}${CL}$(_dev_step_end "$msg")"
808810
log_msg "[OK] $msg"
809811
local sanitized_key
810812
sanitized_key=$(printf '%s' "$msg" | sed 's/\x1b\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')

shared/dev-mode.func

Lines changed: 169 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -7,140 +7,214 @@
77
# ==============================================================================
88
# Opt-in debugging, driven by a comma-separated dev_mode variable:
99
#
10-
# motd set up MOTD/SSH in a kept container so it can be inspected
11-
# keep never delete the container when a build fails
10+
# net log every engine fetch: HTTP status, duration, URL
11+
# timing show how long each step took, and the slowest ones at the end
1212
# trace bash set -x tracing
13-
# pause pause after each msg_info step
13+
# pause wait for a keypress after each step
14+
# keep never delete the container when a build fails
1415
# breakpoint open a shell on error instead of cleaning up
16+
# motd set up MOTD/SSH early so a kept container can be inspected
1517
# logs persist all logs to /var/log/community-scripts/
16-
# dryrun print commands instead of running them
1718
#
18-
# Parsing and the interactive picker live here; the flags themselves are read at
19-
# the point of use, in the backends and the messaging helpers.
19+
# Any of them also prints the resolved context up front: where the engine came
20+
# from, which roots and URLs are in play, and what the host looks like.
21+
#
22+
# Parsing, the context block and the timing bookkeeping live here. The flags
23+
# themselves are read at the point of use, in the backends and the messaging
24+
# helpers -- except net, which build.func reads straight from $dev_mode,
25+
# because the fetches worth seeing happen before this file is even loaded.
2026
# ==============================================================================
2127

2228
[[ -n "${_DEV_MODE_FUNC_LOADED:-}" ]] && return
2329
_DEV_MODE_FUNC_LOADED=1
2430

31+
_DEV_MODE_FLAGS=(net timing trace pause keep breakpoint motd logs)
32+
2533
# ------------------------------------------------------------------------------
2634
# parse_dev_mode()
2735
#
28-
# - Parses comma-separated dev_mode variable (e.g., "motd,keep,trace")
29-
# - Sets global flags for each mode:
30-
# * DEV_MODE_MOTD: Setup SSH/MOTD before installation
31-
# * DEV_MODE_KEEP: Never delete container on failure
32-
# * DEV_MODE_TRACE: Enable bash set -x tracing
33-
# * DEV_MODE_PAUSE: Pause after each msg_info step
34-
# * DEV_MODE_BREAKPOINT: Open shell on error instead of cleanup
35-
# * DEV_MODE_LOGS: Persist all logs to /var/log/community-scripts/
36-
# * DEV_MODE_DRYRUN: Show commands without executing
37-
# - Call this early in script execution
36+
# - Parses the comma-separated dev_mode variable (e.g. "net,timing")
37+
# - Exports one DEV_MODE_<FLAG> per known flag, true or false
38+
# - Names the known flags when one is unrecognised: a typo would otherwise be
39+
# indistinguishable from a mode that simply does nothing
3840
# ------------------------------------------------------------------------------
3941
parse_dev_mode() {
40-
local mode
41-
# Initialize all flags to false
42-
export DEV_MODE_MOTD=false
43-
export DEV_MODE_KEEP=false
44-
export DEV_MODE_TRACE=false
45-
export DEV_MODE_PAUSE=false
46-
export DEV_MODE_BREAKPOINT=false
47-
export DEV_MODE_LOGS=false
48-
export DEV_MODE_DRYRUN=false
49-
50-
# Parse comma-separated modes
51-
if [[ -n "${dev_mode:-}" ]]; then
52-
IFS=',' read -ra MODES <<<"$dev_mode"
53-
for mode in "${MODES[@]}"; do
54-
mode="$(echo "$mode" | xargs)" # Trim whitespace
55-
case "$mode" in
56-
motd) export DEV_MODE_MOTD=true ;;
57-
keep) export DEV_MODE_KEEP=true ;;
58-
trace) export DEV_MODE_TRACE=true ;;
59-
pause) export DEV_MODE_PAUSE=true ;;
60-
breakpoint) export DEV_MODE_BREAKPOINT=true ;;
61-
logs) export DEV_MODE_LOGS=true ;;
62-
dryrun) export DEV_MODE_DRYRUN=true ;;
63-
*)
64-
if declare -f msg_warn >/dev/null 2>&1; then
65-
msg_warn "Unknown dev_mode: '$mode' (ignored)"
66-
else
67-
echo "[WARN] Unknown dev_mode: '$mode' (ignored)" >&2
68-
fi
69-
;;
70-
esac
71-
done
72-
73-
# Show active dev modes
74-
local active_modes=()
75-
[[ $DEV_MODE_MOTD == true ]] && active_modes+=("motd")
76-
[[ $DEV_MODE_KEEP == true ]] && active_modes+=("keep")
77-
[[ $DEV_MODE_TRACE == true ]] && active_modes+=("trace")
78-
[[ $DEV_MODE_PAUSE == true ]] && active_modes+=("pause")
79-
[[ $DEV_MODE_BREAKPOINT == true ]] && active_modes+=("breakpoint")
80-
[[ $DEV_MODE_LOGS == true ]] && active_modes+=("logs")
81-
[[ $DEV_MODE_DRYRUN == true ]] && active_modes+=("dryrun")
82-
83-
if [[ ${#active_modes[@]} -gt 0 ]]; then
84-
if declare -f msg_custom >/dev/null 2>&1; then
85-
msg_custom "🔧" "${YWB}" "Dev modes active: ${active_modes[*]}"
42+
local flag mode
43+
for flag in "${_DEV_MODE_FLAGS[@]}"; do
44+
export "DEV_MODE_${flag^^}=false"
45+
done
46+
47+
[[ -z "${dev_mode:-}" ]] && return 0
48+
49+
local active=()
50+
IFS=',' read -ra _dev_modes <<<"$dev_mode"
51+
for mode in "${_dev_modes[@]}"; do
52+
mode="${mode//[[:space:]]/}"
53+
[[ -z "$mode" ]] && continue
54+
if [[ " ${_DEV_MODE_FLAGS[*]} " == *" $mode "* ]]; then
55+
export "DEV_MODE_${mode^^}=true"
56+
active+=("$mode")
57+
else
58+
if declare -f msg_warn >/dev/null 2>&1; then
59+
msg_warn "Unknown dev_mode '$mode' — known: ${_DEV_MODE_FLAGS[*]}"
8660
else
87-
echo "[DEV] Active modes: ${active_modes[*]}" >&2
61+
echo "[WARN] Unknown dev_mode '$mode' — known: ${_DEV_MODE_FLAGS[*]}" >&2
8862
fi
8963
fi
64+
done
65+
66+
((${#active[@]})) || return 0
67+
if declare -f msg_custom >/dev/null 2>&1; then
68+
msg_custom "🔧" "${YWB:-}" "Dev mode: ${active[*]}"
69+
else
70+
echo "[DEV] ${active[*]}" >&2
9071
fi
72+
dev_mode_context
9173
}
9274

93-
dev_mode_menu() {
94-
local motd=OFF keep=OFF trace=OFF pause=OFF breakpoint=OFF logs=OFF dryrun=OFF verbose=OFF
95-
96-
IFS=',' read -r -a _modes <<<"$dev_mode"
97-
for m in "${_modes[@]}"; do
98-
case "$m" in
99-
motd) motd=ON ;;
100-
keep) keep=ON ;;
101-
trace) trace=ON ;;
102-
pause) pause=ON ;;
103-
breakpoint) breakpoint=ON ;;
104-
logs) logs=ON ;;
105-
dryrun) dryrun=ON ;;
75+
# ------------------------------------------------------------------------------
76+
# dev_mode_context()
77+
#
78+
# - Prints where the engine and the scripts were resolved from, and the host
79+
# facts every other flag's output has to be read against
80+
# ------------------------------------------------------------------------------
81+
dev_mode_context() {
82+
local engine="network (${COMMUNITY_SCRIPTS_CORE_URL:-unset})"
83+
if [[ -n "${COMMUNITY_SCRIPTS_CORE_DIR:-}" ]]; then
84+
case "$COMMUNITY_SCRIPTS_CORE_DIR" in
85+
*/cs-engine.*) engine="prefetched → ${COMMUNITY_SCRIPTS_CORE_DIR}" ;;
86+
*) engine="local checkout → ${COMMUNITY_SCRIPTS_CORE_DIR}" ;;
10687
esac
88+
fi
89+
90+
{
91+
echo " ── dev context ─────────────────────────────────────────────"
92+
printf ' %-14s %s\n' "engine" "$engine"
93+
printf ' %-14s %s\n' "engine url" "${COMMUNITY_SCRIPTS_CORE_URL:-unset}"
94+
printf ' %-14s %s\n' "scripts root" "${COMMUNITY_SCRIPTS_ROOT:-none (remote)}"
95+
printf ' %-14s %s\n' "scripts url" "${COMMUNITY_SCRIPTS_URL:-unset}"
96+
printf ' %-14s %s\n' "app" "${APP:-?} (${NSAPP:-?}, type ${APP_TYPE:-?})"
97+
printf ' %-14s %s\n' "platform" "$(declare -f detect_lxc_platform >/dev/null 2>&1 && detect_lxc_platform || echo unknown)"
98+
printf ' %-14s %s\n' "arch" "$(dpkg --print-architecture 2>/dev/null || uname -m)"
99+
if command -v pveversion >/dev/null 2>&1; then
100+
printf ' %-14s %s\n' "pve" "$(declare -f _pve_version >/dev/null 2>&1 && _pve_version || pveversion)"
101+
fi
102+
printf ' %-14s %s\n' "bash" "${BASH_VERSION%%(*}"
103+
printf ' %-14s %s\n' "curl" "$(curl --version 2>/dev/null | head -1 | awk '{print $2}' || echo absent)"
104+
printf ' %-14s %s\n' "session" "${SESSION_ID:-none}"
105+
echo " ────────────────────────────────────────────────────────────"
106+
} >&2
107+
}
108+
109+
# ------------------------------------------------------------------------------
110+
# _dev_step_start() / _dev_step_end()
111+
#
112+
# - Called from msg_info and msg_ok; no-ops unless dev_mode=timing
113+
# - _dev_step_end returns a suffix for the msg_ok line, so the elapsed time
114+
# lands on the step it belongs to instead of in a separate stream
115+
# - State goes in a file: msg_ok is often reached inside $( ), where a variable
116+
# assignment would be discarded on return
117+
# ------------------------------------------------------------------------------
118+
_dev_timing_file() {
119+
local base
120+
for base in /dev/shm "${TMPDIR:-/tmp}" /tmp; do
121+
[[ -d "$base" && -w "$base" ]] && {
122+
printf '%s/cs-timing.%s' "$base" "$$"
123+
return 0
124+
}
107125
done
126+
return 1
127+
}
128+
129+
_dev_step_key() {
130+
printf '%s' "$1" | sed 's/\x1b\[[0-9;]*m//g; s/[^a-zA-Z0-9]/_/g'
131+
}
132+
133+
_dev_step_start() {
134+
[[ "${DEV_MODE_TIMING:-false}" == "true" ]] || return 0
135+
local f
136+
f="$(_dev_timing_file)" || return 0
137+
printf 'S %s %s\n' "${EPOCHREALTIME/./}" "$(_dev_step_key "$1")" >>"$f"
138+
}
139+
140+
_dev_step_end() {
141+
[[ "${DEV_MODE_TIMING:-false}" == "true" ]] || return 0
142+
local f key start ms
143+
f="$(_dev_timing_file)" || return 0
144+
key="$(_dev_step_key "$1")"
145+
start="$(awk -v k="$key" '$1=="S" && $3==k {t=$2} END {print t}' "$f" 2>/dev/null)"
146+
[[ -z "$start" ]] && return 0
147+
ms=$(((${EPOCHREALTIME/./} - start) / 1000))
148+
printf 'E %s %s\n' "$ms" "$key" >>"$f"
149+
printf ' %s(%s ms)%s' "${DGN:-}" "$ms" "${CL:-}"
150+
}
151+
152+
# ------------------------------------------------------------------------------
153+
# dev_mode_timing_summary()
154+
#
155+
# - Prints the slowest steps of the run; called from the exit path
156+
# ------------------------------------------------------------------------------
157+
dev_mode_timing_summary() {
158+
[[ "${DEV_MODE_TIMING:-false}" == "true" ]] || return 0
159+
local f
160+
f="$(_dev_timing_file)" || return 0
161+
[[ -s "$f" ]] || return 0
162+
{
163+
echo " ── slowest steps ───────────────────────────────────────────"
164+
awk '$1=="E" {printf " %8s ms %s\n", $2, $3}' "$f" | sort -rn | head -10
165+
echo " ────────────────────────────────────────────────────────────"
166+
} >&2
167+
rm -f "$f"
168+
}
108169

109-
[[ "$var_verbose" == "yes" ]] && verbose=ON
170+
# ------------------------------------------------------------------------------
171+
# dev_mode_menu()
172+
#
173+
# - Whiptail picker for the same flags, plus verbose
174+
# ------------------------------------------------------------------------------
175+
dev_mode_menu() {
176+
local flag state items=() selection
177+
local -A labels=(
178+
[net]="Log every engine fetch"
179+
[timing]="Time each step"
180+
[trace]="Bash command tracing"
181+
[pause]="Step-by-step execution"
182+
[keep]="Preserve container on failure"
183+
[breakpoint]="Interactive shell on error"
184+
[motd]="Early SSH/MOTD setup"
185+
[logs]="Persistent logging"
186+
)
187+
for flag in "${_DEV_MODE_FLAGS[@]}"; do
188+
state=OFF
189+
[[ ",${dev_mode:-}," == *",${flag},"* ]] && state=ON
190+
items+=("$flag" "${labels[$flag]}" "$state")
191+
done
192+
items+=("verbose" "Verbose logging" "$([[ "${var_verbose:-no}" == "yes" ]] && echo ON || echo OFF)")
110193

111-
local selection
112194
selection=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
113195
--title "DEV MODE" \
114-
--checklist "Choose one or more Options" 16 51 10 \
115-
"motd" "Early SSH/MOTD Setup" "$motd" \
116-
"keep" "Preserve Container on Failure" "$keep" \
117-
"trace" "Bash Command Tracing" "$trace" \
118-
"pause" "Step-by-Step Execution" "$pause" \
119-
"breakpoint" "Interactive Shell on Error" "$breakpoint" \
120-
"logs" "Persistent Logging" "$logs" \
121-
"dryrun" "Simulation Mode" "$dryrun" \
122-
"verbose" "Verbose logging" "$verbose" \
196+
--checklist "Choose one or more options" 17 56 10 \
197+
"${items[@]}" \
123198
3>&1 1>&2 2>&3) || exit_script
124199

125200
dev_mode=""
126201
var_verbose="no"
127-
local modes_out=()
128-
202+
local picked=() tag
129203
for tag in $selection; do
130204
tag="${tag%\"}"
131205
tag="${tag#\"}"
132206
if [[ "$tag" == "verbose" ]]; then
133207
var_verbose="yes"
134208
else
135-
modes_out+=("$tag")
209+
picked+=("$tag")
136210
fi
137211
done
138-
139-
dev_mode=$(
212+
dev_mode="$(
140213
IFS=,
141-
echo "${modes_out[*]}"
142-
)
143-
unset DEV_MODE_MOTD DEV_MODE_KEEP DEV_MODE_TRACE DEV_MODE_PAUSE DEV_MODE_BREAKPOINT DEV_MODE_LOGS DEV_MODE_DRYRUN
214+
echo "${picked[*]}"
215+
)"
216+
export dev_mode
217+
144218
parse_dev_mode
145219
if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then
146220
mkdir -p /var/log/community-scripts

0 commit comments

Comments
 (0)