Files
mcma-bootstrap/lib/lifecycle.sh
T
Senko-san 1b251869c4 initial
2026-06-08 12:49:45 +03:00

134 lines
3.9 KiB
Bash

# Lifecycle: pull, ordered start (deps → migrate → app), first admin, health,
# plus update/down/logs/status/clean. Wraps `docker compose` with the project's
# env file and generated compose file.
PROJECT="${MCMA_PROJECT:-mcma}"
dc() {
docker compose \
--project-name "$PROJECT" \
--project-directory "$BOOTSTRAP_DIR" \
--env-file "$ENV_FILE" \
-f "$COMPOSE_FILE" "$@"
}
# backing_services — the embedded dependencies present in the compose file that
# must be healthy/complete before migrations (subset of db redis minio*).
backing_services() {
local all want s out=""
all="$(dc config --services 2>/dev/null)"
for want in db redis minio minio-setup; do
while IFS= read -r s; do
[[ "$s" == "$want" ]] && out+="$want "
done <<<"$all"
done
printf '%s' "${out% }"
}
# app_services — everything that is not a one-shot/backing dependency wait.
ensure_media_dir() {
# MEDIA_HOST_PATH is read from the generated env file.
local p
p="$(grep -E '^MEDIA_HOST_PATH=' "$ENV_FILE" | cut -d= -f2-)"
[[ -n "$p" ]] || return 0
# Resolve relative paths against the project directory (compose does too).
[[ "$p" = /* ]] || p="${BOOTSTRAP_DIR}/${p#./}"
mkdir -p "$p"
}
lifecycle_pull() {
ui_info "$(t pull_images "$(grep -E '^MCMA_IMAGE_TAG=' "$ENV_FILE" | cut -d= -f2-)")"
ui_dim "$(t pull_hint)"
dc pull
}
# wait_api_healthy — poll the running API's liveness endpoint over the compose
# network (no host port / TLS assumptions). Returns non-zero on timeout.
wait_api_healthy() {
ui_info "$(t checking_health)"
dc run --rm --no-deps -T api python -c '
import urllib.request, time, sys
for _ in range(60):
try:
if urllib.request.urlopen("http://api:8000/health", timeout=2).status == 200:
sys.exit(0)
except Exception:
pass
time.sleep(2)
sys.exit(1)
'
}
# lifecycle_start CREATE_ADMIN(yes|no)
# Ordered, fail-loud startup. Never starts the backend over a broken DB.
lifecycle_start() {
local create_admin="${1:-no}"
ensure_media_dir
lifecycle_pull
local deps; deps="$(backing_services)"
if [[ -n "$deps" ]]; then
ui_info "$(t starting_deps)"
# shellcheck disable=SC2086
dc up -d --wait $deps
fi
ui_info "$(t running_migrations)"
if ! dc run --rm --no-deps -T api alembic upgrade head; then
ui_err "$(t err_migrations_failed)"
exit 1
fi
if [[ "$create_admin" == "yes" ]]; then
ui_info "$(t creating_admin)"
# Password passed via --password to avoid interactive prompts; not echoed.
dc run --rm --no-deps -T api mcma create-admin "$CFG_ADMIN_USER" --password "$CFG_ADMIN_PASS"
fi
ui_info "$(t starting_app)"
dc up -d
if ! wait_api_healthy; then
ui_err "$(t err_health_timeout)"
exit 1
fi
}
# -- management commands ---------------------------------------------------
lifecycle_update() {
lifecycle_pull
local deps; deps="$(backing_services)"
if [[ -n "$deps" ]]; then
# shellcheck disable=SC2086
dc up -d --wait $deps
fi
ui_info "$(t running_migrations)"
if ! dc run --rm --no-deps -T api alembic upgrade head; then
ui_err "$(t err_migrations_failed)"; exit 1
fi
dc up -d
wait_api_healthy || { ui_err "$(t err_health_timeout)"; exit 1; }
ui_ok "$(t done_title)"
}
lifecycle_up() { ensure_media_dir; dc up -d; }
lifecycle_down() { dc down; }
lifecycle_logs() { dc logs -f --tail=100; }
lifecycle_status() {
dc ps
echo
if wait_api_healthy >/dev/null 2>&1; then ui_ok "API: ok"; else ui_warn "API: not ready"; fi
}
lifecycle_clean() {
dc down
if ui_yesno "$(t clean_volumes_confirm)" "no"; then
dc down -v
fi
if ui_yesno "$(t clean_confirm)" "yes"; then
rm -f "$COMPOSE_FILE" "$CADDYFILE" "$ENV_FILE"
ui_ok "$(t clean_done)"
fi
}