Applies the new write_readme companion-doc convention to ai-stack, paintplus, and kyber-launcher: install-time-invariant content (usage walkthroughs, service tables, troubleshooting) moves out of the .sh heredocs into sibling services/<name>.md files, leaving only genuinely install-specific content inline. - services/paintplus.md: config/cloud/GPU/ai-stack-backend/update/Caddy sections, picked up automatically via write_readme's companion-doc support. - services/ai-stack.md: roles, GPU switcher, service URLs, cloud LLM provider setup, update, Caddy. ai-stack.sh can't use write_readme directly (its POST-INSTALL-NOTES.md filename deliberately avoids colliding with the vendored app's own README.md in the same directory), so it appends the companion file manually. - services/kyber-launcher.md: the full SWBF2 (2017) + Kyber walkthrough, moved out of the root README's "Gaming scripts" section (which now just points here). kyber-launcher.sh now calls write_readme to deploy it to ~/.local/share/kyber/README.md, fixing a stale in-script pointer to a README section that no longer exists.
239 lines
12 KiB
Bash
239 lines
12 KiB
Bash
#!/bin/bash
|
|
# services/paintplus.sh — PaintPlus: self-hosted AI photo editor. Paint a mask over
|
|
# an object, describe the change, and AI replaces only that region.
|
|
#
|
|
# The full application source is vendored in this repo under ./paintplus and is
|
|
# copied to ~/docker/paintplus/src at install time (no network clone).
|
|
# Based on EditmaskwithAI (github.com/outis1one/EditmaskwithAI).
|
|
# Part of the modular post-install system (sourced by setup.sh).
|
|
#
|
|
# Two deployment modes (chosen at install):
|
|
# Cloud — no GPU; uses a cloud provider (OpenAI gpt-image / Replicate), or this
|
|
# box's own ai-stack service (InvokeAI/ComfyUI) if installed. Lightweight.
|
|
# GPU — local inference via the app's own installer (NVIDIA, downloads ~13 GB).
|
|
#
|
|
# The app reads config from a .env the compose file interpolates
|
|
# (${AI_PROVIDER}, ${OPENAI_API_KEY}, ${SECRET_KEY}, ...). No env_file: directive.
|
|
# No built-in auth — protect with Authelia via Caddy.
|
|
|
|
register_service paintplus utilities "AI photo editor — mask a region, AI replaces it (PaintPlus)" 3080
|
|
|
|
install_paintplus() {
|
|
require_docker || return 1
|
|
log_info "Installing PaintPlus (mask-based AI photo editor)..."
|
|
|
|
# Vendored application source lives in this repo at <repo>/paintplus
|
|
local SELF_DIR SRC_DIR
|
|
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
SRC_DIR="$(cd "$SELF_DIR/.." && pwd)/paintplus"
|
|
|
|
local PP_DIR="$DOCKER_DIR/paintplus"
|
|
local APP_DIR="$PP_DIR/src"
|
|
|
|
if [ "$DRY_RUN" = true ]; then
|
|
echo "[DRY-RUN] Would copy vendored source $SRC_DIR -> $APP_DIR"
|
|
echo "[DRY-RUN] Would prompt for deployment mode (Cloud API incl. ai-stack InvokeAI/ComfyUI / Local GPU)"
|
|
echo "[DRY-RUN] Would create .env (AI_PROVIDER, API key, SECRET_KEY)"
|
|
echo "[DRY-RUN] Cloud: docker compose up -d --build | GPU: install-local-gpu.sh + bring-up-local-gpu.sh"
|
|
echo "[DRY-RUN] Would offer Authelia SSO and configure Caddy (paintplus:8000, host port 3080)"
|
|
return 0
|
|
fi
|
|
|
|
if [ ! -d "$SRC_DIR" ]; then
|
|
log_error "Vendored PaintPlus source not found at $SRC_DIR"
|
|
return 1
|
|
fi
|
|
|
|
# ── Copy vendored source into the docker dir ──────────────────────────────
|
|
# Refreshes app files; leaves a user-edited .env and the override untouched
|
|
# (neither is part of the vendored source).
|
|
mkdir -p "$APP_DIR"
|
|
cp -a "$SRC_DIR/." "$APP_DIR/"
|
|
ensure_docker_dir_ownership "$PP_DIR"
|
|
cd "$APP_DIR" || return 1
|
|
|
|
# ── Deployment mode ───────────────────────────────────────────────────────
|
|
echo ""
|
|
log_info "Deployment mode:"
|
|
log_info " 1) Cloud API No GPU needed. Uses OpenAI (gpt-image) or Replicate. Lightweight."
|
|
log_info " 2) Local GPU NVIDIA GPU; runs the app's installer and downloads ~13 GB of models."
|
|
echo ""
|
|
local PP_MODE=""
|
|
prompt_text "Mode [1=Cloud]:" "1" PP_MODE
|
|
|
|
# ── .env from the app's template ──────────────────────────────────────────
|
|
if [ -f .env.example ]; then
|
|
cp .env.example .env
|
|
else
|
|
log_warning ".env.example missing — writing a fresh .env"
|
|
: > .env
|
|
fi
|
|
|
|
# Upsert KEY=VALUE into ./.env (drop any existing/commented line, then append)
|
|
_pp_set_env() {
|
|
sed -i -E "/^#?[[:space:]]*$1=/d" .env
|
|
printf '%s=%s\n' "$1" "$2" >> .env
|
|
}
|
|
|
|
local PP_PROVIDER="local_gpu"
|
|
local PP_USE_AI_STACK_NET=false
|
|
local AI_STACK_DIR="$DOCKER_DIR/ai-stack"
|
|
if [[ "$PP_MODE" != "2" ]]; then
|
|
echo ""
|
|
log_info "Cloud provider:"
|
|
log_info " 1) OpenAI gpt-image editing. Key: https://platform.openai.com/api-keys"
|
|
log_info " 2) Replicate SDXL-inpaint / LaMa etc. Key: https://replicate.com/account/api-tokens"
|
|
[ -d "$AI_STACK_DIR" ] && log_info " 3) ai-stack This box's local InvokeAI/ComfyUI — no key, no cloud cost."
|
|
echo ""
|
|
local _prov=""
|
|
prompt_text "Provider [1=OpenAI]:" "1" _prov
|
|
local _key=""
|
|
if [[ "$_prov" == "2" ]]; then
|
|
PP_PROVIDER="replicate"
|
|
prompt_text "Replicate API key (enter to set later):" "" _key
|
|
if [ -n "$_key" ]; then _pp_set_env REPLICATE_API_KEY "$_key"
|
|
else log_warning "No key entered — set REPLICATE_API_KEY in .env later"; fi
|
|
elif [[ "$_prov" == "3" ]] && [ -d "$AI_STACK_DIR" ]; then
|
|
echo ""
|
|
log_info "ai-stack backend:"
|
|
log_info " 1) InvokeAI Best for Flux/SDXL. http://invokeai:9090"
|
|
log_info " 2) ComfyUI Workflow API — same engine ai-stack's Open WebUI uses. http://comfyui:8188"
|
|
local _backend=""
|
|
prompt_text "Backend [1=InvokeAI]:" "1" _backend
|
|
if [[ "$_backend" == "2" ]]; then
|
|
PP_PROVIDER="comfyui"
|
|
_pp_set_env COMFYUI_URL "http://comfyui:8188"
|
|
else
|
|
PP_PROVIDER="invokeai"
|
|
_pp_set_env INVOKEAI_URL "http://invokeai:9090"
|
|
fi
|
|
PP_USE_AI_STACK_NET=true
|
|
log_warning "ai-stack must be running — PaintPlus reaches it over Docker networking, not localhost."
|
|
else
|
|
PP_PROVIDER="openai"
|
|
prompt_text "OpenAI API key (enter to set later):" "" _key
|
|
if [ -n "$_key" ]; then _pp_set_env OPENAI_API_KEY "$_key"
|
|
else log_warning "No key entered — set OPENAI_API_KEY in .env later"; fi
|
|
fi
|
|
else
|
|
local _hf=""
|
|
prompt_text "HuggingFace token (optional — for gated models, enter to skip):" "" _hf
|
|
[ -n "$_hf" ] && _pp_set_env HF_TOKEN "$_hf"
|
|
fi
|
|
|
|
_pp_set_env AI_PROVIDER "$PP_PROVIDER"
|
|
_pp_set_env SECRET_KEY "$(generate_password 48)"
|
|
chmod 600 .env
|
|
mkdir -p data
|
|
ensure_docker_dir_ownership "$PP_DIR"
|
|
|
|
# Mirrors configure_caddy_for_service's own mode resolution (lib/common.sh):
|
|
# explicit CADDY_MODE from the site config wins, then a local ~/docker/caddy,
|
|
# then the legacy CADDY_REMOTE_HOST var. Only "local" joins caddy_net — a
|
|
# remote Caddy box can't reach this container by name over a bridge network
|
|
# it isn't on anyway.
|
|
local _CADDY_MODE="${CADDY_MODE:-none}"
|
|
[ "$_CADDY_MODE" = "none" ] && [ -d "$DOCKER_DIR/caddy" ] && _CADDY_MODE="local"
|
|
[ "$_CADDY_MODE" = "none" ] && [ -n "${CADDY_REMOTE_HOST:-}" ] && _CADDY_MODE="remote"
|
|
|
|
# ── Caddy network override (cloud mode) ───────────────────────────────────
|
|
# The base compose has no networks, so Caddy (on caddy_net) can't reach the
|
|
# container by name. This override — auto-merged with the default compose —
|
|
# attaches the app to caddy_net. The GPU compose runs with an explicit -f and
|
|
# does NOT merge overrides, so GPU mode is wired with `docker network connect`.
|
|
if [[ "$PP_MODE" != "2" ]] && [ "$_CADDY_MODE" = "local" ]; then
|
|
cat > docker-compose.override.yml << OVR
|
|
# Added by ubuntu-post-install so Caddy (on caddy_net) can reach this app by name.
|
|
services:
|
|
app:
|
|
networks:
|
|
- caddy_net
|
|
networks:
|
|
caddy_net:
|
|
external: true
|
|
name: ${SITE_CADDY_NET:-caddy_net}
|
|
OVR
|
|
fi
|
|
|
|
# ── Bring the stack up ────────────────────────────────────────────────────
|
|
local START_PP=""
|
|
if [[ "$PP_MODE" == "2" ]]; then
|
|
echo ""
|
|
log_warning "GPU mode runs the app's installer (NVIDIA toolkit, DNS fix) and downloads ~13 GB."
|
|
prompt_yn "Run the local-GPU installer now? (y/n):" "y" START_PP
|
|
if [[ "$START_PP" =~ ^[Yy]$ ]]; then
|
|
if [ -f install-local-gpu.sh ] && [ -f bring-up-local-gpu.sh ]; then
|
|
chmod +x install-local-gpu.sh bring-up-local-gpu.sh 2>/dev/null || true
|
|
bash install-local-gpu.sh || log_warning "install-local-gpu.sh reported an error — see output above"
|
|
bash bring-up-local-gpu.sh || log_warning "bring-up-local-gpu.sh failed — check: docker compose -f docker-compose.gpu.yml logs"
|
|
else
|
|
log_warning "GPU scripts not found — start manually per src/README.md"
|
|
fi
|
|
fi
|
|
else
|
|
prompt_yn "Build and start PaintPlus now? (y/n):" "y" START_PP
|
|
if [[ "$START_PP" =~ ^[Yy]$ ]]; then
|
|
docker compose up -d --build \
|
|
&& log_success "PaintPlus started" \
|
|
|| log_warning "Start failed — check: docker compose logs"
|
|
fi
|
|
fi
|
|
|
|
# Ensure the running container is on caddy_net (covers GPU mode, where the
|
|
# override file above is not merged by the app's bring-up script).
|
|
if [ "$_CADDY_MODE" = "local" ] && [[ "$START_PP" =~ ^[Yy]$ ]]; then
|
|
docker network connect "$SITE_CADDY_NET" paintplus 2>/dev/null || true
|
|
fi
|
|
|
|
# Join ai-stack's network so PaintPlus can reach InvokeAI/ComfyUI by name
|
|
# (those containers publish to the host too, but "localhost" inside this
|
|
# container means itself, not the host — a shared Docker network is needed).
|
|
if [ "$PP_USE_AI_STACK_NET" = true ] && [[ "$START_PP" =~ ^[Yy]$ ]]; then
|
|
if docker network inspect ai-stack_default >/dev/null 2>&1; then
|
|
docker network connect ai-stack_default paintplus 2>/dev/null \
|
|
&& log_success "Connected to ai-stack's network for the $PP_PROVIDER backend" \
|
|
|| true
|
|
else
|
|
log_warning "ai-stack network not found (start ai-stack first) — once it's up, run:"
|
|
log_warning " docker network connect ai-stack_default paintplus"
|
|
fi
|
|
fi
|
|
|
|
# ── Auth + Caddy ──────────────────────────────────────────────────────────
|
|
local EXTRA_BLOCK=""
|
|
if [ -d "$DOCKER_DIR/authelia" ]; then
|
|
local _use_auth=""
|
|
prompt_yn "Protect PaintPlus with Authelia SSO? (y/n):" "y" _use_auth
|
|
[[ "$_use_auth" =~ ^[Yy]$ ]] && EXTRA_BLOCK=" import authelia"
|
|
fi
|
|
configure_caddy_for_service "PaintPlus" "paintplus:8000" "paintplus" "$EXTRA_BLOCK"
|
|
|
|
# ── README (deploy notes; the app's own docs are at src/README.md) ────────
|
|
# Static usage sections (config, cloud/GPU/ai-stack modes, update, Caddy)
|
|
# live in the companion services/paintplus.md and get appended
|
|
# automatically by write_readme (lib/common.sh) — kept out of this
|
|
# heredoc since none of it depends on anything chosen during install.
|
|
write_readme "$PP_DIR" << 'MD'
|
|
# PaintPlus (deployment)
|
|
|
|
Self-hosted AI photo editor: paint a mask over any object, describe what you
|
|
want, and the AI replaces just that region. The application source is vendored
|
|
in ubuntu-post-install and copied here to `src/` (no network clone).
|
|
App docs: `src/README.md`. Based on EditmaskwithAI.
|
|
|
|
- URL: http://localhost:3080
|
|
- No built-in login — protect via Authelia SSO if exposed.
|
|
- Provider: set `AI_PROVIDER` in `src/.env` (openai, replicate, local_gpu, stability, comfyui, invokeai).
|
|
- `comfyui`/`invokeai` can point at this box's own `ai-stack` service — see below.
|
|
MD
|
|
|
|
echo ""
|
|
echo " URL: http://localhost:3080"
|
|
echo " App dir: $APP_DIR"
|
|
echo " Provider: $PP_PROVIDER (change AI_PROVIDER in $APP_DIR/.env)"
|
|
if [ "$PP_USE_AI_STACK_NET" = true ]; then
|
|
echo " Backend: ai-stack's $PP_PROVIDER container (Docker network ai-stack_default)"
|
|
fi
|
|
echo ""
|
|
}
|