diff --git a/apps/api/tests/RUNNING_TESTS.md b/apps/api/tests/RUNNING_TESTS.md index eb9036fc028..972f6b9565c 100644 --- a/apps/api/tests/RUNNING_TESTS.md +++ b/apps/api/tests/RUNNING_TESTS.md @@ -61,13 +61,13 @@ docker compose -f docker-compose-test.yml down -v ## How it works -| Service | Image | Purpose | -| ------------ | -------------------------------------------------- | --------------------------------------------- | -| `test-db` | `postgres:15.7-alpine` | Application database | -| `test-redis` | `valkey/valkey:7.2.11-alpine` | Cache / Celery broker | -| `test-mq` | `rabbitmq:3.13.6-management-alpine` | Task queue | -| `test-minio` | `quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z` | S3-compatible object storage | -| `api-tests` | built from `apps/api/Dockerfile.dev` | Installs `requirements/test.txt`, runs pytest | +| Service | Image | Purpose | +| ------------ | ---------------------------------------------------- | --------------------------------------------- | +| `test-db` | `postgres:15.7-alpine` | Application database | +| `test-redis` | `valkey/valkey:7.2.11-alpine` | Cache / Celery broker | +| `test-mq` | `rabbitmq:3.13.6-management-alpine` | Task queue | +| `test-minio` | `docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z` | S3-compatible object storage | +| `api-tests` | built from `apps/api/Dockerfile.dev` | Installs `requirements/test.txt`, runs pytest | All four dependencies expose health checks; `api-tests` waits for `service_healthy` on each via `depends_on`, so pytest only starts once the stack is ready. diff --git a/deployments/cli/community/docker-compose.yml b/deployments/cli/community/docker-compose.yml index 44b61f78604..f7850c93ecb 100644 --- a/deployments/cli/community/docker-compose.yml +++ b/deployments/cli/community/docker-compose.yml @@ -205,7 +205,7 @@ services: # Comment this if you using any external s3 compatible storage plane-minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z + image: docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z command: server /export --console-address ":9090" deploy: replicas: 1 diff --git a/deployments/cli/community/restore-airgapped.sh b/deployments/cli/community/restore-airgapped.sh index 9da02fd869d..70a01322027 100755 --- a/deployments/cli/community/restore-airgapped.sh +++ b/deployments/cli/community/restore-airgapped.sh @@ -1,23 +1,49 @@ #!/bin/bash -+set -euo pipefail +set -euo pipefail function print_header() { clear cat <<"EOF" --------------------------------------------- - ____ _ ///////// -| _ \| | __ _ _ __ ___ ///////// -| |_) | |/ _` | '_ \ / _ \ ///// ///// -| __/| | (_| | | | | __/ ///// ///// -|_| |_|\__,_|_| |_|\___| //// - //// --------------------------------------------- -Project management tool from the future --------------------------------------------- +##+. ##+ .##- + ######+.######-.######. + #######. -### +#####+. + #######. + +######. + #######. .####### + #######. .####### + ####### + .####### + .+#####+ ###- .####### + .######.-#####+.+###### + -##. -## .+## EOF } +# Replace $2 (dest) with $1 (src), keeping the old dest recoverable until the +# swap succeeds. Rolls the old data back if the move fails. +function replaceDir() { + local src="$1" dest="$2" label="$3" + local old="${dest}.old.$$" + + if [ -d "$dest" ]; then + mv "$dest" "$old" + # Restore the old data if we are interrupted mid-swap + trap "mv \"$old\" \"$dest\" 2>/dev/null || true; exit 1" INT TERM HUP + fi + + if mv "$src" "$dest"; then + trap - INT TERM HUP + rm -rf "$old" + echo "Renamed $label" + else + trap - INT TERM HUP + echo "Error: Failed to install $label; restoring previous data" + if [ -d "$old" ]; then + mv "$old" "$dest" + fi + exit 1 + fi +} + function restoreData() { echo "" @@ -27,7 +53,7 @@ function restoreData() { echo "" # set the backup folder path - BACKUP_FOLDER=${1} + BACKUP_FOLDER="${1:-}" if [ -z "$BACKUP_FOLDER" ]; then BACKUP_FOLDER="$PWD/backup" @@ -46,6 +72,8 @@ function restoreData() { # check if there are any .tar.gz files in the backup folder if ! ls "$BACKUP_FOLDER"/*.tar.gz 1> /dev/null 2>&1; then echo "Error: Backup folder does not contain .tar.gz files" + echo "" + echo "Usage: $0 /path/to/backup" exit 1 fi @@ -76,14 +104,22 @@ function restoreData() { exit 1 fi - local dockerServiceStatus + local dockerServiceStatus compose_output if command -v jq &> /dev/null; then - dockerServiceStatus=$($COMPOSE_CMD ls --filter name=plane-airgapped --format=json | jq -r .[0].Status) + if ! compose_output=$($COMPOSE_CMD ls --filter name=plane-airgapped --format=json); then + echo "Error: Failed to query Docker Compose services" + exit 1 + fi + dockerServiceStatus=$(printf '%s\n' "$compose_output" | jq -r '.[0].Status // empty') else - dockerServiceStatus=$($COMPOSE_CMD ls --filter name=plane-airgapped | grep -o "running" | head -n 1) + if ! compose_output=$($COMPOSE_CMD ls --filter name=plane-airgapped); then + echo "Error: Failed to query Docker Compose services" + exit 1 + fi + dockerServiceStatus=$(printf '%s\n' "$compose_output" | grep -o "running" | head -n 1 || true) fi - if [[ $dockerServiceStatus == "running" ]]; then + if [[ "$dockerServiceStatus" == running* ]]; then echo "Plane Airgapped is running. Please STOP the Plane Airgapped before restoring data." exit 1 fi @@ -91,42 +127,52 @@ function restoreData() { CURRENT_USER_ID=$(id -u) CURRENT_GROUP_ID=$(id -g) + DATA_DIR="$AIRGAPPED_INSTALL_PATH/data" + # if the data folder not exists, create it - if [ ! -d "$AIRGAPPED_INSTALL_PATH/data" ]; then - mkdir -p "$AIRGAPPED_INSTALL_PATH/data" - chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$AIRGAPPED_INSTALL_PATH/data" + if [ ! -d "$DATA_DIR" ]; then + mkdir -p "$DATA_DIR" + chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$DATA_DIR" fi - for BACKUP_FILE in "$BACKUP_FOLDER/*.tar.gz"; do - if [ -e "$BACKUP_FILE" ]; then - - # get the basefilename without the extension - BASE_FILE_NAME=$(basename "$BACKUP_FILE" ".tar.gz") - - # extract the restoreFile to the airgapped instance install path - echo "Restoring $BASE_FILE_NAME" - rm -rf "$AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME" || true - - tar -xvzf "$BACKUP_FILE" -C "$AIRGAPPED_INSTALL_PATH/data/" - if [ $? -ne 0 ]; then - echo "Error: Failed to extract $BACKUP_FILE" - exit 1 - fi - chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME" - if [ $? -ne 0 ]; then - echo "Error: Failed to change ownership of $AIRGAPPED_INSTALL_PATH/data/$BASE_FILE_NAME" - exit 1 - fi - else - echo "No .tar.gz files found in the current directory." - echo "" - echo "Please provide the path to the backup file." - echo "" - echo "Usage: $0 /path/to/backup" + # Remove stale extracted source directories from a previous run so tar + # does not merge new files into old data + rm -rf "$DATA_DIR/pgdata" "$DATA_DIR/redisdata" "$DATA_DIR/uploads" "$DATA_DIR/rabbitmq_data" + + # Extract all backup tar files + for BACKUP_FILE in "$BACKUP_FOLDER"/*.tar.gz; do + BASE_FILE_NAME=$(basename "$BACKUP_FILE" ".tar.gz") + echo "Extracting $BASE_FILE_NAME" + if ! tar -xzvf "$BACKUP_FILE" -C "$DATA_DIR/"; then + echo "Error: Failed to extract $BACKUP_FILE" exit 1 fi done + # Rename extracted directories to match docker-compose volume paths + # Backup tars: pgdata, redisdata, uploads, rabbitmq_data + # Docker-compose expects: db, redis, minio/uploads, mq + + if [ -d "$DATA_DIR/pgdata" ]; then + replaceDir "$DATA_DIR/pgdata" "$DATA_DIR/db" "pgdata -> db" + fi + + if [ -d "$DATA_DIR/redisdata" ]; then + replaceDir "$DATA_DIR/redisdata" "$DATA_DIR/redis" "redisdata -> redis" + fi + + if [ -d "$DATA_DIR/uploads" ]; then + mkdir -p "$DATA_DIR/minio" + replaceDir "$DATA_DIR/uploads" "$DATA_DIR/minio/uploads" "uploads -> minio/uploads" + fi + + if [ -d "$DATA_DIR/rabbitmq_data" ]; then + replaceDir "$DATA_DIR/rabbitmq_data" "$DATA_DIR/mq" "rabbitmq_data -> mq" + fi + + # Fix ownership on all restored data + chown -R $CURRENT_USER_ID:$CURRENT_GROUP_ID "$DATA_DIR" + echo "" echo "Restore completed successfully." echo "" diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 23316d1e0cf..4269173c114 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -24,7 +24,7 @@ services: RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST} plane-minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z + image: docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z restart: unless-stopped networks: - dev_env diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 62fdcbb7442..e6134ff0ba6 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -72,7 +72,7 @@ services: retries: 10 test-minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z + image: docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z networks: - test_env env_file: diff --git a/docker-compose.yml b/docker-compose.yml index 6e985b14704..6d86eabe0ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -142,7 +142,7 @@ services: plane-minio: container_name: plane-minio - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z + image: docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z restart: always command: server /export --console-address ":9090" volumes: