Merge claude/1
This commit is contained in:
commit
0c4c607282
@ -37,7 +37,31 @@ cliHandleAppCommands()
|
||||
;;
|
||||
|
||||
"install")
|
||||
dockerInstallApp "$app_name" "$config" "$reset_network"
|
||||
# Two paths in / out:
|
||||
# * The task processor's recursive invocation carries
|
||||
# LIBREPORTAL_TASK_EXEC=1 — run the install inline so it
|
||||
# actually does the work (otherwise we'd loop forever
|
||||
# re-enqueueing).
|
||||
# * The user typed `libreportal app install dashy` — enqueue
|
||||
# a task and follow its log in the foreground, so the
|
||||
# CLI + WebUI watch the same job and locking serialises
|
||||
# parallel attempts. `--detach` queues and exits.
|
||||
if [[ "$LIBREPORTAL_TASK_EXEC" == "1" ]]; then
|
||||
dockerInstallApp "$app_name" "$config" "$reset_network"
|
||||
else
|
||||
local _mode=""
|
||||
for _arg in "$config" "$initial_command5" "$initial_command6"; do
|
||||
[[ "$_arg" == "--detach" ]] && _mode="--detach"
|
||||
done
|
||||
# config / --reset-network passthrough — strip the flags
|
||||
# that are CLI-only, keep what dockerInstallApp expects.
|
||||
local _passthrough_config="$config"
|
||||
[[ "$_passthrough_config" == "--detach" || "$_passthrough_config" == "--reset-network" ]] && _passthrough_config=""
|
||||
local _cmd="LIBREPORTAL_TASK_EXEC=1 libreportal app install $app_name"
|
||||
[[ -n "$_passthrough_config" ]] && _cmd+=" '$_passthrough_config'"
|
||||
[[ "$reset_network" == "true" ]] && _cmd+=" --reset-network"
|
||||
cliTaskRun "$_cmd" "install" "$app_name" "$_mode"
|
||||
fi
|
||||
;;
|
||||
|
||||
"uninstall")
|
||||
|
||||
175
scripts/cli/task/cli_task_run.sh
Normal file
175
scripts/cli/task/cli_task_run.sh
Normal file
@ -0,0 +1,175 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Routes a CLI command through the existing libreportal task processor and
|
||||
# follows its log in the foreground, so the terminal sees the same output
|
||||
# the WebUI sees and Ctrl-C detaches cleanly without killing the task.
|
||||
#
|
||||
# Usage:
|
||||
# cliTaskRun "libreportal app install dashy" install dashy
|
||||
# cliTaskRun "libreportal app install dashy" install dashy --detach
|
||||
#
|
||||
# Sets up a task file that's identical to what the WebUI's "install" button
|
||||
# produces — same format, same task dir, same processor. The CLI just adds
|
||||
# itself as a viewer.
|
||||
#
|
||||
# How the command bypass works: the recursive command in the task file is
|
||||
# prefixed with `LIBREPORTAL_TASK_EXEC=1`, and the caller (the app install
|
||||
# handler) honours that by running inline instead of re-enqueueing. No
|
||||
# processor changes required — the bypass travels with the task.
|
||||
|
||||
# Resolve task dir, FIFO, the manifest of tasks dir/log paths. Mirrors what
|
||||
# crontab_task_processor.sh and webui_task_create.sh use; if those move, both
|
||||
# this file and they have to update together.
|
||||
_taskDir() {
|
||||
echo "${containers_dir}libreportal/frontend/data/tasks"
|
||||
}
|
||||
|
||||
_genTaskId() {
|
||||
local rand
|
||||
rand=$(openssl rand -hex 4 2>/dev/null || printf '%08x' $RANDOM)
|
||||
echo "task_$(date +%s)_${rand}"
|
||||
}
|
||||
|
||||
# Write the task file + append to queue.json. Identical shape to
|
||||
# createTaskFile in webui_task_create.sh, but inlined here so the CLI doesn't
|
||||
# have to source the WebUI generators when LP_LAZY=1 hasn't pulled them in.
|
||||
_writeTaskFile() {
|
||||
local task_id="$1" command="$2" task_type="$3" app_name="$4"
|
||||
local task_dir; task_dir=$(_taskDir)
|
||||
local task_file="$task_dir/${task_id}.json"
|
||||
local created_at; created_at=$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
[[ -d "$task_dir" ]] || runFileOp mkdir -p "$task_dir"
|
||||
|
||||
local task_json
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
task_json=$(jq -n \
|
||||
--arg id "$task_id" \
|
||||
--arg command "$command" \
|
||||
--arg status queued \
|
||||
--arg created_at "$created_at" \
|
||||
--arg type "${task_type:-}" \
|
||||
--arg app "${app_name:-}" \
|
||||
'{id:$id, command:$command, status:$status, created_at:$created_at}
|
||||
+ (if $type != "" then {type:$type} else {} end)
|
||||
+ (if $app != "" then {app:$app} else {} end)')
|
||||
else
|
||||
# Plain string interpolation — command is the only field that needs
|
||||
# escaping. Strip control chars; the install dispatcher uses ascii
|
||||
# only so this is sufficient without pulling jq in.
|
||||
local esc="${command//\\/\\\\}"; esc="${esc//\"/\\\"}"
|
||||
task_json="{\"id\":\"$task_id\",\"command\":\"$esc\",\"status\":\"queued\",\"created_at\":\"$created_at\""
|
||||
[[ -n "$task_type" ]] && task_json+=",\"type\":\"$task_type\""
|
||||
[[ -n "$app_name" ]] && task_json+=",\"app\":\"$app_name\""
|
||||
task_json+="}"
|
||||
fi
|
||||
|
||||
printf '%s' "$task_json" | runFileWrite "$task_file"
|
||||
runFileOp chmod 644 "$task_file" 2>/dev/null
|
||||
|
||||
local queue="$task_dir/queue.json"
|
||||
[[ -f "$queue" ]] || printf '[]' | runFileWrite "$queue"
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
local updated; updated=$(jq --arg id "$task_id" '. + [$id]' "$queue" 2>/dev/null || echo "[\"$task_id\"]")
|
||||
printf '%s' "$updated" | runFileWrite "$queue"
|
||||
else
|
||||
# Best-effort fallback.
|
||||
local cur; cur=$(cat "$queue" 2>/dev/null)
|
||||
if [[ "$cur" == "[]" || -z "$cur" ]]; then
|
||||
printf '["%s"]' "$task_id" | runFileWrite "$queue"
|
||||
else
|
||||
printf '%s' "${cur%]}, \"$task_id\"]" | runFileWrite "$queue"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Poke the FIFO so the processor dispatches immediately instead of
|
||||
# waiting up to IDLE_POLL_SECS (3s) for its next read timeout.
|
||||
local fifo="$task_dir/.queue.fifo"
|
||||
[[ -p "$fifo" ]] && printf '%s\n' "$task_id" >> "$fifo" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Follow a task's log + status until terminal state.
|
||||
# Returns the task's exit_code (0 on completed, non-zero on failed/cancelled).
|
||||
# Ctrl-C from the user detaches the follower without cancelling the task.
|
||||
cliTaskFollow() {
|
||||
local task_id="$1"
|
||||
local task_dir; task_dir=$(_taskDir)
|
||||
local task_file="$task_dir/${task_id}.json"
|
||||
local log_file="$task_dir/${task_id}.log"
|
||||
|
||||
[[ -f "$task_file" ]] || { isError "Task $task_id not found."; return 1; }
|
||||
|
||||
# Ctrl-C detaches us, NOT the task. Print a hint and exit 0 — the
|
||||
# processor keeps running in the background and the WebUI continues
|
||||
# to show the task. `libreportal task log $task_id` (future) can
|
||||
# reattach.
|
||||
local detached=0
|
||||
trap 'detached=1' INT
|
||||
|
||||
# tail -F (capital): retry-on-error, so we can start tailing before
|
||||
# the log file exists. Processor creates it just before exec.
|
||||
tail -n +1 -F "$log_file" 2>/dev/null &
|
||||
local tail_pid=$!
|
||||
|
||||
# Poll the task file until status hits a terminal state OR the user
|
||||
# detaches. 0.5s polling — bash + jq is cheap and the UX feels live.
|
||||
local status="queued" exit_code=0
|
||||
while (( detached == 0 )); do
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
status=$(jq -r '.status // "queued"' "$task_file" 2>/dev/null)
|
||||
else
|
||||
status=$(grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]+"' "$task_file" 2>/dev/null | sed 's/.*"\([^"]*\)"$/\1/')
|
||||
fi
|
||||
case "$status" in
|
||||
completed|failed|cancelled) break ;;
|
||||
esac
|
||||
sleep 0.5
|
||||
done
|
||||
trap - INT
|
||||
|
||||
# Let tail flush the final output buffered behind the log writer, then
|
||||
# kill it cleanly.
|
||||
sleep 0.3
|
||||
kill "$tail_pid" 2>/dev/null
|
||||
wait "$tail_pid" 2>/dev/null
|
||||
|
||||
if (( detached )); then
|
||||
echo ""
|
||||
isNotice "Detached from $task_id. Task continues in the background; the WebUI tasks panel will show its progress."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
exit_code=$(jq -r '.exit_code // 0' "$task_file" 2>/dev/null)
|
||||
fi
|
||||
case "$status" in
|
||||
completed) return "${exit_code:-0}" ;;
|
||||
failed) return "${exit_code:-1}" ;;
|
||||
cancelled) return 130 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
# Enqueue a command and follow until terminal state (or --detach).
|
||||
# Args: command_string task_type app_name [--detach]
|
||||
cliTaskRun() {
|
||||
local command="$1" task_type="$2" app_name="$3" mode="$4"
|
||||
|
||||
if [[ -z "$command" ]]; then
|
||||
isError "cliTaskRun: empty command."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local task_id; task_id=$(_genTaskId)
|
||||
_writeTaskFile "$task_id" "$command" "$task_type" "$app_name"
|
||||
|
||||
if [[ "$mode" == "--detach" ]]; then
|
||||
isSuccessful "Task queued: $task_id"
|
||||
isNotice "Detached — track progress in the WebUI tasks panel or via 'libreportal task log $task_id' (future)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
isNotice "Queued task $task_id ($task_type${app_name:+ $app_name}). Following live output — Ctrl-C detaches without cancelling."
|
||||
echo ""
|
||||
cliTaskFollow "$task_id"
|
||||
}
|
||||
@ -46,5 +46,6 @@ cli_scripts=(
|
||||
"cli/commands/validation/cli_validation_header.sh"
|
||||
"cli/commands/webui/cli_webui_commands.sh"
|
||||
"cli/commands/webui/cli_webui_header.sh"
|
||||
"cli/task/cli_task_run.sh"
|
||||
|
||||
)
|
||||
|
||||
@ -286,6 +286,8 @@ declare -gA LP_FN_MAP=(
|
||||
[cliShowUpdateHelp]="cli/commands/update/cli_update_header.sh"
|
||||
[cliShowValidationHelp]="cli/commands/validation/cli_validation_header.sh"
|
||||
[cliShowWebuiHelp]="cli/commands/webui/cli_webui_header.sh"
|
||||
[cliTaskFollow]="cli/task/cli_task_run.sh"
|
||||
[cliTaskRun]="cli/task/cli_task_run.sh"
|
||||
[cliUpdateCommands]="cli/cli_update.sh"
|
||||
[cliWebuiLoginReset]="cli/commands/webui/cli_webui_commands.sh"
|
||||
[completeMessage]="menu/message/complete.sh"
|
||||
@ -422,6 +424,7 @@ declare -gA LP_FN_MAP=(
|
||||
[generateInstallName]="checks/generate_install_name.sh"
|
||||
[generateRandomPassword]="config/password/password_generate.sh"
|
||||
[generateRandomUsername]="config/password/password_user_generator.sh"
|
||||
[_genTaskId]="cli/task/cli_task_run.sh"
|
||||
[getConfigOptionData]="config/core/config_get_config_data.sh"
|
||||
[getLibrePortalWebUIUrls]="webui/webui_display_logins.sh"
|
||||
[getStoredPassword]="config/password/bcrypt/password_retreive_bcrypt.sh"
|
||||
@ -797,6 +800,7 @@ declare -gA LP_FN_MAP=(
|
||||
[tagsProcessorTraefikControl]="config/tags/processors/tags_processor_traefik_control.sh"
|
||||
[tagsProcessorTrustedDomains]="config/tags/processors/tags_processor_trusted_domains.sh"
|
||||
[tailscaleInstallToContainer]="headscale/scripts/tailscale_install.sh"
|
||||
[_taskDir]="cli/task/cli_task_run.sh"
|
||||
[toolArgsGet]="docker/app/functions/function_app_tool.sh"
|
||||
[toolsMenu]="menu/tools/manage_main.sh"
|
||||
[traefik_install_post_compose]="traefik/scripts/traefik_install_hooks.sh"
|
||||
@ -889,6 +893,7 @@ declare -gA LP_FN_MAP=(
|
||||
[wireguard_install_post_start]="wireguard/scripts/wireguard_install_hooks.sh"
|
||||
[wireguard_install_pre]="wireguard/scripts/wireguard_install_hooks.sh"
|
||||
[writeAtomic]="crontab/task/crontab_task_processor.sh"
|
||||
[_writeTaskFile]="cli/task/cli_task_run.sh"
|
||||
[zipFile]="function/file/zip_file.sh"
|
||||
)
|
||||
|
||||
@ -1170,6 +1175,8 @@ declare -gA LP_FN_ROOT=(
|
||||
[cliShowUpdateHelp]="scripts"
|
||||
[cliShowValidationHelp]="scripts"
|
||||
[cliShowWebuiHelp]="scripts"
|
||||
[cliTaskFollow]="scripts"
|
||||
[cliTaskRun]="scripts"
|
||||
[cliUpdateCommands]="scripts"
|
||||
[cliWebuiLoginReset]="scripts"
|
||||
[completeMessage]="scripts"
|
||||
@ -1306,6 +1313,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[generateInstallName]="scripts"
|
||||
[generateRandomPassword]="scripts"
|
||||
[generateRandomUsername]="scripts"
|
||||
[_genTaskId]="scripts"
|
||||
[getConfigOptionData]="scripts"
|
||||
[getLibrePortalWebUIUrls]="scripts"
|
||||
[getStoredPassword]="scripts"
|
||||
@ -1681,6 +1689,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[tagsProcessorTraefikControl]="scripts"
|
||||
[tagsProcessorTrustedDomains]="scripts"
|
||||
[tailscaleInstallToContainer]="containers"
|
||||
[_taskDir]="scripts"
|
||||
[toolArgsGet]="scripts"
|
||||
[toolsMenu]="scripts"
|
||||
[traefik_install_post_compose]="containers"
|
||||
@ -1773,6 +1782,7 @@ declare -gA LP_FN_ROOT=(
|
||||
[wireguard_install_post_start]="containers"
|
||||
[wireguard_install_pre]="containers"
|
||||
[writeAtomic]="scripts"
|
||||
[_writeTaskFile]="scripts"
|
||||
[zipFile]="scripts"
|
||||
)
|
||||
|
||||
@ -2074,6 +2084,8 @@ cliShowSystemHelp() { source "${install_scripts_dir}cli/commands/system/cli_syst
|
||||
cliShowUpdateHelp() { source "${install_scripts_dir}cli/commands/update/cli_update_header.sh"; cliShowUpdateHelp "$@"; }
|
||||
cliShowValidationHelp() { source "${install_scripts_dir}cli/commands/validation/cli_validation_header.sh"; cliShowValidationHelp "$@"; }
|
||||
cliShowWebuiHelp() { source "${install_scripts_dir}cli/commands/webui/cli_webui_header.sh"; cliShowWebuiHelp "$@"; }
|
||||
cliTaskFollow() { source "${install_scripts_dir}cli/task/cli_task_run.sh"; cliTaskFollow "$@"; }
|
||||
cliTaskRun() { source "${install_scripts_dir}cli/task/cli_task_run.sh"; cliTaskRun "$@"; }
|
||||
cliUpdateCommands() { source "${install_scripts_dir}cli/cli_update.sh"; cliUpdateCommands "$@"; }
|
||||
cliWebuiLoginReset() { source "${install_scripts_dir}cli/commands/webui/cli_webui_commands.sh"; cliWebuiLoginReset "$@"; }
|
||||
completeMessage() { source "${install_scripts_dir}menu/message/complete.sh"; completeMessage "$@"; }
|
||||
@ -2210,6 +2222,7 @@ generateHealthReport() { source "${install_scripts_dir}crontab/task/crontab_chec
|
||||
generateInstallName() { source "${install_scripts_dir}checks/generate_install_name.sh"; generateInstallName "$@"; }
|
||||
generateRandomPassword() { source "${install_scripts_dir}config/password/password_generate.sh"; generateRandomPassword "$@"; }
|
||||
generateRandomUsername() { source "${install_scripts_dir}config/password/password_user_generator.sh"; generateRandomUsername "$@"; }
|
||||
_genTaskId() { source "${install_scripts_dir}cli/task/cli_task_run.sh"; _genTaskId "$@"; }
|
||||
getConfigOptionData() { source "${install_scripts_dir}config/core/config_get_config_data.sh"; getConfigOptionData "$@"; }
|
||||
getLibrePortalWebUIUrls() { source "${install_scripts_dir}webui/webui_display_logins.sh"; getLibrePortalWebUIUrls "$@"; }
|
||||
getStoredPassword() { source "${install_scripts_dir}config/password/bcrypt/password_retreive_bcrypt.sh"; getStoredPassword "$@"; }
|
||||
@ -2585,6 +2598,7 @@ tagsProcessorSpeedtestPass() { source "${install_scripts_dir}config/tags/process
|
||||
tagsProcessorTraefikControl() { source "${install_scripts_dir}config/tags/processors/tags_processor_traefik_control.sh"; tagsProcessorTraefikControl "$@"; }
|
||||
tagsProcessorTrustedDomains() { source "${install_scripts_dir}config/tags/processors/tags_processor_trusted_domains.sh"; tagsProcessorTrustedDomains "$@"; }
|
||||
tailscaleInstallToContainer() { source "${install_containers_dir}headscale/scripts/tailscale_install.sh"; tailscaleInstallToContainer "$@"; }
|
||||
_taskDir() { source "${install_scripts_dir}cli/task/cli_task_run.sh"; _taskDir "$@"; }
|
||||
toolArgsGet() { source "${install_scripts_dir}docker/app/functions/function_app_tool.sh"; toolArgsGet "$@"; }
|
||||
toolsMenu() { source "${install_scripts_dir}menu/tools/manage_main.sh"; toolsMenu "$@"; }
|
||||
traefik_install_post_compose() { source "${install_containers_dir}traefik/scripts/traefik_install_hooks.sh"; traefik_install_post_compose "$@"; }
|
||||
@ -2677,4 +2691,5 @@ wireguard_install_post_compose() { source "${install_containers_dir}wireguard/sc
|
||||
wireguard_install_post_start() { source "${install_containers_dir}wireguard/scripts/wireguard_install_hooks.sh"; wireguard_install_post_start "$@"; }
|
||||
wireguard_install_pre() { source "${install_containers_dir}wireguard/scripts/wireguard_install_hooks.sh"; wireguard_install_pre "$@"; }
|
||||
writeAtomic() { source "${install_scripts_dir}crontab/task/crontab_task_processor.sh"; writeAtomic "$@"; }
|
||||
_writeTaskFile() { source "${install_scripts_dir}cli/task/cli_task_run.sh"; _writeTaskFile "$@"; }
|
||||
zipFile() { source "${install_scripts_dir}function/file/zip_file.sh"; zipFile "$@"; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user