Skip to content

Commit 8436fe2

Browse files
committed
feat: add omni app
1 parent eacc29f commit 8436fe2

9 files changed

Lines changed: 871 additions & 33 deletions

File tree

devenv.nix

Lines changed: 100 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
1-
{ pkgs, ... }:
1+
{
2+
pkgs,
3+
lib,
4+
...
5+
}:
26
let
3-
allComponents = "chat image-analysis flux-image-gen";
47
imagePrefix = "ghcr.io/stackhpc/azimuth-llm";
5-
webAppsDir = "./web-apps";
8+
webAppsDir = ./web-apps;
9+
10+
# Discover components: subdirs of web-apps/ that contain a Dockerfile.
11+
# Keeps `build`, `scan`, and the help text in sync with the filesystem
12+
# so adding a web-app requires no edits here.
13+
componentList =
14+
let
15+
entries = builtins.readDir webAppsDir;
16+
isComponent =
17+
name: entries.${name} == "directory" && builtins.pathExists (webAppsDir + "/${name}/Dockerfile");
18+
in
19+
lib.sort lib.lessThan (lib.filter isComponent (builtins.attrNames entries));
620

7-
# Resolves "all" or empty arg to the full list, validates otherwise.
8-
resolveComponents = ''
9-
ALL_COMPONENTS="${allComponents}"
21+
# Shell prelude shared by every script that operates on components.
22+
# Provides:
23+
# resolve_components <input> - echo input (validated) or all components
24+
# image_name <component> - echo the full image reference
25+
componentPrelude = ''
26+
ALL_COMPONENTS="${lib.concatStringsSep " " componentList}"
1027
1128
resolve_components() {
1229
local input="$1"
1330
if [ -z "$input" ] || [ "$input" = "all" ]; then
1431
echo "$ALL_COMPONENTS"
15-
else
16-
for c in $input; do
17-
if ! echo " $ALL_COMPONENTS " | grep -q " $c "; then
18-
echo "Unknown component: $c" >&2
19-
echo "Available: $ALL_COMPONENTS" >&2
20-
return 1
21-
fi
22-
done
23-
echo "$input"
32+
return 0
2433
fi
34+
for c in $input; do
35+
if ! echo " $ALL_COMPONENTS " | grep -q " $c "; then
36+
echo "Unknown component: $c" >&2
37+
echo "Available: $ALL_COMPONENTS" >&2
38+
return 1
39+
fi
40+
done
41+
echo "$input"
2542
}
2643
2744
image_name() {
@@ -44,12 +61,29 @@ in
4461
# CI tooling
4562
jq
4663
yq-go
47-
# Python
64+
curl
65+
# Python toolchain.
66+
# devenv is NOT the source of truth for Python deps: the pinned
67+
# requirements.txt in each web-app is, and uv installs from it.
68+
# This guarantees the devenv .venv matches the Dockerfile-built image.
69+
# The Dockerfile must use the same Python minor version.
4870
python311
71+
uv
4972
ruff
5073
black
5174
];
5275

