Spaces:
Paused
Paused
| # Bring up the Auralynq stack (rootless Podman, no sudo). See ADR-0012/0013/0014. | |
| # | |
| # Networking: this host's CNI generates conflists with cniVersion 1.0.0, but the | |
| # installed `firewall` plugin only supports up to 0.4.0 β so the network silently | |
| # loses container DNS. The default `podman` network also ships without the | |
| # `dnsname` plugin. We fix BOTH without sudo by rewriting the relevant conflist(s) | |
| # under ~/.config/cni/net.d to cniVersion 0.4.0 + ensuring the dnsname plugin is | |
| # present. With DNS working, services reach peers by container_name | |
| # (auralynq-qdrant / auralynq-api / auralynq-web) β see ADR-0014. | |
| set -euo pipefail | |
| cd "$(dirname "$0")/.." | |
| COMPOSE="$(./scripts/check_container_runtime.sh)" | |
| CF="compose.yml" | |
| CNI_DIR="${HOME}/.config/cni/net.d" | |
| # Patch a CNI conflist in place: pin cniVersion 0.4.0 and append dnsname if absent. | |
| patch_conflist() { | |
| local f="$1" | |
| [ -f "$f" ] || return 0 | |
| python3 - "$f" <<'PY' | |
| import json, sys | |
| p = sys.argv[1] | |
| d = json.load(open(p)) | |
| changed = False | |
| if d.get("cniVersion") != "0.4.0": | |
| d["cniVersion"] = "0.4.0"; changed = True | |
| types = [pl.get("type") for pl in d.get("plugins", [])] | |
| if "dnsname" not in types: | |
| d.setdefault("plugins", []).append( | |
| {"type": "dnsname", "domainName": "dns.podman", "capabilities": {"aliases": True}}) | |
| changed = True | |
| if changed: | |
| json.dump(d, open(p, "w"), indent=2) | |
| print(f" patched {p} -> 0.4.0 + dnsname") | |
| PY | |
| } | |
| echo "β ensuring rootless CNI networks have DNS (no sudo)β¦" | |
| # The default podman network (used by podman-compose) + any project network. | |
| shopt -s nullglob | |
| for f in "${CNI_DIR}"/87-podman.conflist "${CNI_DIR}"/*podman*.conflist "${CNI_DIR}"/auralynq*.conflist; do | |
| patch_conflist "$f" | |
| done | |
| shopt -u nullglob | |
| # Prefer an exported env var (e.g. from scripts/run_local.sh) over the .env file, | |
| # matching podman-compose's own precedence so the printed URL is accurate. | |
| bind_internal="${AURALYNQ_BIND_INTERNAL:-$(grep -E '^AURALYNQ_BIND_INTERNAL=' .env 2>/dev/null | cut -d= -f2)}"; bind_internal="${bind_internal:-127.0.0.1}" | |
| https_port="${AURALYNQ_HTTPS_PORT:-$(grep -E '^AURALYNQ_HTTPS_PORT=' .env 2>/dev/null | cut -d= -f2)}"; https_port="${https_port:-8443}" | |
| # ββ Host Ollama reachability (rootless) βββββββββββββββββββββββββββββββββββββββ | |
| # Containers on the rootless compose bridge cannot reach host loopback services, | |
| # and Podman auto-injects host.containers.internal pointing at the bridge gateway | |
| # (10.88.0.1) β which is NOT the host in rootless mode. If the user runs Ollama | |
| # on the host, route the containers to the host's real LAN IP instead (Ollama | |
| # binds 0.0.0.0, so the bridge can reach it there). Without this the API silently | |
| # degrades to the extractive LLM fallback even when a local GPU model is present. | |
| existing_base="${AURALYNQ_LLM__BASE_URL:-$(grep -E '^AURALYNQ_LLM__BASE_URL=' .env 2>/dev/null | cut -d= -f2- || true)}" | |
| if [ -z "${existing_base}" ]; then | |
| host_ip="$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+' | head -1 || true)" | |
| if [ -n "${host_ip}" ] && curl -fsS -m 2 "http://${host_ip}:11434/api/tags" >/dev/null 2>&1; then | |
| export AURALYNQ_LLM__BASE_URL="http://${host_ip}:11434" | |
| echo "β host Ollama reachable at ${host_ip}:11434 β routing containers there (local model, not extractive)." | |
| else | |
| echo "β host Ollama not detected β API will use its configured/auto LLM (extractive fallback if none)." | |
| fi | |
| fi | |
| # Same reasoning for a host-side vLLM server: inside the container `localhost` | |
| # is the container, so a vLLM on the host is only reachable via the LAN IP. | |
| existing_vllm="${AURALYNQ_LLM__VLLM_BASE_URL:-$(grep -E '^AURALYNQ_LLM__VLLM_BASE_URL=' .env 2>/dev/null | cut -d= -f2- || true)}" | |
| if [ -z "${existing_vllm}" ]; then | |
| vllm_port="${AURALYNQ_VLLM_PORT:-8001}" | |
| vllm_host_ip="$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+' | head -1 || true)" | |
| if [ -n "${vllm_host_ip}" ]; then | |
| export AURALYNQ_LLM__VLLM_BASE_URL="http://${vllm_host_ip}:${vllm_port}/v1" | |
| if curl -fsS -m 2 "http://${vllm_host_ip}:${vllm_port}/v1/models" >/dev/null 2>&1; then | |
| echo "β host vLLM reachable at ${vllm_host_ip}:${vllm_port} β routing containers there." | |
| fi | |
| fi | |
| fi | |
| # ββ Optional NVIDIA GPU visibility for hardware detection βββββββββββββββββββββ | |
| # The API's ModelFit page reports the host's real GPUs. In a rootless container | |
| # without the nvidia-container-toolkit those GPUs are invisible, so the report | |
| # would wrongly say "CPU only". nvidia-smi only needs its binary + libnvidia-ml | |
| # + the /dev/nvidia* device nodes to enumerate GPUs (no CUDA runtime, no toolkit). | |
| # When they exist on the host we generate a gitignored compose override that | |
| # read-only-mounts them into the api container. This is detection-only; inference | |
| # still runs on host Ollama. compose.gpu.yml is regenerated each start. | |
| GPU_OVERRIDE="compose.gpu.yml" | |
| gpu_args=() | |
| rm -f "$GPU_OVERRIDE" | |
| smi_path="$(command -v nvidia-smi 2>/dev/null || true)" | |
| if [ -n "$smi_path" ] && [ -e /dev/nvidiactl ]; then | |
| libml="$(ldconfig -p 2>/dev/null | grep -m1 'libnvidia-ml.so.1' | awk '{print $NF}')" | |
| libml="${libml:-/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1}" | |
| if [ -e "$libml" ]; then | |
| { | |
| echo "# Auto-generated by scripts/stack_up.sh β host NVIDIA GPU visibility for" | |
| echo "# ModelFit hardware detection (nvidia-smi only). Do not edit; regenerated." | |
| echo "services:" | |
| echo " api:" | |
| echo " devices:" | |
| for d in /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools /dev/nvidia0 /dev/nvidia1 /dev/nvidia2 /dev/nvidia3 /dev/nvidia4 /dev/nvidia5 /dev/nvidia6 /dev/nvidia7; do | |
| [ -e "$d" ] && echo " - \"$d\"" | |
| done | |
| echo " volumes:" | |
| echo " - \"$smi_path:/usr/bin/nvidia-smi:ro\"" | |
| echo " - \"$libml:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1:ro\"" | |
| } > "$GPU_OVERRIDE" | |
| gpu_args=(-f "$GPU_OVERRIDE") | |
| echo "β NVIDIA GPU(s) detected β exposing nvidia-smi to the API for accurate hardware detection." | |
| fi | |
| fi | |
| echo "β starting stackβ¦" | |
| $COMPOSE -f "$CF" "${gpu_args[@]}" up -d | |
| # The compose `up` above creates the project network at cniVersion 1.0.0, which | |
| # the older `firewall` CNI plugin rejects (noisy validation warnings on every | |
| # later podman command). Patch it down to 0.4.0 + dnsname now that it exists so | |
| # subsequent `podman ps/logs/exec` and the next `up` run clean. | |
| shopt -s nullglob | |
| for f in "${CNI_DIR}"/*_default.conflist "${CNI_DIR}"/auralynq*.conflist; do | |
| patch_conflist "$f" | |
| done | |
| shopt -u nullglob | |
| echo "β stack up (services resolve peers by container_name via dnsname):" | |
| echo " HTTPS : https://<SERVER_IP>:${https_port} (public β Caddy TLS proxy)" | |
| echo " web/api : internal (${bind_internal} loopback), fronted by Caddy" | |
| echo " Qdrant : internal (${bind_internal} loopback)" | |
| echo " Phoenix : internal (${bind_internal} loopback)" | |