Configuration & deployment¶
Environment variables, security, production checklist, and Docker. For Heal & ready, second GPU, backup restore, and invite email, start with the operator guide.
Security notes¶
This app is designed for a trusted local / LAN setup. By default the HTTP API is open (CORS *) so ComfyUI custom nodes and CLI tools can call it. Auth-off is localhost-only.
When exposing beyond localhost:
- Set
PROMPT_AUTH_ENABLED=true(the Docker compose file defaults to it). With noPROMPT_ADMIN_PASSWORDthere is no account until the first visit: the sign-in page asks for the admin's username and password (8+ characters, not a common default). On a server other machines can reach (bind0.0.0.0,PROMPT_EXPOSED, a publicPROMPT_API_URL) it also asks for a one-time setup code the server prints in its log (docker compose logs castcut) and keeps in<data>/auth/setup-codeuntil the account exists. APROMPT_ADMIN_PASSWORDinstead creates the admin on start and is applied again only when it changes, so a password changed in the app sticks.PROMPT_ADMIN_PASSWORD=adminis refused at startup. - The session secret is made on first start and kept in
<data>/auth/session-secret(owner-only).PROMPT_SESSION_SECREToverrides it (older installs that usedPROMPT_API_TOKENas the secret keep doing so). Startup fails closed only when neither is set and the data folder can't be written. - The UI counts as same-origin when it is opened at
localhost, an IP address (the machine's LAN address), the name a reverse proxy forwards inX-Forwarded-Host(nginx:proxy_set_header X-Forwarded-Host $host;; Caddy and Traefik send it), or a hostname listed inPROMPT_TRUSTED_HOSTS— use that list only for a proxy that sends noX-Forwarded-Host, or the login is refused with Unauthorized. The proxy should sendX-Forwarded-Protoso the session cookie isSecureover HTTPS (it is left off over plain HTTP, where browsers drop it). Scripts and ComfyUI nodes use a personalpt_…key from Profile → API keys;PROMPT_API_TOKENis an optional shared service token (it bypasses user login — keep it secret), and with it set, cross-origin and non-browser clients must sendAuthorization: Bearer <token>. - Set
COMFYUI_ALLOW_CLIENT_URL=falseso callers cannot override the ComfyUI base URL (SSRF). PreferCOMFYUI_ALLOWED_HOSTSfor a hostname allowlist. - In Docker, the compose file in
docker/publishes onCASTCUT_BINDand tells the app the same address (PROMPT_BIND_HOST): on a network address the app refuses to start with sign-in off. Do not run0.0.0.0binds with auth off. - Webhook dispatch blocks private/metadata URLs unless
WEBHOOK_ALLOW_PRIVATE=true. - Invite, reset and notice emails link to the address an admin last signed in at; set
PROMPT_API_URLto pin another public origin. A non-loopbackPROMPT_API_URLis treated as network-exposed and requires auth. - Per-client rate limits and the sign-in throttle use the proxy's
X-Real-IP/ lastX-Forwarded-Forwhen they are sent (TRUST_PROXY_HEADERSunset). SetTRUST_PROXY_HEADERS=falseto ignore them; on a server reached directly a made-up header only dodges the per-client limit — the per-username sign-in limit (5 failures / 15 min) still holds.
Startup fail-closed signals (src/lib/bind-exposure-check.ts): PROMPT_EXPOSED, bind host 0.0.0.0/:: (HOSTNAME / HOST / PROMPT_BIND_HOST), or a non-loopback PROMPT_API_URL. Escape hatch: PROMPT_ALLOW_INSECURE_AUTH=1 (logs loudly; never use on a reachable network). Desktop shells (PROMPT_DESKTOP) are always treated as local.
Production checklist¶
Before exposing Castcut beyond a trusted LAN:
- [ ] In Docker, publish beyond the host (
CASTCUT_BIND=0.0.0.0) only with sign-in on (the compose default); create the admin on first visit with the setup code fromdocker compose logs castcut - [ ] Confirm Settings → Overview shows Auth = accounts on
- [ ] Create the admin on the sign-in page (or set a strong
PROMPT_ADMIN_PASSWORD;adminis refused at startup) - [ ] Back up
<data>/auth/session-secretwith the data folder (or setPROMPT_SESSION_SECRET): a new secret signs everyone out - [ ] Enable
PROMPT_AUTH_ENABLED=trueand create non-admin users with blocked features as needed - [ ] Never set
PROMPT_ALLOW_INSECURE_AUTH=1on a reachable network - [ ] Give CLI/ComfyUI nodes a per-user
pt_…key from Profile → API keys (PROMPT_API_TOKENonly where a shared service token is needed) - [ ] Configure SMTP (
PROMPT_SMTP_*+PROMPT_EMAIL_FROM, or Settings → Users → SMTP) and send a test - [ ] Sign in once as admin at the public address (emails link there), or pin it with
PROMPT_API_URL - [ ] Set
COMFYUI_ALLOW_CLIENT_URL=falseand pinCOMFYUI_API_URLorCOMFYUI_POOL - [ ] If using
COMFYUI_ALLOWED_HOSTS, include every pool hostname (Settings cluster copies a snippet) - [ ] Back up
PROMPT_DATA_DIR(studio.sqliteplus-wal/-shm, leftover*.json.imported, export snapshots) on a schedule - [ ] Export a studio backup JSON after the first real session (Settings → Overview)
- [ ] Run
npm run lint,npm test, andnpm run test:e2ebefore deploy (CI runs these on push) - [ ] For Playwright with auth enabled locally, credentials load from
.env.local(PROMPT_ADMIN_*) or setPROMPT_E2E_USERNAME/PROMPT_E2E_PASSWORD - [ ] Hard auth lane:
PROMPT_AUTH_ENABLED=trueplusnpm run test:e2e:ops:auth(PROMPT_E2E_AUTH=1fails closed if login is disabled or credentials fail)
Batch tools: Topics and Variations show per-row readiness scores; toggle Ready only before queueing. Workflow library Apply bindings injects {{POSITIVE}} / {{NEGATIVE}} placeholders from suggested node maps. Gallery Tag untagged backfills vision tags on completed entries.
Running behind a reverse proxy¶
Castcut can be reached through nginx, Caddy, Traefik or a tunnel. Checklist:
- Login on.
PROMPT_AUTH_ENABLED=true, a long randomPROMPT_SESSION_SECRET, your ownPROMPT_ADMIN_PASSWORD(startup refuses the defaults). Turn on an authenticator (TOTP) for the admin under Profile. - Your public name.
PROMPT_TRUSTED_HOSTS=castcut.example.com— the browser's origin must be recognised or every request (login included) is refused with Unauthorized. Only names you list are trusted; IP addresses andlocalhostalready are. - The client's address.
TRUST_PROXY_HEADERS=true, and have the proxy setX-Real-IP(or append toX-Forwarded-For— Castcut reads the last entry, the one the proxy added, never the first). Rate limits and the login throttle use it; without it every visitor shares one limit. - HTTPS. Terminate TLS at the proxy, send
X-Forwarded-Proto(the session cookie is thenSecure) and add HSTS there (Strict-Transport-Security: max-age=31536000). - Bind Castcut to loopback when the proxy runs on the same machine (
HOSTNAME=127.0.0.1/-H 127.0.0.1), so nobody can skip the proxy and its TLS. Keep ComfyUI (8188) and the LLM server on loopback or a private network — never forward them. - Uploads and long renders. Allow large bodies (
client_max_body_size 200m) and long reads (proxy_read_timeout 600s) for clip encodes.
What Castcut does on its side (2.6): signed-out visitors get only is it up from /api/health and no admin username from /api/auth/session; 5 failed sign-ins for a username lock it for 15 minutes (20 per client address), whatever address they come from; the server refuses to fetch private or local addresses for shot and webhook URLs (checked after DNS resolution); a request may not name its own ComfyUI address while login is on (COMFYUI_ALLOW_CLIENT_URL, unset); pages send X-Frame-Options, frame-ancestors 'self', nosniff, a referrer policy and no X-Powered-By.
nginx example:
server {
listen 443 ssl http2;
server_name castcut.example.com;
# ssl_certificate / ssl_certificate_key …
add_header Strict-Transport-Security "max-age=31536000" always;
client_max_body_size 200m;
location / {
proxy_pass http://127.0.0.1:47832;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
}
}
Docker¶
Everything lives in the docker/ folder: one compose file and one settings file.
cd docker
cp .env.example .env # fill in ComfyUI, the LLM, and a sign-in password + secret
docker compose up -d --build
docker compose logs -f castcut # a missing password or secret is reported here
Open http://<docker host>:47832. .env is the only file to edit, and changes apply on docker compose up -d (no rebuild needed).
- ComfyUI / LLM on the Docker host: use
http://host.docker.internal:<port>(works on Linux too, through the compose file'shost-gateway). ComfyUI must listen beyond localhost (--listen), or the container cannot reach it. On another machine, use that machine's address. - Who can open it:
CASTCUT_BIND=0.0.0.0(the default) publishes on your network, and the app refuses to start unless sign-in is on with a realPROMPT_ADMIN_PASSWORDandPROMPT_SESSION_SECRET(openssl rand -hex 32).CASTCUT_BIND=127.0.0.1keeps it on the Docker host only, and then sign-in may be off. The compose file passes the same address to the app asPROMPT_BIND_HOST, so the startup check sees where the port really is. - Reverse proxy: set
PROMPT_TRUSTED_HOSTSto the public name,TRUST_PROXY_HEADERS=trueandPROMPT_API_URL, and allow request bodies of at least 40 MB (a Pictures backup restore uploads 32 MB pieces). - Your data (Cast, Gallery, settings, kept renders) lives in the
castcut_datavolume at/data, and survives rebuilds and image updates. - Extras:
docker compose --profile llm up -dadds the LLM (llama.cpp with the NSFWVision Qwen3-VL model on an NVIDIA GPU; setLLM_API_BASE_URL=http://llm:8080/v1);--profile ollamaadds a local Ollama (LLM_API_BASE_URL=http://ollama:11434/v1);--profile comfyuiadds a ComfyUI container.
Moving an existing install in. Either use the app: on the old server use Settings → Data → Export backup and Download pictures backup; on the new server Import backup, then Restore pictures backup. Or copy the old PROMPT_DATA_DIR folder into the volume, with the old app stopped so studio.sqlite is complete:
docker compose down
docker run --rm -v docker_castcut_data:/data -v /path/to/old-data:/src alpine \
sh -c 'cp -a /src/. /data/ && chown -R 1001:1001 /data'
docker compose up -d
(The volume is named <compose project>_castcut_data, which is docker_castcut_data when run from docker/; docker volume ls shows it.) Cast plates are pictures in ComfyUI's input/ and output/ folders. If the new server uses a different ComfyUI, the Pictures backup carries them; with a folder copy, copy those ComfyUI folders too.
Without compose (published image, from GitHub Releases; see Releases). This publishes on your network, so other machines can open http://<docker host>:47832, and sign-in is required:
docker run -d --name castcut --restart=always \
-p 47832:47832 \
-v castcut_data:/data \
--add-host=host.docker.internal:host-gateway \
-e PROMPT_AUTH_ENABLED=true \
-e PROMPT_ADMIN_PASSWORD='choose-a-password' \
-e PROMPT_SESSION_SECRET="$(openssl rand -hex 32)" \
-e COMFYUI_API_URL=http://host.docker.internal:8188 \
-e LLM_API_BASE_URL=http://host.docker.internal:1234/v1 \
ghcr.io/doodersrage/castcut:latest
Generate the session secret once and keep it, because a new one signs everyone out: put these lines in a file and pass --env-file castcut.env instead of the -e flags.
Only on the Docker host (a browser on that same machine; nothing else can reach it, which is useless for a VM or a server): publish on loopback and tell the app so, and sign-in may be off:
docker run -d --name castcut --restart=always \
-p 127.0.0.1:47832:47832 -e PROMPT_BIND_HOST=127.0.0.1 \
-v castcut_data:/data --add-host=host.docker.internal:host-gateway \
-e COMFYUI_API_URL=http://host.docker.internal:8188 \
ghcr.io/doodersrage/castcut:latest
Inside the container the server always listens on 0.0.0.0, which the startup check counts as network-exposed. PROMPT_BIND_HOST=127.0.0.1 tells it the port is really only published on loopback; without it, a sign-in-off container refuses to start. Docker Hub (doodersrage/castcut) is updated on the same release when Hub secrets are set. Build locally with docker build -t castcut ..
LLM configuration¶
Built-in LLM (no LM Studio)¶
Settings → LLM → Built-in LLM installs llama.cpp's server (a pinned release, Vulkan build on Linux and Windows, Metal on Mac) and one vision-language model into the data folder, and runs it itself: it starts when an LLM call needs it, stops after 15 idle minutes (LOCAL_LLM_IDLE_MINUTES) and when ComfyUI runs out of memory, and Settings shows its context (8,192 / 16,384 / 32,768 tokens). The default model, NSFWVision Qwen3-VL 8B, writes Story and reads stills for the checks; when LM Studio already downloaded it, nothing is downloaded. While it is on it takes the place of LLM_API_BASE_URL (a hosted provider picked in the browser still wins). Optional: LOCAL_LLM_PORT (default 8191), LOCAL_LLM_GPU_LAYERS (default all; 0 runs it on the CPU). Not in the Alpine Docker image — use docker compose --profile llm there.
Built-in LLM in Docker¶
The Castcut image is Alpine (musl), and llama.cpp's builds need glibc — so in Docker, Settings → LLM says "This server's system … can't run llama.cpp's builds". Run llama.cpp's own server container beside Castcut instead. It is the same server and model the built-in LLM uses; it just stays running.
In docker/.env:
Then start Castcut with the llm profile:
cd docker
docker compose --profile llm up -d
docker compose logs -f llm # first start downloads the model (~6 GB)
It needs an NVIDIA GPU and the NVIDIA Container Toolkit. The model is kept in the
llm_models volume, so later starts are quick.
For a Castcut container you started yourself (or on another machine). NVIDIA GPU:
docker run -d --name castcut-llm --restart unless-stopped --gpus all \
-p 8080:8080 -v castcut_llm_models:/root/.cache/llama.cpp \
ghcr.io/ggml-org/llama.cpp:server-cuda \
-hf GitMylo/nsfwvision-qwen3-vl-8b-v3-gguf:Q4_K_M \
--host 0.0.0.0 --port 8080 -c 8192 -ngl 99 --jinja --alias castcut-local
Then in Castcut's environment:
LLM_API_BASE_URL=http://<llm host>:8080/v1 # host.docker.internal on the same host
LLM_MODEL=castcut-local
LLM_VISION_MODEL=castcut-local
and restart Castcut (docker restart castcut). Putting both on one Docker network
(docker network create castcut, --network castcut on both) lets you use
http://castcut-llm:8080/v1 and drop the -p 8080:8080.
Other hardware — swap the image tag:
| GPU | Image | Extra flags |
|---|---|---|
| NVIDIA | ghcr.io/ggml-org/llama.cpp:server-cuda |
--gpus all |
| AMD (ROCm) | ghcr.io/ggml-org/llama.cpp:server-rocm |
--device /dev/kfd --device /dev/dri |
| AMD / Intel (Vulkan) | ghcr.io/ggml-org/llama.cpp:server-vulkan |
--device /dev/dri |
| None (CPU) | ghcr.io/ggml-org/llama.cpp:server |
drop -ngl 99 — slow, but it works |
Check it: curl http://<llm host>:8080/v1/models lists castcut-local; in Castcut,
Settings → Overview → Play checks shows the LLM and the vision model as ready.
Sharing the GPU with ComfyUI
The model holds about 6–7 GB of VRAM while the container runs. On a 24 GB card that is
fine for stills; the big video tools (Copy moves, Dance, Lip-sync) free a built-in LLM
automatically, but not a container — stop it (docker stop castcut-llm) before a long
video session if they run out of memory, or run the LLM on another machine.
Your own LLM server¶
The generator calls any OpenAI-compatible chat completions API. Configure via .env.local:
| Variable | Default | Description |
|---|---|---|
LLM_API_BASE_URL |
http://localhost:11434/v1 |
API base URL |
LLM_API_KEY |
(empty) | Bearer token if required |
LLM_MODEL |
dolphin-llama3 |
Model name |
LLM_TEMPERATURE |
0.95 |
Sampling temperature (higher = more variation) |
LLM_ENABLED |
true |
Set false for template-only mode |
ALLOW_TEMPLATE_FALLBACK |
true |
Fall back if LLM is unreachable |
PROMPT_API_TOKEN |
(empty) | Optional API bearer token for non-browser clients |
PROMPT_TRUSTED_HOSTS |
(empty) | Comma-separated hostnames the UI is opened at, for a reverse proxy that sends no X-Forwarded-Host (a forwarded name already counts). IP addresses and localhost already are same-origin. |
TRUST_PROXY_HEADERS |
(auto) | Key rate limits and the login throttle on X-Real-IP (else the last X-Forwarded-For entry) when a proxy sends them; false ignores them (every client is one). |
PROMPT_NSFW_GENERATOR_ENABLED |
false |
Unlock Adult generator and Roleplay Sultry / Explicit / Raunchy on the server |
NEXT_PUBLIC_PROMPT_NSFW_GENERATOR_ENABLED |
false |
Same lockout for nav/UI (rebuild required). next.config copies the server flag when this is unset. Desktop installers bake both flags on. Adult stills always carry the age wording and the adult check — see Adult content safeguards. |
PROMPT_DESKTOP / NEXT_PUBLIC_PROMPT_DESKTOP |
false |
Set by the Tauri shell / desktop Next build. First launch opens Settings → ComfyUI connection. |
LLM_VISION_MODEL |
(empty) | Vision-capable model for Image → Prompt, Refine critique, gallery tags, Auto-review. When unset, image checks use a vision model found in the LLM server's model list (qwen-vl, gemma3, llava…) |
LLM_EMBED_MODEL |
(empty) | Optional embedding model for semantic search (OLLAMA_EMBED_MODEL also accepted). Settings → LLM can override per session |
PROMPT_ENGINE |
comfyui |
comfyui (default), diffusers (stills only), fal, replicate, openai, gemini, grok, or runway. Fal/Replicate/Grok/Gemini/Runway queue clips; ChatGPT and Diffusers stay stills. |
FAL_KEY |
(empty) | Fal API key when Settings → Inference engine is Fal (FAL_API_KEY also accepted). Browser Settings key overrides per request. |
FAL_MODEL |
fal-ai/flux/schnell |
Default Fal txt2img model id |
REPLICATE_API_TOKEN |
(empty) | Replicate token when Settings → Inference engine is Replicate (REPLICATE_API_KEY also accepted). |
REPLICATE_MODEL |
black-forest-labs/flux-schnell |
Default Replicate txt2img model id |
OPENAI_API_KEY |
(empty) | OpenAI key when Settings → Inference engine is ChatGPT / OpenAI Images. |
OPENAI_MODEL |
gpt-image-2 |
Default OpenAI image model |
GEMINI_API_KEY |
(empty) | Gemini key when Settings → Inference engine is Google Gemini (GOOGLE_API_KEY also accepted). |
GEMINI_MODEL |
gemini-3.1-flash-image |
Default Gemini image model |
XAI_API_KEY |
(empty) | xAI key when Settings → Inference engine is Grok (GROK_API_KEY also accepted). |
GROK_MODEL |
grok-imagine-image-2.0 |
Default Grok Imagine model |
RUNWAY_API_KEY |
(empty) | Runway key when Settings → Inference engine is Runway (RUNWAYML_API_SECRET also accepted). |
RUNWAY_MODEL |
gen4_image |
Default Runway Gen-4 image model |
PROMPT_API_URL |
(the address an admin last signed in at) | Public origin for invite/reset links and email footers (pins it), and the server scheduled-batch runner (http://127.0.0.1:47832 when unset) |
PROMPT_AUTH_ENABLED |
false |
Enable login and feature access control. Auth-off is localhost-only; network-exposed binds fail closed without auth. |
PROMPT_ADMIN_USERNAME |
admin |
Default admin username (seeded on first enable) |
PROMPT_ADMIN_PASSWORD |
(empty: admin made on the sign-in page) | Admin password applied on start when it changes (a password changed in the app sticks until this changes). admin is refused at startup. |
PROMPT_SESSION_SECRET |
(generated in <data>/auth) |
HMAC secret for session cookies. Unset: one is made on first start and kept in the data folder; startup fails closed only if it can't be written. |
PROMPT_EXPOSED |
false |
Marks the process as network-exposed (for a server published some way the bind check cannot see). With auth off, startup fails unless PROMPT_ALLOW_INSECURE_AUTH=1. |
PROMPT_ALLOW_INSECURE_AUTH |
false |
Emergency escape hatch only — never use on a reachable network. |
PROMPT_AUTH_DIR |
(uses PROMPT_DATA_DIR/auth) |
Legacy JSON import directory (users.json, groups.json, analytics). Live auth now lives in studio.sqlite. |
PROMPT_DATA_DIR |
(empty) | Server SQLite root (studio.sqlite) for /api/storage, auth, collab rooms, SMTP overlay, queue-export overlay, and {PROMPT_DATA_DIR}/plugins server plugin registry |
PROMPT_PLUGIN_HMAC_SECRET |
(empty) | When set, POST /api/plugins/server installs must send X-Prompt-Plugin-Signature (hex HMAC-SHA256 of the raw body / ZIP bytes) |
COLLAB_REDIS_URL |
(empty) | Optional Redis URL for multi-node collab SSE (requires ioredis; falls back to SQLite/memory when unset) |
SERVER_USER_MAINTENANCE |
false |
Enable /api/maintenance/run for per-user scheduled campaigns and export snapshots |
SERVER_USER_MAINTENANCE_INTERVAL_MIN |
15 |
When SERVER_USER_MAINTENANCE=true, run maintenance on this interval (minutes) |
SERVER_USER_MAINTENANCE_EXPORT_KEEP |
20 |
Snapshot files kept per user under {PROMPT_DATA_DIR}/users/<id>/exports/; oldest are deleted past this count after each export. 0 disables pruning |
API_RATE_LIMIT_WINDOW_MS |
60000 |
Rate limit window (ms) for API proxy |
API_RATE_LIMIT_MAX |
120 |
Default max requests per window; overridable per user/group in Settings → Users |
COMFYUI_API_URL |
http://127.0.0.1:8188 |
Default ComfyUI base URL |
COMFYUI_ROOT |
(empty) | Absolute path to the ComfyUI install (same machine). Enables Settings → ComfyUI → Model assets curated weight downloads (image, video, audio, 3D mesh) into models/checkpoints, diffusion_models, vae, text_encoders, etc. |
HF_TOKEN |
(empty) | Optional Hugging Face token for curated downloads (also accepts HUGGING_FACE_HUB_TOKEN) |
CIVITAI_API_TOKEN |
(empty) | Optional Civitai token for gated LoRA downloads from Settings → ComfyUI → LoRA library |
TRAINER_URL |
(empty) | Escape hatch: when set, POST /api/lora-train start POSTs the job payload (including datasetPath) to this webhook instead of spawning a process |
TRAINER_COMMAND |
(empty) | Escape hatch: argv spawned without a shell when TRAINER_URL is unset (dataset/output/trigger appended). Wins over first-party kohya templates |
TRAINER_KOHYA_SCRIPT |
(empty) | Path to sd-scripts train_network.py. When URL/command are unset, start builds a first-party kohya argv (rank/steps/resolution/base) and spawns TRAINER_PYTHON / python3 |
TRAINER_PYTHON |
python3 |
Interpreter for TRAINER_KOHYA_SCRIPT when the script ends in .py (PYTHON is also accepted) |
COMFYUI_ALLOW_CLIENT_URL |
(unset) | Allow clients to override the ComfyUI URL. Unset: allowed only with login off (local single-user); with login on it is refused. |
COMFYUI_ALLOWED_HOSTS |
(empty) | Optional comma-separated ComfyUI host allowlist. Empty = any host (still blocks metadata). Settings extras cannot change this |
COMFYUI_POOL |
(empty) | Comma-separated ComfyUI URLs merged with Settings extras at queue time. Copy a snippet from Settings → ComfyUI cluster after Test |
COMFYUI_QUEUE_EXPORT_DIR |
(empty) | Write JSON sidecars after successful queue. Settings → Automation can overlay a directory when this env is unset |
WEBHOOK_ALLOW_PRIVATE |
false |
Allow webhook POSTs to private/LAN URLs |
PROMPT_EMAIL_ENABLED |
auto | Set true to force email on when SMTP is configured |
PROMPT_SMTP_HOST |
(empty) | SMTP server hostname |
PROMPT_SMTP_PORT |
587 |
SMTP port |
PROMPT_SMTP_SECURE |
false |
Use TLS directly (typical for port 465) |
PROMPT_SMTP_USER |
(empty) | SMTP auth username |
PROMPT_SMTP_PASS |
(empty) | SMTP auth password |
PROMPT_EMAIL_FROM |
(empty) | From header, e.g. Castcut <noreply@example.com> |
PROMPT_ADMIN_EMAIL |
(empty) | Fallback recipient for server batches when users have no email |
PROMPT_EMAIL_NOTIFY_BATCH |
true |
Send email when scheduled batches/campaigns finish |
PROMPT_EMAIL_NOTIFY_PASSWORD |
true |
Send email when a password is changed |
SERVER_SCHEDULED_BATCH |
false |
Headless scheduled batch (needs PROMPT_DATA_DIR). Browser scheduled batch on Automation is separate |
SERVER_SCHEDULED_BATCH_INTERVAL_MIN |
60 |
Minutes between headless batch runs |
SERVER_SCHEDULED_BATCH_TARGET |
random-scene |
random-scene or topics |
SERVER_SCHEDULED_BATCH_COUNT |
3 |
Prompts per headless run |
SERVER_SCHEDULED_BATCH_QUEUE |
false |
Queue headless results to ComfyUI |
Settings overlays: SMTP (GET/POST /api/settings/email) and queue-export dir (/api/settings/queue-export) persist in SQLite when PROMPT_DATA_DIR is set. Without it, SMTP is kept in memory until restart. Env values remain the fallback. Passwords are never returned to the browser.
Invite and password reset: With auth and SMTP enabled, admins send POST /api/auth/invite from Settings → Users. Users request POST /api/email/forgot-password and complete POST /api/auth/reset-password. Links are {PROMPT_API_URL}/login?reset=… and expire in 1 hour. Tokens live in SQLite (password_reset_tokens).
Queue interrupt: POST /api/comfyui/interrupt forwards an interrupt to ComfyUI (also available on the Queue page).
ComfyUI restart: POST /api/comfyui/restart asks ComfyUI-Manager to reboot (/api/manager/reboot or /manager/reboot). Vanilla ComfyUI has no restart API — without Manager the route returns HTTP 501. Also on Queue and Settings → Connection / LoRA library.
LoRA search: GET /api/comfyui/loras/search?q=… queries Civitai server-side. POST /api/comfyui/loras/download with { versionId } writes into COMFYUI_ROOT/models/loras. The browser never supplies download URLs.
LoRA train (kohya / sd-scripts): Admin GET/POST /api/lora-train owns the loop. Cast Export → Train (and Gallery dataset export) can write image+caption pairs under {PROMPT_DATA_DIR}/lora-datasets/… via action=export-dataset, then action=start always accepts datasetPath. Job records persist in SQLite (kv key lora-train-jobs) so Settings polling survives restart. Priority: TRAINER_URL → TRAINER_COMMAND → first-party kohya template (TRAINER_KOHYA_SCRIPT + Settings template/rank/steps/resolution/base) → manual job. Stdout progress is parsed from kohya/tqdm lines. On complete, Studio copies .safetensors into COMFYUI_ROOT/models/loras, registers a library entry (client persists), and can pin + auto-queue validation from prefs.
Second GPU probe: POST /api/comfyui/probe with { "url": "http://…" }. Fetches the host only after allowlist checks. An allowlist miss returns HTTP 400 with code: "allowlist" and does not contact the host.
Webhooks → email: Outbound webhooks fire on job completion/error. When signed in with batch email notifications enabled, the gallery client also batches completion emails via POST /api/email/jobs-completed (debounced ~8s). Server scheduled batches use POST /api/email/batch-completed.