76+
# PyPI wheels (numpy, scipy, pillow, ...) are linked against system libs
77+
# that don't exist on NixOS at standard FHS paths. Expose them via
78+
# LD_LIBRARY_PATH so the wheels' C extensions can load.
79+
env.LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
80+
pkgs.stdenv.cc.cc.lib # libstdc++.so.6, libgcc_s.so.1
81+
pkgs.zlib # libz.so.1 (numpy, scipy, pillow)
82+
pkgs.libjpeg # libjpeg.so (pillow)
83+
pkgs.libpng # libpng.so (pillow)
84+
pkgs.libwebp # libwebp.so (pillow)
85+
];
86+
5387
treefmt = {
5488
enable = true;
5589
config.programs = {
@@ -69,7 +103,8 @@ in
69103

70104
scripts = {
71105
build.exec = ''
72-
${resolveComponents}
106+
${componentPrelude}
107+
73108
TAG="latest"
74109
COMPONENT=""
75110
while [ $# -gt 0 ]; do
@@ -78,20 +113,20 @@ in
78113
*) COMPONENT="$COMPONENT $1"; shift ;;
79114
esac
80115
done
81-
COMPONENT="''${COMPONENT## }"
82116
83-
TARGETS=$(resolve_components "$COMPONENT") || exit 1
117+
TARGETS=$(resolve_components "''${COMPONENT## }") || exit 1
84118
for c in $TARGETS; do
85119
echo "==> Building $c (tag: $TAG)"
86120
docker build \
87121
-t "$(image_name "$c"):$TAG" \
88-
-f ${webAppsDir}/"$c"/Dockerfile \
89-
${webAppsDir}/
122+
-f ./web-apps/"$c"/Dockerfile \
123+
./web-apps/
90124
done
91125
'';
92126

93127
scan.exec = ''
94-
${resolveComponents}
128+
${componentPrelude}
129+
95130
TAG="latest"
96131
FAIL_ON="critical"
97132
COMPONENT=""
@@ -102,32 +137,68 @@ in
102137
*) COMPONENT="$COMPONENT $1"; shift ;;
103138
esac
104139
done
105-
COMPONENT="''${COMPONENT## }"
106140
107-
TARGETS=$(resolve_components "$COMPONENT") || exit 1
141+
TARGETS=$(resolve_components "''${COMPONENT## }") || exit 1
108142
EXIT=0
109143
for c in $TARGETS; do
110144
build "$c" --tag "$TAG"
111-
112145
IMAGE="$(image_name "$c"):$TAG"
113146
echo ""
114147
echo "==> Scanning $IMAGE (fail-on: $FAIL_ON)"
115-
if ! grype "$IMAGE" --fail-on "$FAIL_ON" --only-fixed; then
116-
EXIT=1
117-
fi
148+
grype "$IMAGE" --fail-on "$FAIL_ON" --only-fixed || EXIT=1
118149
done
119150
exit $EXIT
120151
'';
152+
153+
# ---------------- omni helpers ----------------
154+
#
155+
# omni-venv installs omni's Python deps into web-apps/omni/.venv from
156+
# requirements.txt, re-running only when requirements.txt is newer than
157+
# the install stamp. The `../utils` line in requirements.txt is resolved
158+
# by uv as a local path dep (same mechanism the Dockerfile uses, modulo
159+
# the sed rewrite).
160+
#
161+
# omni-run ensures the venv is up to date, activates it, and launches
162+
# the Gradio UI.
163+
164+
omni-venv.exec = ''
165+
set -e
166+
cd ./web-apps/omni
167+
STAMP=.venv/.installed
168+
if [ ! -d .venv ] || [ requirements.txt -nt "$STAMP" ]; then
169+
echo "==> Creating/refreshing ./web-apps/omni/.venv (Python 3.11)"
170+
uv venv --python 3.11 .venv
171+
# shellcheck disable=SC1091
172+
. .venv/bin/activate
173+
echo "==> Installing dependencies from requirements.txt"
174+
uv pip install -r requirements.txt
175+
touch "$STAMP"
176+
fi
177+
'';
178+
179+
omni-run.exec = ''
180+
set -e
181+
omni-venv
182+
cd ./web-apps/omni
183+
# shellcheck disable=SC1091
184+
. .venv/bin/activate
185+
echo "==> Starting Omni interface on http://localhost:7860"
186+
exec python app.py "$@"
187+
'';
121188
};
122189

123190
enterShell = ''
124191
echo "$GREET"
125192
echo ""
126-
echo "Commands (component = chat | image-analysis | flux-image-gen | omit for all):"
193+
echo "Commands (component = ${lib.concatStringsSep " | " componentList} | omit for all):"
127194
echo ""
128195
echo " prek -a Format/lint all files"
129196
echo " build [component] [--tag TAG] Build container image(s)"
130197
echo " scan [component] [--tag TAG] [--fail-on SEV] Build if needed + Grype scan"
131198
echo ""
199+
echo "Omni (multimodal UI) helpers:"
200+
echo ""
201+
echo " omni-run Start the Omni Gradio UI on :7860"
202+
echo ""
132203
'';
133204
}

web-apps/chat/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
22

