#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" STACK_ENV="${STACK_ENV:-$ROOT_DIR/stack.env}" POD_NAME="gia" REDIS_CONTAINER="redis_gia" SIGNAL_CONTAINER="signal" MIGRATION_CONTAINER="migration_gia" COLLECTSTATIC_CONTAINER="collectstatic_gia" APP_CONTAINER="gia" ASGI_CONTAINER="asgi_gia" UR_CONTAINER="ur_gia" SCHED_CONTAINER="scheduling_gia" REDIS_DATA_DIR="${QUADLET_REDIS_DATA_DIR:-$ROOT_DIR/.podman/gia_redis_data}" WHATSAPP_DATA_DIR="${QUADLET_WHATSAPP_DATA_DIR:-$ROOT_DIR/.podman/gia_whatsapp_data}" VRUN_DIR="/code/vrun" load_env() { set -a . "$STACK_ENV" set +a } require_podman() { if ! command -v podman >/dev/null 2>&1; then echo "podman not found" >&2 exit 1 fi } ensure_dirs() { mkdir -p "$REDIS_DATA_DIR" "$WHATSAPP_DATA_DIR" "$VRUN_DIR" "$ROOT_DIR/signal-cli-config" } rm_if_exists() { podman rm -f "$1" >/dev/null 2>&1 || true } wait_for_redis_socket() { local sock="$VRUN_DIR/gia-redis.sock" local i for i in $(seq 1 60); do [[ -S "$sock" ]] && return 0 sleep 1 done echo "redis socket did not appear at $sock" >&2 return 1 } run_worker_container() { local name="$1" local cmd="$2" local with_uwsgi="${3:-0}" local with_whatsapp="${4:-0}" rm_if_exists "$name" local args=( --replace --name "$name" --pod "$POD_NAME" --env-file "$STACK_ENV" --env "SIGNAL_HTTP_URL=http://127.0.0.1:8080" -v "$REPO_DIR:/code" -v "$APP_DATABASE_FILE:/conf/db.sqlite3" -v "$VRUN_DIR:/var/run" ) if [[ "$with_uwsgi" == "1" ]]; then args+=( -v "$REPO_DIR/docker/uwsgi.ini:/conf/uwsgi.ini:ro" ) fi if [[ "$with_whatsapp" == "1" ]]; then args+=( -v "$WHATSAPP_DATA_DIR:${WHATSAPP_DB_DIR:-/var/tmp/whatsapp}" ) fi podman run -d "${args[@]}" localhost/xf/gia:prod sh -c "$cmd" >/dev/null } run_oneshot_container() { local name="$1" local cmd="$2" local with_whatsapp="${3:-0}" rm_if_exists "$name" local args=( --replace --name "$name" --pod "$POD_NAME" --env-file "$STACK_ENV" --env "SIGNAL_HTTP_URL=http://127.0.0.1:8080" -v "$REPO_DIR:/code" -v "$APP_DATABASE_FILE:/conf/db.sqlite3" -v "$VRUN_DIR:/var/run" ) if [[ "$with_whatsapp" == "1" ]]; then args+=( -v "$WHATSAPP_DATA_DIR:${WHATSAPP_DB_DIR:-/var/tmp/whatsapp}" ) fi podman run "${args[@]}" localhost/xf/gia:prod sh -c "$cmd" >/dev/null } down_stack() { podman pod rm -f "$POD_NAME" >/dev/null 2>&1 || true rm_if_exists "$REDIS_CONTAINER" rm_if_exists "$SIGNAL_CONTAINER" rm_if_exists "$MIGRATION_CONTAINER" rm_if_exists "$COLLECTSTATIC_CONTAINER" rm_if_exists "$APP_CONTAINER" rm_if_exists "$ASGI_CONTAINER" rm_if_exists "$UR_CONTAINER" rm_if_exists "$SCHED_CONTAINER" } start_stack() { require_podman load_env ensure_dirs down_stack podman pod create --name "$POD_NAME" -p "${APP_PORT:-5006}:8000" -p "8080:8080" >/dev/null podman run -d \ --replace \ --name "$REDIS_CONTAINER" \ --pod "$POD_NAME" \ -v "$REPO_DIR/docker/redis.conf:/etc/redis.conf:ro" \ -v "$REDIS_DATA_DIR:/data" \ -v "$VRUN_DIR:/var/run" \ docker.io/library/redis:latest \ redis-server /etc/redis.conf >/dev/null podman run -d \ --replace \ --name "$SIGNAL_CONTAINER" \ --pod "$POD_NAME" \ -e MODE=json-rpc \ -v "$ROOT_DIR/signal-cli-config:/home/.local/share/signal-cli" \ docker.io/bbernhard/signal-cli-rest-api:latest >/dev/null wait_for_redis_socket run_oneshot_container "$MIGRATION_CONTAINER" ". /venv/bin/activate && python manage.py migrate --noinput" run_oneshot_container "$COLLECTSTATIC_CONTAINER" ". /venv/bin/activate && python manage.py collectstatic --noinput" run_worker_container "$APP_CONTAINER" "if [ \"\$OPERATION\" = \"uwsgi\" ] ; then . /venv/bin/activate && uwsgi --ini /conf/uwsgi.ini ; else . /venv/bin/activate && exec python manage.py runserver 0.0.0.0:8000; fi" 1 1 run_worker_container "$ASGI_CONTAINER" "rm -f /var/run/asgi-gia.sock && . /venv/bin/activate && python -m pip install --disable-pip-version-check -q uvicorn && python -m uvicorn app.asgi:application --uds /var/run/asgi-gia.sock --workers 1" 0 1 run_worker_container "$UR_CONTAINER" ". /venv/bin/activate && python manage.py ur" 1 1 run_worker_container "$SCHED_CONTAINER" ". /venv/bin/activate && python manage.py scheduling" 1 0 } render_units() { python3 "$ROOT_DIR/scripts/quadlet/render_units.py" --stack-env "$STACK_ENV" } case "${1:-}" in install) render_units ;; up) start_stack trap 'down_stack; exit 0' INT TERM podman logs -f "$APP_CONTAINER" "$ASGI_CONTAINER" "$UR_CONTAINER" "$SCHED_CONTAINER" "$REDIS_CONTAINER" "$SIGNAL_CONTAINER" || true ;; down) require_podman down_stack ;; restart) start_stack ;; status) require_podman podman pod ps --format "table {{.Name}}\t{{.Status}}" | grep -E "^$POD_NAME\b" || true podman ps --format "table {{.Names}}\t{{.Status}}" | grep -E "^($APP_CONTAINER|$ASGI_CONTAINER|$UR_CONTAINER|$SCHED_CONTAINER|$REDIS_CONTAINER|$SIGNAL_CONTAINER)\b" || true ;; logs) require_podman podman logs -f "$APP_CONTAINER" "$ASGI_CONTAINER" "$UR_CONTAINER" "$SCHED_CONTAINER" "$REDIS_CONTAINER" "$SIGNAL_CONTAINER" ;; *) echo "Usage: $0 {install|up|down|restart|status|logs}" >&2 exit 2 ;; esac