-
Notifications
You must be signed in to change notification settings - Fork 477
Expand file tree
/
Copy pathJustfile
More file actions
405 lines (359 loc) · 17.6 KB
/
Copy pathJustfile
File metadata and controls
405 lines (359 loc) · 17.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#!/usr/bin/env just --justfile
import "recipes.justfile"
# -------------------------------------------------------------------------------------------------
# Build variables
# -------------------------------------------------------------------------------------------------
docker-build-context := justfile_directory() / ".."
docker-build-args := "--build-arg http_proxy --build-arg https_proxy --build-arg no_proxy --build-arg HTTP_PROXY --build-arg HTTPS_PROXY --build-arg NO_PROXY"
application-name := "geti"
# -------------------------------------------------------------------------------------------------
# Runtime variables
# -------------------------------------------------------------------------------------------------
webrtc-ports := "50100-50109"
coturn-port := "443"
coturn-image := "quay.io/coturn/coturn"
data-volume := application-name + "-data"
logs-volume := application-name + "-logs"
# Build Geti Docker image for a specific training backend
[arg("accelerator", long="accelerator", short="a", pattern="cpu|xpu|cuda")]
[arg("tag", long="tag", short="t")]
[arg("exclude-agpl-models", long="exclude-agpl-models", value="true", help="Exclude Ultralytics and other AGPL-licensed models from the image")]
build-image accelerator="cpu" tag="latest" exclude-agpl-models="false":
#!/usr/bin/env bash
set -euo pipefail
IMAGE="{{ application-name }}-{{ accelerator }}"
CACHE_FROM_ARGS=""
if [[ -n "${BUILDX_CACHE_FROM:-}" ]]; then
for src in ${BUILDX_CACHE_FROM}; do
CACHE_FROM_ARGS="${CACHE_FROM_ARGS} --cache-from ${src}"
done
fi
CACHE_TO_ARGS=""
if [[ -n "${BUILDX_CACHE_TO:-}" ]]; then
for dst in ${BUILDX_CACHE_TO}; do
CACHE_TO_ARGS="${CACHE_TO_ARGS} --cache-to ${dst}"
done
fi
TAG_ARGS="--tag ${IMAGE}:{{ tag }}"
EXCLUDE_AGPL_MODELS_ARG=""
if [[ "{{ exclude-agpl-models }}" == "true" ]]; then
EXCLUDE_AGPL_MODELS_ARG="--build-arg EXCLUDE_AGPL_MODELS=1"
fi
echo "Building docker image for accelerator: {{ accelerator }}"
docker buildx build \
{{ docker-build-args }} \
{{ docker-build-context }} \
-f "{{ justfile_directory() }}/docker/Dockerfile" \
--ulimit nofile=1048576:1048576 \
--target "${IMAGE}" \
${TAG_ARGS} \
${CACHE_FROM_ARGS} \
${CACHE_TO_ARGS} \
${EXCLUDE_AGPL_MODELS_ARG} \
--load
# Run Geti as a Docker container for a specific training backend
[arg("accelerator", long="accelerator", short="a", pattern="cpu|xpu|cuda")]
[arg("tag", long="tag", short="t")]
[arg("port", long="port", short="p", help="Host port to publish (forwarded to the container's internal port)")]
[arg("container-port", long="container-port", help="Container-internal port the server listens on")]
[arg("container-name", long="name", short="n", help="Custom name for the container (defaults to geti-<accelerator>)")]
[arg("detach", long="detach", short="d", value="true", help="Run the container in detached mode")]
[arg("remove", long="remove", value="true", help="Automatically remove the container after it exits")]
[arg("reload", long="reload", short="r", value="true", help="Automatically reload the container if it is already running")]
[arg("shm-size", long="shm-size", help="Size of /dev/shm inside the container (e.g. 256m, 4g). Defaults to 8g")]
[arg("passthrough-devices", long="passthrough-devices", help="Space-separated list of additional device paths to pass through to the container (e.g. '/dev/video0 /dev/video1')")]
[arg("volumes", long="volumes", help="Space-separated list of host:container path mappings to mount (e.g. '/host/path:/container/path /host/path2:/container/path2')")]
[arg("enable_coturn", long="coturn", help="Enable coturn server for WebRTC relay (only needed if the server is behind a restrictive NAT)")]
[arg("coturn_host", long="coturn-host", help="Coturn server listening host (default: server's public IP)")]
[arg("coturn_port", long="coturn-port", help="Coturn server listening port (default: 443)")]
[arg("stun_server", long="stun", help="STUN server URL to use for WebRTC")]
[arg("gpu-slots", long="gpu-slots", help="Number of GPU slots available for model tuning (default: 1)")]
[arg("certfile", long="certfile", help="Path to TLS certificate file for HTTPs (optional, for secure hosting)")]
[arg("keyfile", long="keyfile", help="Path to TLS key file for HTTPs (optional, for secure hosting)")]
run-image accelerator="cpu" tag="latest" port="7860" container-port="7860" container-name="" detach="false" \
remove="false" reload="false" shm-size="8g" passthrough-devices="" volumes="" enable_coturn="false" coturn_host="" \
coturn_port=coturn-port stun_server="" gpu-slots="1" certfile="" keyfile="":
#!/usr/bin/env bash
set -euo pipefail
shopt -s nullglob
IMAGE_NAME="{{ application-name }}-{{ accelerator }}:{{ tag }}"
if [[ -n "{{ container-name }}" ]]; then
CONTAINER_NAME="{{ container-name }}"
else
CONTAINER_NAME="{{ application-name }}-{{ accelerator }}"
fi
if ! docker image inspect "${IMAGE_NAME}" > /dev/null 2>&1; then
echo "Error: Docker image '${IMAGE_NAME}' not found." >&2
echo "Build it first with: just build-image --accelerator {{ accelerator }} --tag {{ tag }}" >&2
exit 1
fi
EXISTING_CONTAINER_ID=$(docker ps -aq -f "name=^/${CONTAINER_NAME}$")
if [[ -n "$EXISTING_CONTAINER_ID" ]]; then
if [[ "{{ reload }}" == "true" ]]; then
# If a container with this name is running, stop it first
RUNNING_CONTAINER_ID=$(docker ps -q -f "name=^/${CONTAINER_NAME}$")
if [[ -n "$RUNNING_CONTAINER_ID" ]]; then
echo "Stopping existing running container: ${CONTAINER_NAME}"
docker stop "$RUNNING_CONTAINER_ID"
fi
echo "Removing existing container: ${CONTAINER_NAME}"
docker rm -f "${CONTAINER_NAME}" > /dev/null 2>&1 || true
# Docker can take a moment to fully deregister the container name
# (e.g. for containers auto-removed after `docker stop`), so wait
# until the name is actually free before proceeding, to avoid a
# "Conflict. The container name is already in use" race.
for _ in {1..20}; do
if ! docker container inspect "${CONTAINER_NAME}" > /dev/null 2>&1; then
break
fi
sleep 0.5
done
if docker container inspect "${CONTAINER_NAME}" > /dev/null 2>&1; then
echo "Warning: container name '${CONTAINER_NAME}' may not be fully released yet." >&2
fi
else
echo "Error: A container named '${CONTAINER_NAME}' is already running." >&2
echo "Either pass --reload to stop and replace it, or specify a different --name." >&2
exit 1
fi
fi
run_args=(
--env http_proxy
--env https_proxy
--env no_proxy
--env HTTP_PROXY
--env HTTPS_PROXY
--env NO_PROXY
--publish "{{ webrtc-ports }}:{{ webrtc-ports }}/udp"
--publish "{{ port }}:{{ container-port }}"
--env "HOST=0.0.0.0"
--env "PORT={{ container-port }}"
--env "GPU_SLOTS={{ gpu-slots }}"
--sysctl "net.ipv4.ip_local_port_range={{ replace(webrtc-ports, '-', ' ') }}"
--volume "{{ data-volume }}:/application/data/"
--volume "{{ logs-volume }}:/application/logs/"
--name "$CONTAINER_NAME"
)
if [[ "{{ detach }}" == "true" ]]; then
run_args+=(--detach)
fi
if [[ "{{ remove }}" == "true" ]]; then
run_args+=(--rm)
fi
HOST_IP=$(just resolve-host-ip)
if [[ "{{ enable_coturn }}" == "true" ]]; then
if [[ -n "{{ coturn_host }}" ]]; then
COTURN_HOST="{{ coturn_host }}"
else
COTURN_HOST="$HOST_IP"
fi
run_args+=(--env "COTURN_HOST=$COTURN_HOST" --env "COTURN_PORT={{ coturn_port }}")
fi
if [[ -n "{{ stun_server }}" ]]; then
run_args+=(--env "STUN_SERVER={{ stun_server }}")
fi
if [[ -n "{{ certfile }}" ]]; then
run_args+=(--env "CERTFILE={{ certfile }}")
fi
if [[ -n "{{ keyfile }}" ]]; then
run_args+=(--env "KEYFILE={{ keyfile }}")
fi
run_args+=(--env "WEBRTC_ADVERTISE_IP=$HOST_IP")
# For XPU acceleration, we need to pass through the appropriate render device and group permissions
if [[ "{{ accelerator }}" == "xpu" ]]; then
RENDER_GID=$(getent group render | cut -d: -f3)
for node in /dev/dri/renderD*; do
vendor=$(cat /sys/class/drm/$(basename "$node")/device/vendor 2>/dev/null)
if [[ "$vendor" == "0x8086" ]]; then
run_args+=(--device "$node")
fi
done
if [[ -n "$RENDER_GID" ]]; then
run_args+=(--group-add "$RENDER_GID")
fi
fi
has_video_device=false
for dev in {{ passthrough-devices }}; do
if [[ -e "$dev" ]]; then
run_args+=(--device "$dev:$dev")
# Track if any video device is being passed through
if [[ "$dev" == /dev/video* ]]; then
has_video_device=true
fi
else
echo "Warning: device '$dev' not found, skipping." >&2
fi
done
# When a video device is passed through, add the host's video group so the
# container process can actually open /dev/videoN (the in-container 'video'
# group created in the Dockerfile may have a different GID than the host's).
if [[ "$has_video_device" == "true" ]]; then
VIDEO_GID=$(getent group video | cut -d: -f3)
if [[ -n "$VIDEO_GID" ]]; then
run_args+=(--group-add "$VIDEO_GID")
else
echo "Warning: could not resolve host 'video' group GID; camera access may fail." >&2
fi
fi
if [[ -n "{{ shm-size }}" ]]; then
run_args+=(--shm-size "{{ shm-size }}")
fi
# For GPU acceleration on WSL2, it's necessary to pass through the /dev/dxg device and mount the WSL libraries
if [[ "{{ accelerator }}" != "cpu" ]]; then
if [[ -e "/dev/dxg" ]]; then
run_args+=(--device "/dev/dxg:/dev/dxg")
fi
if [[ -d "/usr/lib/wsl" ]]; then
run_args+=(--volume "/usr/lib/wsl:/usr/lib/wsl:ro")
fi
fi
for vol in {{ volumes }}; do
run_args+=(--volume "$vol")
done
if [[ "{{ accelerator }}" == "cuda" ]]; then
run_args+=(--gpus all)
fi
echo "Running docker container from image: ${IMAGE_NAME}"
docker run "${run_args[@]}" "${IMAGE_NAME}"
if [[ "{{ detach }}" == "true" ]]; then
echo "Container started: ${CONTAINER_NAME}"
fi
# Remove persisted data and logs volumes
[confirm("WARNING: This will permanently delete all persisted projects, models, media, and logs. Continue?")]
clean-volumes:
docker volume rm {{ data-volume }} {{ logs-volume }} || true
# Symlink the PyInstaller backend sidecar into `ui/src-tauri/` so that
# `tauri dev` / `tauri build` can pick it up via `externalBin` + `resources`.
#
# Requires `just pyinstaller` to have been run in `backend/` first. The links
# are gitignored and always point at the latest PyInstaller output, so they
# only need to be created once.
[macos]
[linux]
tauri-link-sidecar:
#!/usr/bin/env bash
set -euo pipefail
DIST="{{ justfile_directory() }}/backend/dist/geti-backend"
# An interrupted PyInstaller COLLECT leaves a 0-byte executable, which execs
# as a no-op and makes the desktop app quit with "backend exited (code 0)".
if [[ ! -s "$DIST/geti-backend" || ! -x "$DIST/geti-backend" ]]; then
echo "Backend sidecar missing or empty in $DIST — run 'just pyinstaller' in backend/ first" >&2
exit 1
fi
if [[ ! -d "$DIST/_internal" || -z "$(ls -A "$DIST/_internal" 2>/dev/null)" ]]; then
echo "Backend resources missing or empty in $DIST/_internal — run 'just pyinstaller' in backend/ first" >&2
exit 1
fi
if ! command -v rustc >/dev/null 2>&1; then
echo "'rustc' not found on PATH — install Rust via https://rustup.rs to resolve the host triple" >&2
exit 1
fi
TRIPLE=$(rustc -vV | sed -n 's/^host: //p')
if [[ -z "$TRIPLE" ]]; then
echo "Could not determine the Rust host triple from 'rustc -vV'" >&2
exit 1
fi
cd ui/src-tauri
ln -sfn ../../backend/dist/geti-backend/geti-backend "geti-backend-${TRIPLE}"
ln -sfn ../../backend/dist/geti-backend/_internal _internal
echo "✅ Linked geti-backend-${TRIPLE} and _internal in ui/src-tauri"
# Symlink the PyInstaller backend sidecar into `ui\src-tauri\`.
# Needs Developer Mode enabled (or an elevated shell) to create symlinks.
[windows]
tauri-link-sidecar:
#!powershell.exe
$ErrorActionPreference = 'Stop'
$dist = Join-Path '{{ justfile_directory() }}' 'backend\dist\geti-backend'
if (-not (Test-Path "$dist\geti-backend.exe") -or (Get-Item "$dist\geti-backend.exe").Length -eq 0) {
Write-Error "Backend sidecar missing or empty in $dist - run 'just pyinstaller' in backend\ first"
}
if (-not (Test-Path "$dist\_internal") -or -not (Get-ChildItem -Force "$dist\_internal" | Select-Object -First 1)) {
Write-Error "Backend resources missing or empty in $dist\_internal - run 'just pyinstaller' in backend\ first"
}
if (-not (Get-Command rustc -ErrorAction SilentlyContinue)) {
Write-Error "'rustc' not found on PATH - install Rust via https://rustup.rs to resolve the host triple"
}
$triple = (rustc -vV | Select-String '^host: ').Line -replace '^host: ', ''
Set-Location ui\src-tauri
New-Item -Force -ItemType SymbolicLink -Path ".\geti-backend-$triple.exe" -Target "$dist\geti-backend.exe" | Out-Null
New-Item -Force -ItemType SymbolicLink -Path '.\_internal' -Target "$dist\_internal" | Out-Null
Write-Host "Linked geti-backend-$triple.exe and _internal in ui\src-tauri"
# Build the macOS .app distributable.
#
# Runs `tauri build` and then patches the bundle layout: the PyInstaller
# sidecar and its `_internal/` directory are moved from `Contents/MacOS/` and
# `Contents/Resources/` respectively into `Contents/MacOS/backend/`. Without
# this, PyInstaller's bootloader detects the `.app` bundle path and switches
# to "macOS bundle" mode, looking for shared libraries under
# `Contents/Frameworks/` (which Tauri doesn't populate) instead of the
# standalone `_internal/` layout it actually shipped — the sidecar then dies
# on launch with `Failed to load Python shared library 'libpython*.dylib'`.
# Putting the sidecar one directory deeper avoids the bundle detection.
#
# The Rust shell handles either layout — see `locate_backend()` in
# `ui/src-tauri/src/backend.rs`.
[macos]
tauri-build:
#!/usr/bin/env bash
set -euo pipefail
cd ui && npx tauri build
APP="src-tauri/target/release/bundle/macos/Geti.app"
if [[ ! -d "$APP" ]]; then
echo "Expected bundle not found at $APP" >&2
exit 1
fi
BACKEND_DIR="$APP/Contents/MacOS/backend"
mkdir -p "$BACKEND_DIR"
if [[ -f "$APP/Contents/MacOS/geti-backend" ]]; then
echo "▶ Moving sidecar into $BACKEND_DIR"
mv "$APP/Contents/MacOS/geti-backend" "$BACKEND_DIR/geti-backend"
fi
if [[ -d "$APP/Contents/Resources/_internal" ]]; then
echo "▶ Moving _internal into $BACKEND_DIR"
mv "$APP/Contents/Resources/_internal" "$BACKEND_DIR/_internal"
fi
if [[ ! -s "$BACKEND_DIR/geti-backend" ]]; then
echo "Bundled sidecar $BACKEND_DIR/geti-backend is missing or empty — re-run 'just pyinstaller' in backend/ and 'just tauri-link-sidecar'" >&2
exit 1
fi
if [[ ! -d "$BACKEND_DIR/_internal" || -z "$(ls -A "$BACKEND_DIR/_internal" 2>/dev/null)" ]]; then
echo "Bundled resources $BACKEND_DIR/_internal are missing or empty — re-run 'just pyinstaller' in backend/ and 'just tauri-link-sidecar'" >&2
exit 1
fi
echo "✅ Bundle ready at $APP"
# `tauri build` on non-macOS platforms (Windows .msi/.exe, Linux AppImage/.deb).
# The flat install layout doesn't trigger PyInstaller's bundle detection, so
# no post-build patching is needed.
[linux]
[windows]
tauri-build:
cd ui && npx tauri build
# run coturn server
run-coturn:
#!/usr/bin/env bash
set -euo pipefail
HOST_IP=$(just resolve-host-ip)
echo "Running coturn server on port {{ coturn-port }} with external IP ${HOST_IP}..."
docker run --rm --detach \
--network=host \
--name coturn-server \
{{ coturn-image }} \
-n \
--listening-port={{ coturn-port }} \
--external-ip="${HOST_IP}" \
--user=user:password \
--realm=my-realm \
--no-udp \
--log-file=stdout \
--verbose
# stop coturn server
stop-coturn:
@echo "Stopping coturn server..."
@docker stop coturn-server || true
# Patch Geti source code to use CUDA 12.6 instead of 13.0 for the 'cuda' target, needed for legacy Nvidia GPUs
# (Volta, Maxwell, Pascal). See https://github.com/open-edge-platform/geti/issues/7163
patch-for-legacy-gpu-support:
cd {{ justfile_directory() }}/../library && just patch-for-legacy-gpu-support
cd {{ justfile_directory() }}/backend && just venv-lock
@echo "Patched the code to use CUDA 12.6 for the 'cuda' target (legacy Nvidia GPU support)."
@echo "For Docker users: run 'just build-image --accelerator cuda' to rebuild the image with the patched code."