262 lines
8.2 KiB
Bash
Executable File
262 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
STACK_ENV="${STACK_ENV:-$ROOT_DIR/stack.env}"
|
|
|
|
STACK_ID="${GIA_STACK_ID:-${STACK_ID:-}}"
|
|
STACK_ID="$(echo "$STACK_ID" | tr -cs 'a-zA-Z0-9._-' '-' | sed 's/^-*//; s/-*$//')"
|
|
|
|
name_with_stack() {
|
|
local base="$1"
|
|
if [[ -n "$STACK_ID" ]]; then
|
|
echo "${base}_${STACK_ID}"
|
|
else
|
|
echo "$base"
|
|
fi
|
|
}
|
|
|
|
POD_NAME="$(name_with_stack "gia")"
|
|
|
|
REDIS_CONTAINER="$(name_with_stack "redis_gia")"
|
|
SIGNAL_CONTAINER="$(name_with_stack "signal")"
|
|
MIGRATION_CONTAINER="$(name_with_stack "migration_gia")"
|
|
COLLECTSTATIC_CONTAINER="$(name_with_stack "collectstatic_gia")"
|
|
APP_CONTAINER="$(name_with_stack "gia")"
|
|
ASGI_CONTAINER="$(name_with_stack "asgi_gia")"
|
|
UR_CONTAINER="$(name_with_stack "ur_gia")"
|
|
SCHED_CONTAINER="$(name_with_stack "scheduling_gia")"
|
|
CODEX_WORKER_CONTAINER="$(name_with_stack "codex_worker_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}"
|
|
SQLITE_DATA_DIR="${QUADLET_SQLITE_DATA_DIR:-$ROOT_DIR/.podman/gia_sqlite_data}"
|
|
if [[ -n "${STACK_ID}" ]]; then
|
|
VRUN_DIR="/code/vrun/${STACK_ID}"
|
|
else
|
|
VRUN_DIR="/code/vrun"
|
|
fi
|
|
|
|
load_env() {
|
|
set -a
|
|
. "$STACK_ENV"
|
|
set +a
|
|
}
|
|
|
|
is_remote() {
|
|
[[ -n "${CONTAINER_HOST:-}" ]] || [[ -n "${PODMAN_HOST:-}" ]] || [[ -n "${DOCKER_HOST:-}" ]]
|
|
}
|
|
|
|
resolve_path() {
|
|
local path="$1"
|
|
if [[ "$path" = /* ]]; then
|
|
echo "$path"
|
|
else
|
|
echo "$ROOT_DIR/$path"
|
|
fi
|
|
}
|
|
|
|
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" "$SQLITE_DATA_DIR" "$VRUN_DIR" "$ROOT_DIR/signal-cli-config"
|
|
chmod 0777 "$SQLITE_DATA_DIR" 2>/dev/null || true
|
|
# Container runs as uid 1000 (xf); rootless Podman remaps uids so plain
|
|
# chown won't work — podman unshare translates to the correct host uid.
|
|
if [[ -n "${QUADLET_SKIP_UNSHARE:-}" ]] || [[ -n "${CONTAINER_HOST:-}" ]] || [[ -n "${PODMAN_HOST:-}" ]] || [[ -n "${DOCKER_HOST:-}" ]]; then
|
|
echo "Skipping podman unshare chown -- running in sandbox"
|
|
else
|
|
podman unshare chown 1000:1000 "$WHATSAPP_DATA_DIR" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
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"
|
|
--user "$(id -u):$(id -g)"
|
|
--env-file "$STACK_ENV"
|
|
--env "SIGNAL_HTTP_URL=http://127.0.0.1:8080"
|
|
--env "APP_DATABASE_PATH=$APP_DATABASE_PATH"
|
|
-v "$REPO_DIR:/code"
|
|
-v "$SQLITE_DATA_DIR:/conf"
|
|
-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"
|
|
--user "$(id -u):$(id -g)"
|
|
--env-file "$STACK_ENV"
|
|
--env "SIGNAL_HTTP_URL=http://127.0.0.1:8080"
|
|
--env "APP_DATABASE_PATH=$APP_DATABASE_PATH"
|
|
-v "$REPO_DIR:/code"
|
|
-v "$SQLITE_DATA_DIR:/conf"
|
|
-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"
|
|
rm_if_exists "$CODEX_WORKER_CONTAINER"
|
|
}
|
|
|
|
start_stack() {
|
|
require_podman
|
|
load_env
|
|
REPO_DIR="$(resolve_path "$REPO_DIR")"
|
|
APP_DATABASE_FILE="$(resolve_path "$APP_DATABASE_FILE")"
|
|
APP_DATABASE_BASENAME="$(basename "$APP_DATABASE_FILE")"
|
|
APP_DATABASE_PATH="/conf/$APP_DATABASE_BASENAME"
|
|
HOST_DATABASE_FILE="$SQLITE_DATA_DIR/$APP_DATABASE_BASENAME"
|
|
APP_LOCAL_SETTINGS="$(resolve_path "$APP_LOCAL_SETTINGS")"
|
|
ensure_dirs
|
|
if [[ "$APP_DATABASE_FILE" != "$HOST_DATABASE_FILE" ]] && [[ -f "$APP_DATABASE_FILE" ]] && [[ ! -f "$HOST_DATABASE_FILE" ]]; then
|
|
cp "$APP_DATABASE_FILE" "$HOST_DATABASE_FILE"
|
|
fi
|
|
touch "$HOST_DATABASE_FILE"
|
|
chmod 0666 "$HOST_DATABASE_FILE" 2>/dev/null || true
|
|
down_stack
|
|
|
|
local port_offset="${GIA_STACK_PORT_OFFSET:-}"
|
|
if [[ -z "$port_offset" && -n "$STACK_ID" ]]; then
|
|
port_offset="$(( $(printf '%s' "$STACK_ID" | cksum | awk '{print $1}') % 500 + 1 ))"
|
|
fi
|
|
port_offset="${port_offset:-0}"
|
|
local app_port="${APP_PORT:-$((5006 + port_offset))}"
|
|
local signal_port="${SIGNAL_PUBLIC_PORT:-$((8080 + port_offset))}"
|
|
|
|
podman pod create --name "$POD_NAME" -p "${app_port}:8000" -p "${signal_port}: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
|
|
run_worker_container "$CODEX_WORKER_CONTAINER" ". /venv/bin/activate && python manage.py codex_worker" 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
|
|
if is_remote; then
|
|
podman logs -f "$APP_CONTAINER" || true
|
|
else
|
|
podman logs -f "$APP_CONTAINER" "$ASGI_CONTAINER" "$UR_CONTAINER" "$SCHED_CONTAINER" "$REDIS_CONTAINER" "$SIGNAL_CONTAINER" || true
|
|
fi
|
|
;;
|
|
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|$CODEX_WORKER_CONTAINER|$REDIS_CONTAINER|$SIGNAL_CONTAINER)\b" || true
|
|
;;
|
|
logs)
|
|
require_podman
|
|
if is_remote; then
|
|
podman logs -f "$APP_CONTAINER"
|
|
else
|
|
podman logs -f "$APP_CONTAINER" "$ASGI_CONTAINER" "$UR_CONTAINER" "$SCHED_CONTAINER" "$CODEX_WORKER_CONTAINER" "$REDIS_CONTAINER" "$SIGNAL_CONTAINER"
|
|
fi
|
|
;;
|
|
watch)
|
|
require_podman
|
|
load_env
|
|
exec "$ROOT_DIR/scripts/quadlet/watchdog.sh"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {install|up|down|restart|status|logs|watch}" >&2
|
|
exit 2
|
|
;;
|
|
esac
|