#!/bin/bash dockerComposeUp() { local app_name="$1" local custom_compose="$2" local type="$3" if [[ "$app_name" == "" ]]; then isError "Something went wrong...No app name provided..." return 1 fi # Real exit status of the compose command, so callers that gate on it (the # updater's rollback, the artifact apply pipeline) aren't fail-open. checkSuccess # only logs; it does NOT set the function's return — without this the function # always returned 0 and a failed `up -d` looked like success. local _rc=0 isHeader "Docker Compose Up $app_name" # Make sure we are able to get the compose file if [[ $compose_setup == "" ]]; then setupBasicScanVariables "$app_name" fi # Compose file public variable for restarting etc if [[ $compose_setup == "default" ]]; then local setup_compose="-f docker-compose.yml" local compose_file="docker-compose.yml" elif [[ $compose_setup == "app" ]]; then local setup_compose="-f docker-compose.yml -f docker-compose.$app_name.yml" local compose_file="docker-compose.$app_name.yml" fi if [[ $custom_compose != "" ]]; then local setup_compose="-f docker-compose.yml -f $custom_compose" local compose_file="$custom_compose" fi if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then if [ -f "$containers_dir$app_name/$compose_file" ]; then # Quiet pull + plain progress so progress redraws don't flood the log. local _compose_quiet="--quiet-pull" export COMPOSE_PROGRESS=plain # Force a rebuild when the app ships a Dockerfile. Without # --build, `up -d` reuses the cached image and silently # ignores any edits to Dockerfile / source between installs. local _compose_build_flag="" local _is_local_build=0 if [[ -f "$containers_dir$app_name/Dockerfile" ]]; then _compose_build_flag="--build" _is_local_build=1 fi # Used for the standard LibrePortal app if [[ "$type" == "" ]]; then # Refuse to start if the runtime compose still has raw # `#LIBREPORTAL|TAG|VALUE` placeholders on the value side. # Happens when the template was copied over the runtime # compose without the tag processors running (manual # copy, partial restore, aborted install). Without this # guard docker errors with "invalid boolean: # HEALTHCHECK_DATA" or similar. # # Two-pass: detect → attempt self-heal by re-running the # full tag-processor pipeline → re-detect. Only refuse # if heal didn't clear all placeholders. _scanStaleTags() { awk ' { idx = index($0, "#LIBREPORTAL|") if (idx == 0) next # Skip whole-line YAML comments (e.g. "# - PORTS_DATA_4 ...") value_part = substr($0, 1, idx - 1) trimmed = value_part sub(/^[ \t]+/, "", trimmed) if (substr(trimmed, 1, 1) == "#") next marker = substr($0, idx + length("#LIBREPORTAL|")) if (split(marker, parts, "|") < 2) next placeholder = parts[2] gsub(/[ \t]+$/, "", placeholder) if (placeholder ~ /^[A-Z][A-Z0-9_]*_DATA(_[0-9]+)?$/) { printf " line %d: %s\n", NR, $0 } } ' "$1" } local _compose_path="$containers_dir$app_name/$compose_file" local _stale_tags _stale_tags=$(_scanStaleTags "$_compose_path") if [[ -n "$_stale_tags" ]]; then isNotice "$app_name compose has unsubstituted LibrePortal tag placeholders — attempting self-heal via tag processors." if declare -F dockerConfigSetupFileWithData >/dev/null 2>&1; then # The processors read per-app env vars # (healthcheck, host_setup, app_category, …) set # by initializeAppVariables. Without it some # tags get filled with empty strings and the # heal leaves placeholders behind. if declare -F initializeAppVariables >/dev/null 2>&1; then initializeAppVariables "$app_name" >/dev/null 2>&1 || true fi dockerConfigSetupFileWithData "$app_name" >/dev/null 2>&1 || true _stale_tags=$(_scanStaleTags "$_compose_path") fi if [[ -n "$_stale_tags" ]]; then isError "$app_name compose still has unsubstituted LibrePortal tag placeholders after self-heal — refusing to start." isNotice "File: $_compose_path" while IFS= read -r _l; do isNotice "$_l"; done <<< "$_stale_tags" isNotice "Fix: run 'libreportal app install $app_name' to re-apply the full install pipeline." unset -f _scanStaleTags return 1 fi isSuccessful "Tag processors re-applied — placeholders resolved, continuing start." fi unset -f _scanStaleTags # Local-build apps take noticeably longer than image-pull # apps. Specific heads-up so the user doesn't wonder if # it's stuck during a multi-minute Dockerfile build. if (( _is_local_build )); then isNotice "$app_name has a Dockerfile — building image locally." isNotice "First build can take a few minutes." fi if [[ $CFG_DOCKER_INSTALL_TYPE == "rootless" ]]; then isNotice "Starting container for $app_name, this may take a while..." local result; result=$(dockerCommandRunInstallUser "cd $containers_dir$app_name && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d"); _rc=$? checkSuccess "Started container for $app_name" elif [[ $CFG_DOCKER_INSTALL_TYPE == "rooted" ]]; then isNotice "Starting container for $app_name, this may take a while..." local result; result=$(cd "$containers_dir$app_name" && COMPOSE_PROGRESS=plain docker compose $setup_compose up $_compose_quiet $_compose_build_flag -d); _rc=$? checkSuccess "Started container for $app_name" fi # Used for the CLI dockertype switcher. else if [[ $type == "rootless" ]]; then local result; result=$(dockerCommandRunInstallUser "cd $containers_dir$app_name && docker compose $setup_compose down"); _rc=$? checkSuccess "Shutting down container for $app_name" elif [[ $type == "rooted" ]]; then local result; result=$(cd "$containers_dir$app_name" && docker compose $setup_compose down); _rc=$? checkSuccess "Shutting down container for $app_name" fi fi else isNotice "Unable to find the compose file to docker compose up this application." _rc=1 fi fi return $_rc }