3-
RUN apt-get update && apt-get install -y libssl3=3.0.19-1~deb12u2 openssl=3.0.19-1~deb12u2 && rm -rf /var/lib/apt/lists/*
3+
RUN apt-get update && apt-get install -y libssl3=3.0.19-1~deb12u2 openssl=3.0.19-1~deb12u2 libgnutls30=3.7.9-2+deb12u7 && rm -rf /var/lib/apt/lists/*
44

55
ARG DIR=chat
66

web-apps/flux-image-gen/Dockerfile

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,50 @@
11
FROM ghcr.io/astral-sh/uv:python3.11-trixie
22

3-
# https://stackoverflow.com/questions/55313610/importerror-libgl-so-1-cannot-open-shared-object-file-no-such-file-or-directo
3+
ARG IMAGEMAGICK_VERSION=8:7.1.1.43+dfsg1-1+deb13u9
4+
ARG LIBUNBOUND_VERSION=1.22.0-2+deb13u3
5+
ARG KRB5_VERSION=1.21.3-5+deb13u1
6+
ARG LIBGCRYPT_VERSION=1.11.0-7+deb13u1
47
RUN apt-get update && \
5-
apt-get install -y ffmpeg libsm6 libxext6 && \
8+
apt-get install -y --no-install-recommends \
9+
ffmpeg \
10+
libsm6 \
11+
libxext6 \
12+
"imagemagick=${IMAGEMAGICK_VERSION}" \
13+
"imagemagick-7-common=${IMAGEMAGICK_VERSION}" \
14+
"imagemagick-7.q16=${IMAGEMAGICK_VERSION}" \
15+
"libmagickcore-7-arch-config=${IMAGEMAGICK_VERSION}" \
16+
"libmagickcore-7-headers=${IMAGEMAGICK_VERSION}" \
17+
"libmagickcore-7.q16-10=${IMAGEMAGICK_VERSION}" \
18+
"libmagickcore-7.q16-10-extra=${IMAGEMAGICK_VERSION}" \
19+
"libmagickcore-7.q16-dev=${IMAGEMAGICK_VERSION}" \
20+
"libmagickcore-dev=${IMAGEMAGICK_VERSION}" \
21+
"libmagickwand-7-headers=${IMAGEMAGICK_VERSION}" \
22+
"libmagickwand-7.q16-10=${IMAGEMAGICK_VERSION}" \
23+
"libmagickwand-7.q16-dev=${IMAGEMAGICK_VERSION}" \
24+
"libmagickwand-dev=${IMAGEMAGICK_VERSION}" \
25+
"libunbound8=${LIBUNBOUND_VERSION}" \
26+
"krb5-multidev=${KRB5_VERSION}" \
27+
"libgssapi-krb5-2=${KRB5_VERSION}" \
28+
"libgssrpc4t64=${KRB5_VERSION}" \
29+
"libk5crypto3=${KRB5_VERSION}" \
30+
"libkadm5clnt-mit12=${KRB5_VERSION}" \
31+
"libkadm5srv-mit12=${KRB5_VERSION}" \
32+
"libkdb5-10t64=${KRB5_VERSION}" \
33+
"libkrb5-3=${KRB5_VERSION}" \
34+
"libkrb5-dev=${KRB5_VERSION}" \
35+
"libkrb5support0=${KRB5_VERSION}" \
36+
"libgcrypt20=${LIBGCRYPT_VERSION}" && \
37+
apt-get clean && \
638
rm -rf /var/lib/apt/lists/*
739

840

941
ARG DIR=flux-image-gen
1042

43+
RUN uv pip install --system --no-cache-dir --upgrade \
44+
pip \
45+
setuptools \
46+
wheel
47+
1148
COPY $DIR/requirements.txt requirements.txt
1249
RUN uv pip install --system --no-cache-dir -r requirements.txt
1350

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
flux[gradio] @ git+https://github.com/black-forest-labs/flux@478338d
22
fastapi[standard]
33
httpx
4+
urllib3>=2.7.0
5+
idna>=3.15
46
# ../utils

web-apps/image-analysis/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
22

33
RUN apt-get update && \
4-
apt-get install -y --only-upgrade libssl3=3.0.19-1~deb12u2 openssl=3.0.19-1~deb12u2 && \
4+
apt-get install -y --only-upgrade libssl3=3.0.19-1~deb12u2 openssl=3.0.19-1~deb12u2 \
5+
libgnutls30=3.7.9-2+deb12u7 && \
56
rm -rf /var/lib/apt/lists/*
67

78
ARG DIR=image-analysis

web-apps/omni/Dockerfile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
2+
3+
RUN apt-get update && apt-get install -y \
4+
libssl3=3.0.19-1~deb12u2 \
5+
openssl=3.0.19-1~deb12u2 \
6+
libgnutls30=3.7.9-2+deb12u7 \
7+
&& rm -rf /var/lib/apt/lists/*
8+
9+
ARG DIR=omni
10+
11+
COPY $DIR/requirements.txt requirements.txt
12+
RUN sed -i s$../utils$./utils$ requirements.txt
13+
COPY utils utils
14+
RUN uv pip install --system --no-cache-dir -r requirements.txt
15+
16+
COPY purge-google-fonts.sh purge-google-fonts.sh
17+
RUN bash purge-google-fonts.sh
18+
19+
WORKDIR /app
20+
21+
COPY $DIR/*.py .
22+
23+
COPY $DIR/defaults.yml .
24+
25+
ENTRYPOINT ["python3", "app.py"]

0 commit comments

Comments
 (0)