From a62b220202d075b01f393637c89e396625d2e116 Mon Sep 17 00:00:00 2001 From: Peter Kahn Date: Thu, 28 Aug 2025 09:51:13 -0400 Subject: [PATCH 1/3] - Documentation covers MacOS - Reload AI prompts at initial and subsequent system starts - Health check moves on to image (4.3.0 and above) - Branding refactor (4.3.0 and above) - Commenting for OIDC providers in example - Removal of defunct env variables - System supports MacOS and Linux - System handles installation to any path --- README.md | 2 +- doc/controlling-swirl-service.md | 35 ++++++++++++++++ docker-compose.yml | 17 ++++++-- env.example | 13 ++++-- scripts/swirl-load-job.sh | 2 + scripts/swirl-service.sh | 70 ++++++++++++++++++++++++-------- 6 files changed, 115 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4ff1293..80c88fc 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Please contact [hello@swirlaiconnect.com](mailto:hello@swirlaiconnect.com) for m # Additional Documentation -[Overview](https://docs.swirlaiconnect.com/) | [Quick Start](https://docs.swirlaiconnect.com/Quick-Start) | [User Guide](https://docs.swirlaiconnect.com/User-Guide) | [Admin Guide](https://docs.swirlaiconnect.com/Admin-Guide) | [M365 Guide](https://docs.swirlaiconnect.com/M365-Guide) | [Developer Guide](https://docs.swirlaiconnect.com/Developer-Guide) | [Developer Reference](https://docs.swirlaiconnect.com/Developer-Reference) | [AI Guide](https://docs.swirlaiconnect.com/AI-Guide) +[Overview](https://docs.swirlaiconnect.com/) | [Quick Start](https://docs.swirlaiconnect.com/Quick-Start) | [User Guide](https://docs.swirlaiconnect.com/User-Guide) | [Admin Guide](https://docs.swirlaiconnect.com/Admin-Guide) | [M365 Guide](https://docs.swirlaiconnect.com/M365-Guide) | [Developer Guide](https://docs.swirlaiconnect.com/Developer-Guide) | [Developer Reference](https://docs.swirlaiconnect.com/Developer-Reference) | [AI Search Guide](https://docs.swirlaiconnect.com/AI-Search.html) | [AI Search Assistant Guide](https://docs.swirlaiconnect.com/AI-Search-Assistant.html) # Support diff --git a/doc/controlling-swirl-service.md b/doc/controlling-swirl-service.md index 1b1a70e..7c6ae7f 100644 --- a/doc/controlling-swirl-service.md +++ b/doc/controlling-swirl-service.md @@ -13,6 +13,30 @@ sudo systemctl start swirl sudo systemctl stop swirl sudo systemctl restart swirl ``` +### MacOS +> Note: The service must be installed as a user-level LaunchAgent (e.g., in `~Library/LaunchAgents/com.swirl.service.plist`). + +**Start (manually):** +```bash +launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist +``` + +**Stop:** +```bash +launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist +``` + +**Restart:** +```bash +launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist +launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist +``` + +> Note: if `RunAtLoad` is set to `false`, the service won't run automatically when loaded. You'll need to explicitly start it using `kickstart`: +```bash +launchctl kickstart -k gui/$(id -u)/com.swirl.service +``` + ### Docker ### Monitoring the Service To monitor the status of the Swirl service, you can use: @@ -23,6 +47,17 @@ sudo systemctl status swirl sudo journalctl -u swirl ``` +### MacOS +To check if the service is running: +```bash +launchctl list | grep com.swirl.service +``` + +To monitor logs (if configured with `StandardOutPath` and `StandardErrorPath`): +```bash +tail -f $HOME/tmp/log/swirl-service.out $HOME/tmp/log/swirl-service.out +``` + ### Docker Monitoring As the containers are standard docker containers, you can also monitor them via - `docker inspect` diff --git a/docker-compose.yml b/docker-compose.yml index e7ec248..88eb149 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,7 +111,19 @@ services: env_file: - .env networks: ["swirl"] - command: ["python", "manage.py", "migrate"] + command: ["python", "swirl.py", "migrate", "-v"] + + swirl-reload: + profiles: ["all", "reload"] + image: swirlai/release-swirl-search-enterprise:${SWIRL_VERSION} + container_name: swirl_app_reload_prompts + env_file: + - .env + networks: ["swirl"] + command: ["python", "swirl.py", "reload_ai_prompts"] + depends_on: + swirl: + condition: service_healthy swirl: profiles: ["all", "svc", "swirl"] @@ -130,11 +142,10 @@ services: volumes: - ./nginx/nginx.template:/nginx/nginx.template - ./scripts/swirl-load.sh:/tmp/swirl-load.sh - - ./scripts/swirl-health-check.sh:/tmp/swirl-health-check.sh - ./uploads/:/app/uploads/ networks: ["swirl"] healthcheck: - test: ["CMD-SHELL", "/tmp/swirl-health-check.sh || exit 1"] + test: ["CMD-SHELL", "/app/swirl-health-check.sh || exit 1"] interval: 30s timeout: 10s retries: 3 diff --git a/env.example b/env.example index 0262cb0..f09880c 100755 --- a/env.example +++ b/env.example @@ -22,15 +22,21 @@ CELERY_BROKER_URL="redis://redis:6379/0" CELERY_RESULT_BACKEND="redis://redis:6379/0" CSRF_TRUSTED_ORIGINS="http://localhost:8000" # CHANGE_ME GOOGLE_APPLICATION_CREDENTIALS="/app/secrets/google-credentials.json" -GOOGLE_AUTH_CLIENT_ID="" + +# Uncomment and set the GOOGLE_AUTH_CLIENT_ID if you want to use Google authentication +# GOOGLE_AUTH_CLIENT_ID="" + IN_PRODUCTION="False" LOGIN_REDIRECT_URL="" LOGOUT_REDIRECT_URL="" MICROSOFT_CLIENT_ID="" MICROSOFT_CLIENT_SECRET="" MICROSOFT_REDIRECT_URI="" -MS_AUTH_CLIENT_ID="" -MS_TENANT_ID="" + +# Uncomment and set the following if you want to use Microsoft authentication +# MS_AUTH_CLIENT_ID="" +# MS_TENANT_ID="" + OIDC_AUTHENTICATION_CALLBACK_URL="" OIDC_OP_AUTHORIZATION_ENDPOINT="" OIDC_OP_JWKS_ENDPOINT="" @@ -65,7 +71,6 @@ SWIRL_RAG_DISTRIBUTION_STRATEGY="RoundRobin" SWIRL_SVC="swirl" SWIRL_TEXT_SUMMARIZATION_URL="http://ttm:7029" TIKA_SERVER_ENDPOINT="http://tika:9998" -WEBSOCKET_TIMEOUT="90000" # Secrets ADMIN_PASSWORD="" # CHANGE_ME diff --git a/scripts/swirl-load-job.sh b/scripts/swirl-load-job.sh index f17cd34..df87a58 100755 --- a/scripts/swirl-load-job.sh +++ b/scripts/swirl-load-job.sh @@ -59,3 +59,5 @@ fi # Load Swirl's initial data python swirl.py load_data +python swirl.py reload_ai_prompts +python swirl.py load_branding diff --git a/scripts/swirl-service.sh b/scripts/swirl-service.sh index 4f07da8..6916794 100755 --- a/scripts/swirl-service.sh +++ b/scripts/swirl-service.sh @@ -6,6 +6,10 @@ set -e # Disable X11 for GUI apps to avoid DBUS-related issues export DBUS_SESSION_BUS_ADDRESS=/dev/null +# Find full path to Docker binary +export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export DOCKER_BIN="$(command -v docker)" + # Logging function function log() { echo "[$(date +%Y-%m-%dT%H:%M:%S) ${STAGE}] $1" @@ -16,10 +20,17 @@ function error() { echo "[$(date +%Y-%m-%dT%H:%M:%S) ${STAGE} ERROR] $1" } - # Ensure log directory exists and redirect output to log file -mkdir -p /var/log/swirl -exec > >(tee -a /var/log/swirl/swirl.log) 2>&1 +if [[ "$OSTYPE" == "darwin"* ]]; then + LOG_DIR="$HOME/Library/Logs/swirl" + log "Creating base log directory for MacOS: $LOG_DIR" +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + LOG_DIR="/var/log/swirl" + log "Creating base log directory for Linux: $LOG_DIR" +fi +mkdir -p "$LOG_DIR" +log "Log directory successfully created." +exec > >(tee -a "$LOG_DIR/swirl.log") 2>&1 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" log "Script directory: $SCRIPT_DIR" @@ -28,7 +39,7 @@ log "Parent directory: $PARENT_DIR" SERVICE_SETUP_FLAG="$PARENT_DIR/.swirl-service-setup-complete.flag" ENV_FILE="$PARENT_DIR/.env" -EXAMPLE_ENV_FILE="$PARENT_DIR/.env.example" +EXAMPLE_ENV_FILE="$PARENT_DIR/env.example" # Create .env file from example if not present if [ ! -f "$ENV_FILE" ]; then @@ -83,17 +94,46 @@ else fi # check for local images - if docker inspect "swirlai/release-swirl-search-enterprise:${SWIRL_VERSION}" > /dev/null 2>&1; then + if "${DOCKER_BIN}" inspect "swirlai/release-swirl-search-enterprise:${SWIRL_VERSION}" > /dev/null 2>&1; then log "Setup: Found local Swirl image swirlai/release-swirl-search-enterprise:${SWIRL_VERSION}" else log "Setup: Local Swirl image swirlai/release-swirl-search-enterprise:${SWIRL_VERSION} not found. Pulling images from Docker Hub." - docker compose --profile all pull --quiet + "${DOCKER_BIN}" compose -f $PARENT_DIR/docker-compose.yml --profile all pull --quiet fi - log "Setup: Setup starting" + log "Setup: Setup starting..." log "Setup: Enabling Swirl service to start on boot..." - if [[ "$OSTYPE" == "linux-gnu"* ]]; then + if [[ "$OSTYPE" == "darwin"* ]]; then + log "Setup: Running on macOS (user-level LaunchAgent)." + + SERVICE_FILE="$SCRIPT_DIR/com.swirl.service.plist" + REPLACEMENT="$SCRIPT_DIR/swirl-service.sh" + + log "Setup: Patching service .plist file to use $REPLACEMENT..." + sed -i '' "s|{{SWIRL_SCRIPT_PATH}}|$REPLACEMENT|g" "$SERVICE_FILE" + sed -i '' "s|{{HOME_LOG_PATH}}|$HOME|g" "$SERVICE_FILE" + log "Setup: Service .plist file successfully patched" + + log "Setup: Copying service .plist file to ~/Library/LaunchAgents/" + cp "$SERVICE_FILE" ~/Library/LaunchAgents/com.swirl.service.plist + log "Service .plist file successfully copied to ~/Library/LaunchAgents/com.swirl.service.plist" + + # Verifies if the current shell is a valid GUI session + if launchctl print "gui/$(id -u)" &>/dev/null; then + log "Setup: Session supports user-level LaunchAgent. Bootstrapping..." + + launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist 2>/dev/null || true + launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist + launchctl enable gui/$(id -u)/com.swirl.service + + log "Setup: LaunchAgent bootstrapped successfully." + log "To start Swirl manually, run the following command in a terminal: 'launchctl kickstart -k gui/\$(id -u)/com.swirl.service'" + else + log "WARNING: Current shell is not a GUI session." + log "You must manually run the following command in a terminal: 'launchctl bootstrap gui/\$(id -u) ~/Library/LaunchAgents/com.swirl.service.plist'" + fi + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then log "Setup: Running on Linux." if [ ! -f "/etc/systemd/system/swirl.service" ]; then @@ -114,7 +154,7 @@ else fi systemctl enable swirl - log "Start Service via: systemctl start swirl " + log "Start Service via: systemctl start swirl" log "Monitor Service via: journalctl -u swirl" else @@ -129,15 +169,14 @@ fi # Base profile for Swirl services COMPOSE_PROFILES=svc - # Stop previously running Swirl containers log "Stopping any Swirl containers from previous run" -docker compose --profile all stop +"${DOCKER_BIN}" compose -f $PARENT_DIR/docker-compose.yml --profile all stop # Conditionally add local Postgres if [ "$USE_LOCAL_POSTGRES" == "true" ]; then log "Local Postgres is enabled. Starting service." - (COMPOSE_PROFILES=db docker compose up --pull never -d) + (COMPOSE_PROFILES=db "${DOCKER_BIN}" compose -f $PARENT_DIR/docker-compose.yml up --pull never -d) log "Started local Postgres service." sleep 15 fi @@ -166,8 +205,6 @@ if [ "$USE_NGINX" == "true" ]; then log "DNS name $SWIRL_FQDN resolved after $WAITED seconds." - - TEMPLATE_FILE="$PARENT_DIR/nginx/nginx-template.tls" UPDATE_MARKER="# swirl-service updated: USE_TLS=true, USE_CERT=false" @@ -261,8 +298,9 @@ fi ONETIME_JOB_FLAG="$PARENT_DIR/.swirl-application-setup-job-complete.flag" if [ -f "$ONETIME_JOB_FLAG" ]; then log "Application setup job already completed. Skipping initial setup." + COMPOSE_PROFILES="$COMPOSE_PROFILES,reload" else - log "Setiing up run one-time application setup job..." + log "Setting up run one-time application setup job..." # Run the initial setup job COMPOSE_PROFILES="$COMPOSE_PROFILES,setup" touch "$ONETIME_JOB_FLAG" @@ -270,4 +308,4 @@ fi # Final startup log "Docker Compose Up with profiles: $COMPOSE_PROFILES" -COMPOSE_PROFILES=$COMPOSE_PROFILES docker compose up --pull never \ No newline at end of file +COMPOSE_PROFILES=$COMPOSE_PROFILES "${DOCKER_BIN}" compose -f $PARENT_DIR/docker-compose.yml up --pull never From 9a8bff4aeac720bf990109d0c39debf3f0773304 Mon Sep 17 00:00:00 2001 From: Peter Kahn Date: Thu, 28 Aug 2025 11:20:16 -0400 Subject: [PATCH 2/3] missed file --- scripts/com.swirl.service.plist | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 scripts/com.swirl.service.plist diff --git a/scripts/com.swirl.service.plist b/scripts/com.swirl.service.plist new file mode 100644 index 0000000..2955319 --- /dev/null +++ b/scripts/com.swirl.service.plist @@ -0,0 +1,26 @@ + + + + + Label + com.swirl.service + + ProgramArguments + + {{SWIRL_SCRIPT_PATH}} + + + KeepAlive + + + RunAtLoad + + + StandardOutPath + {{HOME_LOG_PATH}}/tmp/log/swirl-service.out + + StandardErrorPath + {{HOME_LOG_PATH}}/tmp/log/swirl-service.err + + From 8cd038afdc16fd69f6273f137b86d138da915ae4 Mon Sep 17 00:00:00 2001 From: Peter Kahn Date: Thu, 28 Aug 2025 11:20:56 -0400 Subject: [PATCH 3/3] remove defunct file --- scripts/swirl-health-check.sh | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100755 scripts/swirl-health-check.sh diff --git a/scripts/swirl-health-check.sh b/scripts/swirl-health-check.sh deleted file mode 100755 index 64e380f..0000000 --- a/scripts/swirl-health-check.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 - -import requests - -try: - headers = { - "Content-Type": "application/json", - "Host": "localhost" - } - response = requests.get("http://localhost:8000/swirl/health/celery/", headers=headers) - response.raise_for_status() # Raise an exception for HTTP errors - print("Health check passed") -except requests.exceptions.RequestException as e: - print(f"Health check failed: {e}") - exit(1) \ No newline at end of file