launcher: image auto-resolution, --user in-place config persistence, --debug...

launcher: image auto-resolution, --user in-place config persistence, --debug SPEC, install/uninstall image handling

run_oci.sh:
- Resolve the installed coderai image when none is given (single -> use it,
  several -> menu); no longer hardcodes a possibly-wrong tag.
- --debug accepts SPEC as the next token (not just --debug=SPEC) and won't
  swallow an image-looking arg, fixing "image: <debug spec>" mishaps.
- --local puts the runtime dir under ~/.config/coderai-runtime (override
  --data-dir).
- New --user[=UID[:GID]]: run as that user and switch a config dir to an
  IN-PLACE mount so the app's config edits persist (owned by you). Without
  --user, --local/--config-dir stay a throwaway copy. Banner shows user.

Non-destructive config persistence (no startup rewrite):
- codai/cli.py: new --host/--port that override config.server in memory only.
- codai/main.py: apply those overrides right after config load (never written
  to config.json).
- coderai-oci: pass --host/--port to the server and stop rewriting an existing
  config.json (only create one on true first run) — so an in-place-mounted
  config dir is never modified at startup.

install.sh / uninstall.sh:
- install: after loading, offer to remove OTHER coderai images (default no);
  no auto-launch.
- uninstall: resolve the installed coderai image(s) to remove (single ->
  confirm, several -> menu / all / none) instead of a hardcoded tag.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 69ff740e
