diff --git a/.env.TEMPLATE b/.env.TEMPLATE index da917a4..6992744 100644 --- a/.env.TEMPLATE +++ b/.env.TEMPLATE @@ -58,3 +58,9 @@ FLAGEMBEDDING_CACHE_DIR="/opt/model_cache/" # Ollama API host, running on the host machine OLLAMA_API_BASE="http://localhost:11434" + +POSTGRES_USER="postgres" +POSTGRES_PASSWORD= +POSTGRES_HOST="db" +POSTGRES_PORT="5432" +POSTGRES_DB="manufold" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 04aa832..cacd015 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-yaml - id: detect-private-key - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.16.2" + rev: "v2.18.1" hooks: - id: pyproject-fmt - repo: https://github.com/citation-file-format/cffconvert diff --git a/README.md b/README.md index b7ee852..c970430 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ MANUGENAI_FIGURE_MODEL_NAME="ollama/gemma3:4b" Run the following command: ```bash -docker compose up --build +./run_stack.sh ``` This will build the Docker images and start the application. @@ -234,7 +234,7 @@ Visit http://localhost:8900 in your web browser to access the backend API; docs If you want to clear the session state, you can run the following command to stop any application containers that are running and remove the database volume: ```bash -docker compose down -v +./run_stack.sh down -v ``` This will purge any sessions or other data that ADK stores to the database. @@ -257,15 +257,19 @@ The production configuration differs from the development configuration in a few To launch the production version of the app, you can run the following command: ```bash -docker compose -f docker-compose.yml -f docker-compose.prod.yml up --build -d +./run_stack.sh prod ``` +(You can also change `DEFAULT_ENV` in your `.env` file to `prod` and then just run `./run_stack.sh` without the `prod` argument.) + This will build the production images and start the application in detached mode. -To tail the container logs, you can run: +It will then tail the logs of all the containers, but you can exit by pressing `Ctrl+C` (the containers will keep running in the background). + +To bring down the production app, you can run the following command: ```bash -docker compose -f docker-compose.yml -f docker-compose.prod.yml logs -f +./run_stack.sh prod down ``` - *For project members: [internal planning doc](https://olucdenver.sharepoint.com/:w:/r/sites/CenterforHealthAI939-SoftwareEngineering/Shared%20Documents/Software%20Engineering/Projects/PivLab%20-%20ADK%20Hackathon/Agent%20Development%20Kit%20Hackathon%20with%20Google%20Cloud.docx?d=w0cfff935f2754c3492489ef5b15fe2f4&csf=1&web=1&e=NRM3en)* diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 44787ea..63b12c0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -9,6 +9,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] dependencies = [ + "asyncpg>=0.31", "fastapi>=0.104", "manufold", "uvicorn[standard]>=0.24", diff --git a/backend/uv.lock b/backend/uv.lock index 39442fe..bd6e291 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -107,6 +107,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, ] +[[package]] +name = "asyncpg" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, + { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, + { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, +] + [[package]] name = "attrs" version = "25.3.0" @@ -1383,6 +1399,7 @@ name = "manufold-backend" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "asyncpg" }, { name = "fastapi" }, { name = "manufold" }, { name = "uvicorn", extra = ["standard"] }, @@ -1390,6 +1407,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "asyncpg", specifier = ">=0.31.0" }, { name = "fastapi", specifier = ">=0.104" }, { name = "manufold", directory = "../packages/manufold" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.24" }, diff --git a/docker-compose.with-pg.yml b/docker-compose.with-pg.yml index 3681ec9..ad730fa 100644 --- a/docker-compose.with-pg.yml +++ b/docker-compose.with-pg.yml @@ -16,7 +16,7 @@ services: backend: environment: # yamllint disable-line rule:line-length - - "SESSION_DB_CONN_STRING=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" + - "SESSION_DB_CONN_STRING=postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" depends_on: - db diff --git a/run_stack.sh b/run_stack.sh new file mode 100755 index 0000000..dc1a5fa --- /dev/null +++ b/run_stack.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +# this script manages compose configurations for different environments +# and ensures the necessary environment variables are set up correctly. + +# invoke it like so: +# +# ./run_stack.sh [dev|prod] [docker-compose command options] +# +# - the first argument is the environment (default: dev) if the first arg isn't +# provided or doesn't match 'dev' or 'prod', it defaults to 'dev'. +# - the remainder of the arguments are passed to the `docker compose` command. +# if no arguments are provided, it defaults to an environment-specific +# command, e.g. `docker compose up --build` for dev or `docker compose up +# --build -d` for prod. + +# ------------------------------------ +# --- preamble: .env file validation, sourcing +# ------------------------------------ + +# check if `.env` is present, and if not copy it from `.env.TEMPLATE` +# and fill in passwords with random values +if [ ! -f .env ]; then + echo ".env file not found, copying from .env.TEMPLATE" + cp .env.TEMPLATE .env +fi + +# ensure a value for the postgres db's password exists, and if not, generate a random one +RANDOM_PG_PASSWORD=$(openssl rand -base64 32 | tr -d '/=') +perl -i -pe"s/^POSTGRES_PASSWORD=$/POSTGRES_PASSWORD=\"${RANDOM_PG_PASSWORD}\"/g" .env + +# source the .env file and export its contents to the environment +set -a +source .env +set +a + +# ------------------------------------ +# --- environment resolution +# ------------------------------------ + +# sets the default environment to 'dev' if not specified via DEFAULT_ENV +DEFAULT_ENV=${DEFAULT_ENV:-dev} +# defaults to a no-op command to run after docker compose +POST_COMMAND=":" + +if [ -z "$1" ] || [[ ! "$1" =~ ^(dev|prod)$ ]]; then + echo "No environment specified, using default: $DEFAULT_ENV" + export ENV=$DEFAULT_ENV +else + # use the first argument as the environment + export ENV="$1"; shift + echo "Using specified environment: $ENV" +fi + +# set up compose files and commands based on the environment +case $ENV in + dev) + COMPOSE_FILES="-f docker-compose.yml -f docker-compose.override.yml" + COMPOSE_CMD="up --build" + ;; + prod) + COMPOSE_FILES="-f docker-compose.yml -f docker-compose.with-pg.yml -f docker-compose.prod.yml" + COMPOSE_CMD="up --build -d" + # tail all container logs in production + POST_COMMAND="docker compose ${COMPOSE_FILES} logs -f" + ;; + *) + echo "Unknown environment: $ENV, aborting" + exit 1 + ;; +esac + + +# ------------------------------------ +# --- execute docker compose command +# ------------------------------------ + +# if we have any remaining arguments, override the compose command with them +if [ $# -gt 0 ]; then + echo "Overriding compose command with: $*" + COMPOSE_CMD="$*" +fi + +docker compose ${COMPOSE_FILES} ${COMPOSE_CMD} && \ +${POST_COMMAND}