diff --git a/Containerfile.add_llama_to_lightspeed b/Containerfile.add_llama_to_lightspeed index ec0fd87..2ad68f0 100644 --- a/Containerfile.add_llama_to_lightspeed +++ b/Containerfile.add_llama_to_lightspeed @@ -11,6 +11,9 @@ RUN cd /app-root/llama-stack && python3.12 -m pip install --editable . RUN cd /app-root/ && python3.12 -m pip install . +COPY migrate.py /app/migrate.py +ENTRYPOINT ["/bin/sh", "-c", "python3.12 /app/migrate.py && python3.12 src/lightspeed_stack.py"] + USER 1001 EXPOSE 8080 diff --git a/Containerfile.assisted-chat b/Containerfile.assisted-chat index f99cb81..552c219 100644 --- a/Containerfile.assisted-chat +++ b/Containerfile.assisted-chat @@ -2,6 +2,9 @@ # This is the digest of quay.io/lightspeed-core/lightspeed-stack:0.3.0 FROM quay.io/lightspeed-core/lightspeed-stack@sha256:d1805df92f4de55d662e6274328830e2c1300f308c258abfcfad825a241cb50d +COPY migrate.py /app/migrate.py +ENTRYPOINT ["/bin/sh", "-c", "python3.12 /app/migrate.py && python3.12 src/lightspeed_stack.py"] + USER 1001 EXPOSE 8080 diff --git a/assisted-chat-pod.yaml b/assisted-chat-pod.yaml index bcc4645..910bd8b 100644 --- a/assisted-chat-pod.yaml +++ b/assisted-chat-pod.yaml @@ -21,6 +21,8 @@ spec: value: assisted-chat - name: ASSISTED_CHAT_POSTGRES_NAME value: assisted-chat + - name: LIGHTSPEED_STACK_POSTGRES_SSL_MODE + value: ${LIGHTSPEED_STACK_POSTGRES_SSL_MODE} ports: - containerPort: 8090 hostPort: 8090 @@ -109,9 +111,10 @@ spec: value: assisted-chat ports: - containerPort: 5432 + hostPort: 5432 volumeMounts: - name: pgdata - mountPath: /var/lib/pgsql/data + mountPath: /var/lib/pgsql/data:Z volumes: - name: config hostPath: @@ -119,3 +122,7 @@ spec: type: Directory - name: pgdata emptyDir: {} + # Uncomment this and comment out emptyDir to persist data between pod restarts + # hostPath: + # path: /home/$USER/.local/share/assisted-chat/pgdata + # type: DirectoryOrCreate diff --git a/migrate.py b/migrate.py new file mode 100644 index 0000000..353a7d9 --- /dev/null +++ b/migrate.py @@ -0,0 +1,66 @@ +""" +This script connects to a PostgreSQL database and performs migrations. + +This is because lightspeed-stack does not currently perform migrations on its own, +which means the database either has to be created from scratch or migrated like we do here. + +Any migrations added should be idempotent, meaning they can be ran multiple times without +causing errors or unintended effects. This is because we run this script every time the +service starts to ensure the database is up to date. + +Currently the migrations are as follows: + +1. Add a new column `topic_summary` as it was added in lightspeed-stack v0.3.0 + +WARNING: This script assumes that the database is postgres and that the schema used is +called `lightspeed-stack`. If either of these assumptions are incorrect, the script may fail +or cause unintended effects. lightspeed-stack could also use sqlite or a different schema +if configured to do so, but we don't handle those cases here because we don't use them. +""" + +import os +import time +import sys + +import psycopg2 + +for _ in range(30): + try: + conn = psycopg2.connect( + host=os.getenv("ASSISTED_CHAT_POSTGRES_HOST"), + port=os.getenv("ASSISTED_CHAT_POSTGRES_PORT"), + dbname=os.getenv("ASSISTED_CHAT_POSTGRES_NAME"), + user=os.getenv("ASSISTED_CHAT_POSTGRES_USER"), + password=os.getenv("ASSISTED_CHAT_POSTGRES_PASSWORD"), + sslmode=os.getenv("LIGHTSPEED_STACK_POSTGRES_SSL_MODE"), + ) + break + except psycopg2.OperationalError as e: + print("Waiting for Postgres...", e, file=sys.stderr) + time.sleep(2) +else: + sys.exit("Postgres not available after 60s") + + +# Ensure the schema even exists, if it doesn't, it's a fresh database and +# we don't need to run migrations +with conn.cursor() as cur: + cur.execute( + "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'lightspeed-stack'" + ) + if not cur.fetchone(): + print( + "Schema 'lightspeed-stack' absent, database probably fresh, skipping migrations" + ) + conn.close() + sys.exit(0) + + +cur = conn.cursor() +cur.execute( + 'ALTER TABLE "lightspeed-stack"."user_conversation" ADD COLUMN IF NOT EXISTS topic_summary text' +) +conn.commit() +cur.close() +conn.close() +print("Migration completed") diff --git a/scripts/run.sh b/scripts/run.sh index 4c1424d..05db9a4 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -24,6 +24,7 @@ else fi set -a && source "$PROJECT_ROOT/.env" && set +a +set -a && source "$PROJECT_ROOT/template-params.dev.env" && set +a export LIGHTSPEED_STACK_IMAGE_OVERRIDE="${LIGHTSPEED_STACK_IMAGE_OVERRIDE:-localhost/local-ai-chat-lightspeed-stack-plus-llama-stack}" # Validate and export OCM tokens for use in pod configuration @@ -31,6 +32,17 @@ if ! export_ocm_token; then echo "Failed to get OCM tokens. The UI container will not be able to authenticate with OCM." exit 1 fi -podman play kube --build=false <(envsubst < "$PROJECT_ROOT"/assisted-chat-pod.yaml) + +# This is conditional because it's super slow for some reason. If the user +# doesn't have a hostPath volume for pgdata, we don't need it anyway +if <"$PROJECT_ROOT/assisted-chat-pod.yaml" yq | jq '.spec.volumes[] | select(.name == "pgdata").hostPath != null' --exit-status; then + # Map the PostgreSQL user (UID 26) inside the container to the current host user + # This allows the PostgreSQL container to write to host-mounted volumes without permission issues + POSTGRES_USER_ID=26 + POSTGRES_GROUP_ID=26 + podman play kube --build=false --userns=keep-id:uid=$POSTGRES_USER_ID,gid=$POSTGRES_GROUP_ID <(envsubst <"$PROJECT_ROOT"/assisted-chat-pod.yaml) +else + podman play kube --build=false <(envsubst <"$PROJECT_ROOT"/assisted-chat-pod.yaml) +fi "$SCRIPT_DIR/logs.sh"