......@@ -194,6 +194,23 @@ configuration directory (--config DIR, default: OS-specific CoderAI directory).
"volume when /tmp is small — 4x upscaling can exhaust a small /tmp "
"('No space left on device').",
)
parser.add_argument(
"--host",
type=str,
default=None,
metavar="ADDR",
help="Override the server bind host (config.server.host) for this run only, "
"WITHOUT modifying config.json. Used by the container launcher so an "
"in-place-mounted config dir is never rewritten.",
)
parser.add_argument(
"--port",
type=int,
default=None,
metavar="PORT",
help="Override the server bind port (config.server.port) for this run only, "
"WITHOUT modifying config.json.",
)
parser.add_argument(
"--debug",
action="store_true",
......
......@@ -321,6 +321,16 @@ def main():
config_mgr = ConfigManager(config_dir)
config = config_mgr.load()
# CLI --host/--port override the server bind for THIS run only, in memory —
# they are never written back to config.json. The container launcher uses this
# so a config dir mounted in place (persistent) is not modified at startup.
_cli_host = getattr(args, "host", None)
_cli_port = getattr(args, "port", None)
if _cli_host:
config.server.host = _cli_host
if _cli_port:
config.server.port = int(_cli_port)
# Configure the temporary-working-files directory as early as possible (before
# any tempfile.* call). CLI --tmp overrides config.tmp_dir. Setting both
# tempfile.tempdir and TMPDIR/TMP/TEMP makes every tempfile.* call AND child
......
......@@ -55,7 +55,10 @@ if ! "$ENGINE" info >/dev/null 2>&1; then
fi
say "[install] loading image from $IMAGE_TAR — this is large, please wait…"
"${DK[@]}" load -i "$IMAGE_TAR"
LOAD_OUT="$("${DK[@]}" load -i "$IMAGE_TAR")"
say "$LOAD_OUT"
# e.g. "Loaded image: coderai:full_all_0.1.0" — the tag we just installed.
LOADED_TAG="$(printf '%s\n' "$LOAD_OUT" | sed -n 's/^Loaded image: //p' | head -1)"
# Pick the install dir by privilege.
if [ "$(id -u)" -eq 0 ]; then
......@@ -85,6 +88,35 @@ if [ "$(id -u)" -ne 0 ]; then
esac
fi
# Offer to remove OTHER coderai images (e.g. older versions) — default NO.
OTHERS=()
while IFS= read -r line; do
[ -n "$line" ] && [ "$line" != "$LOADED_TAG" ] && OTHERS+=("$line")
done < <(
"${DK[@]}" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null \
| grep -E '^coderai:' | grep -v ':<none>$' | sort -u)
if [ "${#OTHERS[@]}" -gt 0 ]; then
say ""
say "[install] other coderai image(s) already present:"
for img in "${OTHERS[@]}"; do say " $img"; done
# Default NO — only remove on an explicit yes (and not under non-interactive --yes).
if [ "$ASSUME_YES" -ne 1 ]; then
printf '[install] remove the other image(s) above? [y/N] '
read -r reply </dev/tty || reply=""
case "$reply" in
y|Y|yes|YES)
for img in "${OTHERS[@]}"; do
"${DK[@]}" image rm "$img" >/dev/null 2>&1 && say "[install] removed $img" \
|| say "[install] could not remove $img (in use?)"
done ;;
*) say "[install] keeping the other image(s)." ;;
esac
else
say "[install] --yes given; keeping the other image(s) (default)."
fi
fi
say ""
say "Done. Quick start:"
say " coderai-docker --nvidia # or --vulkan / --cpu"
......
......@@ -2,7 +2,9 @@
# CoderAI Docker distribution uninstaller — reverses install.sh. It:
# 1. Removes the `coderai-docker` runner from both the root and user install
# dirs (/usr/local/bin and ~/.local/usr/bin).
# 2. Removes the loaded image (unless --keep-image).
# 2. Removes the installed CoderAI image(s) (unless --keep-image). With no
# --image, it resolves the actual loaded coderai image(s): one -> that one,
# several -> asks which (or all). --image TAG targets a specific tag.
# 3. Points out the PATH line install.sh may have added to ~/.bashrc.
#
# It does NOT touch your runtime data (coderai-runtime/, ~/.coderai) — those are
......@@ -10,20 +12,22 @@
#
# Usage: ./uninstall.sh [--keep-image] [--image TAG] [--yes]
# Env: CONTAINER_ENGINE=docker|podman (default docker)
# OCI_IMAGE=TAG image tag to remove (default coderai:dist)
# OCI_IMAGE=TAG specific image tag to remove
set -euo pipefail
ENGINE="${CONTAINER_ENGINE:-docker}"
IMAGE="${OCI_IMAGE:-coderai:dist}"
IMAGE="${OCI_IMAGE:-}"
IMAGE_EXPLICIT=0
[ -n "$IMAGE" ] && IMAGE_EXPLICIT=1
KEEP_IMAGE=0
ASSUME_YES=0
while [[ $# -gt 0 ]]; do
case "$1" in
--keep-image) KEEP_IMAGE=1; shift ;;
--image) [[ $# -ge 2 ]] || { echo "Error: --image requires a tag" >&2; exit 2; }; IMAGE="$2"; shift 2 ;;
--image) [[ $# -ge 2 ]] || { echo "Error: --image requires a tag" >&2; exit 2; }; IMAGE="$2"; IMAGE_EXPLICIT=1; shift 2 ;;
-y|--yes) ASSUME_YES=1; shift ;;
-h|--help) sed -n '2,12p' "$0"; exit 0 ;;
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
*) echo "Error: unknown option: $1" >&2; exit 2 ;;
esac
done
......@@ -32,14 +36,23 @@ say(){ printf '%s\n' "$*"; }
die(){ printf 'Error: %s\n' "$*" >&2; exit 1; }
# Tell the user what this will do, and confirm before proceeding.
say "This will remove the 'coderai-docker' runner and$([ "$KEEP_IMAGE" -eq 1 ] && echo ' keep' || echo ' (after a prompt) remove') the CoderAI image"
say "'$IMAGE' from $ENGINE. Your runtime data and config are not touched."
if [ "$KEEP_IMAGE" -eq 1 ]; then _img_note="keep the CoderAI image(s)";
elif [ "$IMAGE_EXPLICIT" -eq 1 ]; then _img_note="(after a prompt) remove the image '$IMAGE'";
else _img_note="(after a prompt) remove the installed CoderAI image(s)"; fi
say "This will remove the 'coderai-docker' runner and $_img_note from $ENGINE."
say "Your runtime data and config are not touched."
if [ "$ASSUME_YES" -ne 1 ]; then
printf 'Proceed? [y/N] '
read -r reply </dev/tty || reply=""
case "$reply" in y|Y|yes|YES) ;; *) die "aborted by user." ;; esac
fi
# Engine access (sudo only if needed for the daemon).
DK=("$ENGINE")
if ! "$ENGINE" info >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then DK=(sudo "$ENGINE"); fi
fi
# 1. Remove the runner from every place install.sh may have put it.
removed_any=0
for d in /usr/local/bin "$HOME/.local/usr/bin"; do
......@@ -55,26 +68,64 @@ for d in /usr/local/bin "$HOME/.local/usr/bin"; do
done
[ "$removed_any" -eq 1 ] || say "[uninstall] no coderai-docker runner found in the standard locations."
# 2. Remove the image unless asked to keep it.
if [ "$KEEP_IMAGE" -eq 0 ]; then
DK=("$ENGINE")
if ! "$ENGINE" info >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then DK=(sudo "$ENGINE"); fi
fi
# Remove one image tag (helper).
rm_image(){
"${DK[@]}" image rm "$1" >/dev/null 2>&1 && say "[uninstall] removed image: $1" \
|| say "[uninstall] could not remove image: $1"
}
# 2. Remove the image(s) unless asked to keep them.
if [ "$KEEP_IMAGE" -eq 1 ]; then
say "[uninstall] keeping image(s) (--keep-image)."
elif [ "$IMAGE_EXPLICIT" -eq 1 ]; then
# Explicit tag: remove just that one.
if "${DK[@]}" image inspect "$IMAGE" >/dev/null 2>&1; then
if [ "$ASSUME_YES" -ne 1 ]; then
if [ "$ASSUME_YES" -eq 1 ]; then rm_image "$IMAGE"; else
printf '[uninstall] remove image "%s"? [y/N] ' "$IMAGE"
read -r reply </dev/tty || reply=""
case "$reply" in y|Y|yes|YES) ;; *) say "[uninstall] keeping image $IMAGE."; KEEP_IMAGE=1 ;; esac
fi
if [ "$KEEP_IMAGE" -eq 0 ]; then
"${DK[@]}" image rm "$IMAGE" && say "[uninstall] removed image: $IMAGE"
case "$reply" in y|Y|yes|YES) rm_image "$IMAGE" ;; *) say "[uninstall] keeping image $IMAGE." ;; esac
fi
else
say "[uninstall] image '$IMAGE' not present (use --image TAG if you tagged it differently)."
say "[uninstall] image '$IMAGE' not present."
fi
else
say "[uninstall] keeping image (--keep-image)."
# Resolve the installed coderai image(s).
IMGS=()
while IFS= read -r line; do [ -n "$line" ] && IMGS+=("$line"); done < <(
"${DK[@]}" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null \
| grep -E '^coderai:' | grep -v ':<none>$' | sort -u)
case "${#IMGS[@]}" in
0) say "[uninstall] no coderai image found to remove." ;;
1)
if [ "$ASSUME_YES" -eq 1 ]; then rm_image "${IMGS[0]}"; else
printf '[uninstall] remove image "%s"? [y/N] ' "${IMGS[0]}"
read -r reply </dev/tty || reply=""
case "$reply" in y|Y|yes|YES) rm_image "${IMGS[0]}" ;; *) say "[uninstall] keeping image ${IMGS[0]}." ;; esac
fi
;;
*)
if [ "$ASSUME_YES" -eq 1 ]; then
say "[uninstall] multiple coderai images; --yes -> removing all:"
for img in "${IMGS[@]}"; do rm_image "$img"; done
else
say "Multiple coderai images found:"
i=1; for img in "${IMGS[@]}"; do printf ' %d) %s\n' "$i" "$img"; i=$((i+1)); done
printf 'Remove which? [1-%d / a=all / n=none, default n]: ' "${#IMGS[@]}"
read -r sel </dev/tty || sel=""
case "$sel" in
a|A|all) for img in "${IMGS[@]}"; do rm_image "$img"; done ;;
""|n|N|no|NO) say "[uninstall] keeping all images." ;;
*)
if printf '%s' "$sel" | grep -qE '^[0-9]+$' && [ "$sel" -ge 1 ] && [ "$sel" -le "${#IMGS[@]}" ]; then
rm_image "${IMGS[$((sel-1))]}"
else
die "invalid selection: $sel"
fi
;;
esac
fi
;;
esac
fi
# 3. Note the PATH line install.sh may have appended (we don't edit ~/.bashrc).
......
......@@ -32,8 +32,10 @@ mkdir -p "$CODERAI_CONFIG_DIR/coderai" "$CODERAI_MODELS_DIR/coderai" "$CODERAI_C
CONFIG_DIR="$CODERAI_CONFIG_DIR/coderai"
CONFIG_FILE="$CONFIG_DIR/config.json"
# Ensure the container binds to all interfaces even when config.json was created
# by the app default, which uses 127.0.0.1 for local desktop installs.
# Bind host/port are passed to coderai on the CLI (--host/--port below), which
# override config.server in memory WITHOUT modifying config.json. This keeps an
# in-place-mounted config dir untouched at startup. We only CREATE config.json on
# first run (when it doesn't exist yet) — that's not modifying user config.
if [ ! -f "$CONFIG_FILE" ]; then
/opt/coderai/python/bin/python3 - "$CONFIG_FILE" "$CODERAI_HOST" "$CODERAI_PORT" <<'PY'
import json
......@@ -45,31 +47,6 @@ host = sys.argv[2]
port = int(sys.argv[3])
path.write_text(json.dumps({"server": {"host": host, "port": port}}, indent=2) + "\n")
PY
else
/opt/coderai/python/bin/python3 - "$CONFIG_FILE" "$CODERAI_HOST" "$CODERAI_PORT" <<'PY'
import json
import sys
from pathlib import Path
path = Path(sys.argv[1])
host = sys.argv[2]
port = int(sys.argv[3])
try:
data = json.loads(path.read_text())
except Exception:
data = {}
server = data.setdefault("server", {})
changed = False
if server.get("host") in (None, "", "127.0.0.1", "localhost") or host != "0.0.0.0":
if server.get("host") != host:
server["host"] = host
changed = True
if server.get("port") != port:
server["port"] = port
changed = True
if changed:
path.write_text(json.dumps(data, indent=2) + "\n")
PY
fi
# Optional debug logging. CODERAI_DEBUG selects coderai's --debug* flags:
......@@ -97,8 +74,9 @@ esac
# only sub-flags.
case " $DEBUG_ARGS " in *" --debug "*) : ;; *[!\ ]*) DEBUG_ARGS="--debug$DEBUG_ARGS" ;; esac
# Assemble the server argv: --config, optional --tmp, debug flags, then passthrough.
set -- --config "$CONFIG_DIR" "$@"
# Assemble the server argv: --config, --host/--port (override config.server in
# memory, never written to config.json), optional --tmp, debug flags, passthrough.
set -- --config "$CONFIG_DIR" --host "$CODERAI_HOST" --port "$CODERAI_PORT" "$@"
[ -n "${CODERAI_TMP:-}" ] && set -- "$@" --tmp "$CODERAI_TMP"
# Extra coderai CLI flags passed straight through from the host launcher
......
......@@ -9,7 +9,14 @@ if [[ -f "$VERSIONS_FILE" ]]; then
fi
ENGINE="${CONTAINER_ENGINE:-docker}"
# Default image tag (this literal is pinned by make_dist_bundle to the shipped
# tag). It's only a FALLBACK: when the user gives no explicit image (positional
# arg) and OCI_IMAGE is unset, we resolve the actual loaded coderai image below —
# auto-pick when there's exactly one, ask when there are several — so the runner
# always targets a real installed image instead of a possibly-wrong fixed tag.
IMAGE_TAG="${OCI_IMAGE:-coderai:dist}"
IMAGE_EXPLICIT=0
[[ -n "${OCI_IMAGE:-}" ]] && IMAGE_EXPLICIT=1
# Selected GPU backends. ADDITIVE: --nvidia --vulkan enables BOTH, so the
# container gets the NVIDIA driver libs (libcuda.so.1 — needed even by a
# CUDA-built llama-cpp running under Vulkan) AND /dev/dri. CPU always works.
......@@ -26,7 +33,19 @@ HOST_BIND="${CODERAI_HOST_BIND:-}"
# container (via CODERAI_EXTRA_ARGS, appended by the in-image coderai launcher).
# Built from --coderai-arg (repeatable, one token each) and --coderai-args "...".
CODERAI_EXTRA_ARGS="${CODERAI_EXTRA_ARGS:-}"
# Runtime dir (models/cache, and config when not using --local). Defaults to
# $PWD/coderai-runtime, but for --local it moves to a stable per-user location
# (~/.config/coderai-runtime) so it doesn't depend on the current directory —
# unless the user pins it with --data-dir. DATA_ROOT_EXPLICIT tracks --data-dir.
DATA_ROOT="$PWD/coderai-runtime"
DATA_ROOT_EXPLICIT=0
IS_LOCAL=0
# Run the container as a specific user. Empty = container default (root). When set
# (e.g. via --user with no value -> your uid:gid) AND a config dir is used, the
# config is mounted IN PLACE so the app's edits persist to it (files stay owned by
# you). Without --user, an in-place mount as root would create root-owned files,
# so we fall back to the throwaway temp copy.
USER_SPEC=""
DETACH=0
NAME="coderai"
EXTRA_ARGS=()
......@@ -87,18 +106,26 @@ Options:
--coderai-args STR Pass a raw string of extra coderai flags (space-separated),
e.g. --coderai-args "--foo bar --baz". Appended after any
--coderai-arg values.
--data-dir PATH Directory for config/models/cache (default: ./coderai-runtime).
--data-dir PATH Directory for config/models/cache. Default ./coderai-runtime,
or ~/.config/coderai-runtime when --local is used.
--name NAME Container name (default: coderai).
-d, --detach Run in background.
--config-dir PATH Use an EXISTING config dir (with config.json/models.json),
mounted at /config/coderai. Copied to a temp dir by default
so the image's host/port rewrite leaves your dir untouched.
--local Shortcut for --config-dir ~/.coderai.
--inplace-config Mount --config-dir in place (the image WILL edit host/port).
--local Shortcut for --config-dir ~/.coderai. Also puts the runtime
dir under ~/.config/coderai-runtime (override with --data-dir).
Add --user to persist the app's config edits back to it.
--user[=UID[:GID]] Run the container as that user (no value = your uid:gid). With
a config dir, switches to an IN-PLACE mount so config edits
persist there, owned by you. Without it, --config-dir/--local
use a throwaway copy (no persistence).
--inplace-config Mount --config-dir in place (config edits persist there).
--map HOST[:CONT] Bind-mount a host dir at the SAME path (or HOST:CONT) inside
the container, so absolute paths in models.json resolve
(e.g. --map /AI/guffcache). Repeatable.
--debug[=SPEC] Run coderai with debug flags. SPEC (default 'all'):
--debug[=SPEC] Run coderai with debug flags. SPEC (default 'all'); may be
given as --debug=SPEC or --debug SPEC:
all | engine,requests,ws,web,thermal,lora,engine-web
Also writes a host-tailable file log (see --log-file).
--log-file PATH In-container log path (default /cache/logs/coderai.log,
......@@ -144,19 +171,34 @@ while [[ $# -gt 0 ]]; do
CODERAI_EXTRA_ARGS="${CODERAI_EXTRA_ARGS:+$CODERAI_EXTRA_ARGS }$2"; shift 2 ;;
--data-dir)
[[ $# -ge 2 ]] || { echo "Error: --data-dir requires a path" >&2; exit 2; }
DATA_ROOT="$2"; shift 2 ;;
DATA_ROOT="$2"; DATA_ROOT_EXPLICIT=1; shift 2 ;;
--name)
[[ $# -ge 2 ]] || { echo "Error: --name requires a value" >&2; exit 2; }
NAME="$2"; shift 2 ;;
--config-dir)
[[ $# -ge 2 ]] || { echo "Error: --config-dir requires a path" >&2; exit 2; }
CONFIG_DIR_SRC="$2"; shift 2 ;;
--local) CONFIG_DIR_SRC="$HOME/.coderai"; shift ;;
--local) CONFIG_DIR_SRC="$HOME/.coderai"; IS_LOCAL=1; shift ;;
--inplace-config) INPLACE_CONFIG=1; shift ;;
# --user [UID[:GID]] runs the container as that user (default: your uid:gid).
# With a config dir, this also switches the mount to IN PLACE so edits persist.
--user)
if [[ $# -ge 2 && "$2" =~ ^[0-9] ]]; then USER_SPEC="$2"; shift 2
else USER_SPEC="$(id -u):$(id -g)"; shift; fi ;;
--user=*) USER_SPEC="${1#*=}"; shift ;;
--map)
[[ $# -ge 2 ]] || { echo "Error: --map requires HOST[:CONT]" >&2; exit 2; }
MAPS+=("$2"); shift 2 ;;
--debug) DEBUG_SPEC="all"; shift ;;
# --debug accepts an optional SPEC as the next token (e.g. --debug engine,ws)
# OR as --debug=SPEC. The next token is only taken as SPEC when it isn't
# another option and doesn't look like an image ref (no ':' or '/'), so e.g.
# `--debug coderai:tag` leaves the tag as the image, not the debug spec.
--debug)
if [[ $# -ge 2 && "$2" != -* && "$2" != *[:/]* ]]; then
DEBUG_SPEC="$2"; shift 2
else
DEBUG_SPEC="all"; shift
fi ;;
--debug=*) DEBUG_SPEC="${1#*=}"; shift ;;
--log-file)
[[ $# -ge 2 ]] || { echo "Error: --log-file requires a path" >&2; exit 2; }
......@@ -177,10 +219,54 @@ while [[ $# -gt 0 ]]; do
break ;;
-h|--help) usage; exit 0 ;;
-*) echo "Error: unknown option: $1" >&2; usage >&2; exit 2 ;;
*) IMAGE_TAG="$1"; shift ;;
*) IMAGE_TAG="$1"; IMAGE_EXPLICIT=1; shift ;;
esac
done
# Resolve the image when the user didn't name one: prefer the single loaded
# coderai image; if several exist, ask (default = the pinned fallback if present,
# else the first). Keeps the runner pointed at a real installed image instead of
# a fixed tag that may not exist after a rebuild/retag.
if [[ "$IMAGE_EXPLICIT" -eq 0 ]]; then
_imgs=()
while IFS= read -r _l; do [[ -n "$_l" ]] && _imgs+=("$_l"); done < <(
"$ENGINE" images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null \
| grep -E '^coderai:' | grep -v ':<none>$' | sort -u)
if [[ "${#_imgs[@]}" -eq 1 ]]; then
IMAGE_TAG="${_imgs[0]}"
elif [[ "${#_imgs[@]}" -gt 1 ]]; then
# Default selection: the pinned fallback if it's one of them, else the first.
_default="${_imgs[0]}"
for _i in "${_imgs[@]}"; do [[ "$_i" == "$IMAGE_TAG" ]] && _default="$IMAGE_TAG"; done
if [[ -t 0 ]]; then
echo "Multiple coderai images found — choose one to run:" >&2
_n=1; for _i in "${_imgs[@]}"; do
_mark=""; [[ "$_i" == "$_default" ]] && _mark=" (default)"
printf ' %d) %s%s\n' "$_n" "$_i" "$_mark" >&2; _n=$((_n+1))
done
printf 'Selection [1-%d, default %s]: ' "${#_imgs[@]}" "$_default" >&2
read -r _sel </dev/tty || _sel=""
if [[ -z "$_sel" ]]; then
IMAGE_TAG="$_default"
elif [[ "$_sel" =~ ^[0-9]+$ ]] && (( _sel >= 1 && _sel <= ${#_imgs[@]} )); then
IMAGE_TAG="${_imgs[$((_sel-1))]}"
else
echo "Error: invalid selection: $_sel" >&2; exit 2
fi
else
IMAGE_TAG="$_default"
echo "Note: multiple coderai images; no TTY to choose — using $IMAGE_TAG" >&2
fi
fi
# 0 images: keep the pinned fallback (docker run will report if it's missing).
fi
# For --local, default the runtime dir to a stable per-user location under
# ~/.config instead of the current directory (unless --data-dir was given).
if [[ "$IS_LOCAL" -eq 1 && "$DATA_ROOT_EXPLICIT" -eq 0 ]]; then
DATA_ROOT="${XDG_CONFIG_HOME:-$HOME/.config}/coderai-runtime"
fi
mkdir -p "$DATA_ROOT/config" "$DATA_ROOT/models" "$DATA_ROOT/cache"
DATA_ROOT="$(cd "$DATA_ROOT" && pwd)"
......@@ -197,6 +283,9 @@ args=(run --rm --name "$NAME" --ipc=host -p "$PUBLISH" -e CODERAI_HOST=0.0.0.0 -
if [[ -n "$CODERAI_EXTRA_ARGS" ]]; then
args+=(-e "CODERAI_EXTRA_ARGS=$CODERAI_EXTRA_ARGS")
fi
if [[ -n "$USER_SPEC" ]]; then
args+=(--user "$USER_SPEC")
fi
if [[ "$DETACH" == "1" ]]; then
args+=(-d)
fi
......@@ -209,6 +298,15 @@ fi
if [[ -n "${MODES[nvidia]:-}" ]]; then
if [[ "$ENGINE" == "docker" ]]; then
args+=(--gpus all)
# Some hosts set `no-cgroups = true` in /etc/nvidia-container-runtime/config.toml.
# Then --gpus all injects the device nodes + driver libs but does NOT add them to
# the container's device-cgroup allowlist, so the kernel blocks GPU access and
# NVML fails ("Failed to initialize NVML: Unknown Error") -> torch.cuda sees no
# GPU -> coderai falls back to vulkan/cpu. Passing the nodes via --device adds
# them to the cgroup allowlist. Harmless when cgroups are managed normally.
for _dev in /dev/nvidia*; do
[[ -c "$_dev" ]] && args+=(--device "$_dev")
done
else
args+=(--hooks-dir=/usr/share/containers/oci/hooks.d)
fi
......@@ -260,19 +358,25 @@ CONFIG_NOTE="$DATA_ROOT/config (fresh)"
if [[ -n "$CONFIG_DIR_SRC" ]]; then
[[ -d "$CONFIG_DIR_SRC" ]] || { echo "Error: --config-dir '$CONFIG_DIR_SRC' not found" >&2; exit 2; }
CONFIG_DIR_SRC="$(cd "$CONFIG_DIR_SRC" && pwd)"
if [[ "$INPLACE_CONFIG" == "1" ]]; then
# Mount IN PLACE (so the app's config edits persist back to the real dir) when
# explicitly asked (--inplace-config) OR when running as a specific --user (so
# the files written stay owned by you, not root). The in-image launcher no
# longer rewrites host/port into config.json (it passes them on the CLI), so an
# in-place mount is non-destructive. Without --user, fall back to a throwaway
# copy so a root container can't leave root-owned files in your real config.
if [[ "$INPLACE_CONFIG" == "1" || -n "$USER_SPEC" ]]; then
CFG_MOUNT="$CONFIG_DIR_SRC"
CONFIG_NOTE="$CONFIG_DIR_SRC (in place — image rewrites host/port!)"
CONFIG_NOTE="$CONFIG_DIR_SRC (in place — edits persist here)"
else
# Copy ONLY the json config files to a throwaway dir so the image's host/port
# rewrite never touches your real config, and we don't copy big subdirs
# Copy ONLY the json config files to a throwaway dir so nothing in the
# container can touch your real config, and we don't copy big subdirs
# (e.g. ~/.coderai/ds4 weights).
CFG_PARENT="$(mktemp -d "${TMPDIR:-/tmp}/coderai-cfg.XXXXXX")"
CFG_MOUNT="$CFG_PARENT/coderai"
mkdir -p "$CFG_MOUNT"
cp -a "$CONFIG_DIR_SRC"/*.json "$CFG_MOUNT/" 2>/dev/null || true
[[ -f "$CFG_MOUNT/config.json" ]] || { echo "Error: no config.json in '$CONFIG_DIR_SRC'" >&2; exit 2; }
CONFIG_NOTE="$CONFIG_DIR_SRC$CFG_MOUNT (copy; original untouched)"
CONFIG_NOTE="$CONFIG_DIR_SRC$CFG_MOUNT (temporary copy; original untouched)"
fi
args+=(-v "$CFG_MOUNT:/config/coderai$volume_suffix" \
-v "$DATA_ROOT/models:/models$volume_suffix" -v "$DATA_ROOT/cache:/cache$volume_suffix")
......@@ -340,6 +444,7 @@ Starting CoderAI OCI container
debug: ${DEBUG_SPEC:-off}
log: $LOG_HOST_NOTE
tools: $TOOLS_NOTE
user: ${USER_SPEC:-container default (root)}
cdr-args:${CODERAI_EXTRA_ARGS:+ $CODERAI_EXTRA_ARGS}
EOF
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment