The get.docker.com convenience script internally wraps every step in
'sudo -E sh -c ...'. On minimal/cloud Ubuntu images that never installed
the sudo package (common when operating purely as root), those internal
sudo calls silently fail while the outer script still exits 0 — apt never
actually runs, but no error surfaces. require_docker already runs as root,
so there's no need for sudo at all.
Replaced it with Docker's documented apt-repo steps run directly: add the
keyring, add the repo (with architecture/codename detected via dpkg and
os-release), apt-get install docker-ce + compose plugin, enable the
service. Real apt/curl/systemctl failures now propagate and print to the
terminal instead of being masked by the wrapper script.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
require_docker returning non-zero was silently ignored (no set -e).
Add explicit warning so the operator sees the failure; setup.sh already
has an unconditional Docker check after base that will retry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
The Docker check+install was inside the else branch that only runs when
base has never been installed. On re-runs (base already present) Docker
was silently skipped and only warned about. Move the check outside the
if/else so Docker is always installed if missing, regardless of whether
base was skipped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
whiptail fix:
- bootstrap.sh: redirect stdout and stderr to /dev/tty alongside stdin so
whiptail has full terminal control for raw mode (arrow keys, highlighting)
- setup.sh: run 'stty sane' on /dev/tty before the menu loop to reset any
stale terminal state from SSH reconnections or prior sessions
Installed-service summary:
- Print a grouped list of all currently-installed services before every
menu session so the operator knows the current state at a glance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
Two fixes:
1. Export TERM (default xterm-256color) early — whiptail needs a valid
TERM to enter raw mode; when bash is started via pipe TERM may be
unset, causing keypresses to leak to the shell instead of the menu
2. Add </dev/tty to both whiptail calls so keyboard input always comes
from the controlling terminal regardless of how stdin was redirected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
Three fixes:
1. configure_caddy_for_service: remove the '!= example.com' filter that
silently dropped any valid domain matching that string; now any non-empty
SITE_DOMAIN is used as the default subdomain suggestion
2. load_site_config: trim leading/trailing whitespace from key and val so
hand-edited .config files with extra spaces still parse correctly
3. setup.sh: call load_site_config after the site wizard saves so the
in-memory values are guaranteed fresh for all subsequent service installs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
- require_docker now runs as part of base so Docker is present on every box
- Install openssh-server, offer GitHub (gh:) and Launchpad (lp:) key import
via ssh-import-id; disable password auth only after keys are confirmed imported
- Handle Ubuntu cloud-init drop-in that re-enables PasswordAuthentication
- Offer NetBird install with optional --allow-server-ssh flag and setup key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
command -v may miss the binary if sudo stripped PATH; check the canonical
apt install location directly as a fallback before reporting failure, and
use the same fallback when printing the installed version.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
Two bugs in require_docker:
1. apt post-install hooks (needrestart etc.) block on stdin which is
at EOF when running via pipe; DEBIAN_FRONTEND=noninteractive skips them
2. bash's command hash table doesn't pick up a newly installed binary;
hash -r flushes it so command -v docker finds /usr/bin/docker
Also moved usermod and success log after the binary check so [OK] only
prints when docker is actually reachable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
curl | bash consumes stdin from the pipe, so when bootstrap hands off
to setup.sh the script gets EOF immediately and exits with 'Cancelled'.
Redirecting </dev/tty restores keyboard input for the interactive menu.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
BASH_SOURCE[0] is unbound when bash reads from a pipe; set -u turns
this into a fatal error that no amount of :- or set +u reliably fixes
across bash versions. $0 is always set: 'bash' when piped (dirname
gives '.' where no setup.sh exists), and the correct path when run
directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
${BASH_SOURCE[0]:-} still triggers set -u when BASH_SOURCE is entirely
unset (not just empty) in pipe mode. Temporarily disable -u for that
single assignment, then restore it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
set -euo pipefail causes ${BASH_SOURCE[0]} to abort with 'unbound variable'
when the script is fed via curl | bash. Use ${BASH_SOURCE[0]:-} so the
variable expands to an empty string in that context, letting SCRIPT_DIR
resolve safely and the pipe path continue to the git-clone branch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQJBvqzXeyuhhAcAA3Q5Wq
Three root-cause fixes found by reading the Docker image source:
1. Wine/Proton page fault: Docker's default seccomp profile blocks
system calls that Wine Proton GE requires. Fix: security_opt:
seccomp=unconfined + shm_size: 256m (Xvfb needs /dev/shm for
MIT-SHM extension; 64 MB default is too small).
Added network_mode: host for game traffic (dynamic UDP ports).
2. KYBER_MAP_ROTATION exit 64: the Kyber CLI decodes base64 and
parses newline-separated "MODE;MAP_PATH" lines, not JSON objects.
Our JSON [{map:...,mode:...}] format split on semicolons into one
field → ExitCode.usage (64). Fixed builder to emit MODE;MAP_PATH\n
lines and updated .env comment + README example.
3. GPU passthrough removed: Proton GE includes DXVK which crashes
headlessly when a GPU is passed through (no Vulkan display). The
server needs no GPU; removing passthrough is the correct fix.
Also: install libgamemode0:i386 on the host (Wine/Proton dep),
add alphanumeric-password warning (special chars → INVALID_PASSWORD).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014be1aK9G8CY2msho5LjxR4
dall-e-2/dall-e-3 retired May 12 2026 and gpt-image-1 deprecates Oct 23
2026, so move every OpenAI default (config.py, both provider classes,
both compose files, .env.example, the in-app provider-settings dropdown,
README) to gpt-image-2 for both generation and edits. Also fix response
parsing in ai_provider.py's OpenAIProvider, which never sent a model
param and assumed a url response — gpt-image-1/2 only return b64_json.
Separately, .env.example shipped AI_PROVIDER=replicate by default, but
replicate has no driver in remote_provider.py, so following the
documented "cp .env.example .env" setup silently broke every AI call
and defeated the GPU quick-start (an explicit non-empty .env value
overrides docker-compose.gpu.yml's own local_gpu fallback). Default to
local_gpu instead, mark replicate/stability as not-yet-implemented, and
recommend Lykon/dreamshaper-8-inpainting as a hands/face-tuned
HF_MODEL_INPAINT override for 4-6GB cards (Quadro P2200, GTX 1060/1660).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
inpaint()/img2img()/outpaint() called /v1/images/edits without a model
field, so OpenAI defaulted every cloud edit to dall-e-2 regardless of
configuration — while txt2img used dall-e-3. Add a separate
OPENAI_EDIT_MODEL (default gpt-image-1, the only current model that
supports masked edits at ChatGPT-comparable quality), thread it through
the provider and both compose files, and handle gpt-image-1's
b64_json-only response shape alongside the url shape dall-e-2/3 return.
setup.sh's run_service() looks up install_<name> using the literal
hyphenated registered name, not an underscore-converted one. borg-backup,
calibre-web, gaming-backup, and stirling-pdf all used underscored function
names and were therefore uninstallable ("has no install_<name>"). Same
fix already applied to ai-gpu/ai-stack; this closes out the rest of the
repo-wide audit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
PaintPlus's backend already supported invokeai/comfyui providers (generic
"self-hosted, on another machine" remote APIs) but the installer never
exposed them and the compose file never passed the URLs through. Add a
3rd provider choice — shown only when the ai-stack service is installed —
that sets AI_PROVIDER + INVOKEAI_URL/COMFYUI_URL and joins ai-stack's
Docker network (ai-stack_default) so PaintPlus can reach those containers
by name. No cloud key, no extra GPU download: it rides on ai-stack's
already-running InvokeAI/ComfyUI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
Add gpu-mode.sh (written into ~/docker/ai-stack at install): time-shares
one small GPU between local chat (Ollama, for Open WebUI) and local
image-gen (InvokeAI + ComfyUI, backends for PaintPlus), leaving the
always-on services (Open WebUI, Gitea, RAG, MCP, Kiwix) untouched. A new
"small local GPU?" prompt enables it: when on, the GPU defaults to chat
mode after install and the user is told to swap deliberately. Cloud
models need no swap.
Document the intended split in POST-INSTALL-NOTES: Open WebUI is the
text hub (chat/research/light coding, wired to Gitea + GitHub sync + RAG
+ MCP), PaintPlus is the single image hub (cloud or local comfyui/
invokeai backend). Note web search is DuckDuckGo — this installer path
generates no SearXNG, so the SearXNG references are dropped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
Vendor the functional source of github.com/outis1one/local-ai into
./ai-stack (22 files) and add services/ai-stack.sh, which copies the
source to ~/docker/ai-stack and hands off to the app's VRAM-aware
installer (local-ai-setup.sh). The stack bundles Ollama, Open WebUI,
RAG + MCP servers, ChromaDB, SearXNG, Kiwix, Gitea, InvokeAI, ComfyUI
and Portainer.
Cloud LLM providers (Groq/DeepInfra/OpenAI/OpenRouter) are optionally
wired into Open WebUI via the plural OPENAI_API_BASE_URLS list, with the
local RAG connection kept as the first entry so RAG keeps working. Open
WebUI ships built-in auth, so Caddy is configured without Authelia.
Excludes the upstream's two bundled copies of this very project
(ubuntu-post-install.sh, ubuntu-post-install-main.zip) — stale and
circular. Coexists with the existing ai-gpu service.
Also fix the install-function names for ai-gpu and ai-stack: the
dispatcher calls install_<raw-name>, so the function must be
install_ai-gpu / install_ai-stack (hyphen), matching the working
mail-archiver / wg-easy services. ai-gpu was previously uninstallable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
Shows a numbered menu of 12 known SWBF2 maps (Galactic Assault, Planetary
Battles, Starfighter Assault) during install. Selected maps are encoded as
base64 JSON and written to KYBER_MAP_ROTATION in .env.
Map IDs decoded from maprotation.hive binary: Geonosis, Kamino, Endor,
Tatooine, Death Star II, Starkiller Base, Crait, Hoth, Fondor (space).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Bring the full EditmaskwithAI application into the repo under paintplus/
(429 files) so the service is self-contained — the installer copies the
vendored source to ~/docker/paintplus/src instead of cloning at runtime.
Rename to PaintPlus (service + branding; app logic untouched):
- services/editmaskwithai.sh -> services/paintplus.sh (register_service
paintplus, install_paintplus, ~/docker/paintplus, Caddy paintplus:8000,
Authelia option preserved)
- container names -> paintplus across docker-compose*.yml; dev network
-> paintplus-network
- browser <title> -> "PaintPlus - AI Image Editor"; README heading ->
PaintPlus with upstream provenance note
- README utilities table: editmaskwithai -> paintplus
Backend/frontend code (help strings referencing the old container name,
the ai_photo_edit.db filename) is intentionally left as-is to avoid
touching application logic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
Package the self-hosted mask-based AI photo editor
(github.com/outis1one/EditmaskwithAI) as a utilities service. Clones the
repo into ~/docker/editmaskwithai and deploys on port 3080.
Two install modes:
- Cloud API (no GPU): writes .env from the repo template with the chosen
AI_PROVIDER (OpenAI gpt-image or Replicate), API key, and a generated
SECRET_KEY, then `docker compose up -d --build`.
- Local GPU (NVIDIA): runs the repo's own install-local-gpu.sh and
bring-up-local-gpu.sh (downloads ~13 GB of models).
The app has no built-in auth, so it offers Authelia SSO and reverse-proxies
via Caddy (ai-photo-edit:8000). Cloud mode joins caddy_net through a
docker-compose.override.yml; GPU mode is attached with `docker network
connect` since the GPU compose runs with an explicit -f and does not merge
overrides.
Also lists the service in the README utilities table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
When run directly without setup.sh, write_readme is not defined.
Add a minimal implementation to the else block of both kyber-server.sh
and sunshine.sh so standalone runs no longer fail with command not found.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Generalize the optional Groq toggle into a provider picker so the GPU AI
stack can wire Groq, DeepInfra, OpenAI, and/or OpenRouter into Open WebUI
at once. All are OpenAI-compatible, so they share Open WebUI's
OPENAI_API_BASE_URLS / OPENAI_API_KEYS (semicolon-separated, matched by
position). Keys are written to llm/.env; only ${VAR} references go into
docker-compose.yml.
Updates the install prompt, .env template, compose patch, README, the
dry-run note, and the final summary accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nb2vJ8W7bHKx1JXVvpCraH
If the kyber bin symlink and desktop entry are not present, prompt to
set up the launcher. Calls install_kyber_launcher directly if already
loaded (wizard), or sources kyber-launcher.sh when run standalone.
Skips the prompt on rerun if the launcher is already installed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
If auth.toml has no token, launch the Kyber AppImage as the actual user
(with DISPLAY/XAUTHORITY passed through), prompt the user to log in via
the EA Account button, then re-read auth.toml once they close Kyber.
Falls back to manual token paste if auth.toml still has nothing.
Skips the AppImage entirely on rerun when a token is already cached.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Running kyber_cli get_token under sudo opens a broken browser OAuth flow.
Instead, read the token directly from ~/.local/share/maxima/auth.toml which
Kyber writes after a normal GUI login. If missing, show clear instructions
to run the AppImage as yourself first, then re-run the installer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
On rerun, if ~/.local/share/maxima exists, offer to wipe it so EA login
starts completely fresh (fixes stuck/broken auth states).
Single-quote all .env values so passwords and tokens containing $, !, &,
spaces, and other shell special characters are written safely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
The standard Steam path search missed installs on secondary drives.
Now also reads libraryfolders.vdf to find all Steam library paths, then
falls back to a broad find across home/mnt/media. Wolf's copy is excluded
from the fallback search to prefer the native Steam install.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Adds a Moonlight app entry that launches SWBF2 directly via Steam
(steam://rungameid/1237950) — both with and without the Kyber entry.
Moonlight app list now shows: Kyber SWBF2 | SWBF2 Single Player | Desktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
When SWBF2 is not found locally, prompt to rsync from a remote host.
Asks for remote host, username, and path (defaults to the standard Steam
Linux path). Files land in ~/docker/kyber-server/swbf2/ and that path is
used automatically. Falls back to manual local path entry if rsync declined.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Instead of hard-failing, show an rsync example for copying game files from
another machine, then prompt for the path. Validates that starwarsbattlefrontii.exe
exists at the given location before continuing.
Also silence "register_service: command not found" in all three services when
run standalone (guard with command -v check).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs
Steam must be running when Kyber is used. The installer now detects whether
Steam is present (command or ~/.steam path), and if not, prompts to install
it via apt (steam-installer or steam package, with i386 multiarch enabled).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D8ckUJQtj1pH8jtAddBDZs