Skip to content

Repository files navigation

ScarLiXoS v19.0.5 — Sovereign AI Cloud

SCARLIX OS v19.0.5

Suverénny domáci OS pre AI cloud, coding, gaming a rodinnú zábavu.
Architektúra Secure Host-Bridge · Token autentifikácia · State machine · Privilege boundary

Version License Base CI


Working AI Path: model-aware, fail-hard, healthcheck + fallback. Verified: SGLang (GPU0, --disable-flashinfer) + vLLM (GPU1, TP=1, experimental) + BeeLlama (CPU) + Ollama (CPU tertiary fallback). LiteLLM Gateway (v19.0.0+): unified OpenAI-compatible API on :4001. Uses a simplified 3-tier fallback (SGLang → Ollama → BeeLlama) for external clients — vLLM excluded because it's experimental (.experimental only). scarlix-mode's direct AI path keeps the full 4-tier including vLLM.

Version: v19.0.5 | Base: EndeavourOS (Arch) | License: MIT

🚀 Install (NO ISO)

Primary (safe — review first) ⭐

git clone https://github.com/MoZoHuJa/OS.git ~/scarlix-os
cd ~/scarlix-os
git checkout v19.0.5   # ALWAYS checkout specific tag (main may be ahead)
nano install.sh         # review
bash install.sh

Post-install (3 steps):

# 1. Reboot (activate NVIDIA driver + docker group takes effect on re-login)
sudo reboot

# 2. Download models (50-150GB, takes hours — disk-space pre-checked)
download-models.sh

# 3. Start AI inference
scarlix-mode ai

# 4. (optional) Open dashboard — token printed by install.sh
#    http://127.0.0.1:8090/  (localhost only — use Tailscale/SSH tunnel for LAN)

🆕 What's New in v19.0.5 (vs v19.0.4)

Housekeeping — header version drift cleanup. 3 fixes.

Final audit of v19.0.4 found 2 P2 hygiene items: stale version headers in scarlihq/Dockerfile (said v18.5.2) and ai/sglang/docker-compose.yml (said v18.8.3) — the runtime content was already correct, only the top comment had drifted. v19.0.5 bumps both headers to current and aligns all version refs.

v19.0.5 fixes (3)

# Severity Fix Was Now
1 P2 scarlihq/Dockerfile header # ScarliHQ v18.5.2 — Multi-stage Dockerfile (stale by 10 minor releases — ARG SCARLIX_VERSION tracked correctly, but the file's top comment had drifted) # ScarliHQ v19.0.5 — Multi-stage Dockerfile (historical architecture comments retained below)
2 P2 ai/sglang/docker-compose.yml header # SCARLIX OS v18.8.3 — SGLang (Tier-1, GPU 0) (stale by 6 minor releases — image tag was already v0.4.9.post6-cu128-b200 from v19.0.3) # SCARLIX OS v19.0.5 — SGLang Tier-1 GPU inference (Blackwell sm_120, Qwen3) (historical P-fix comments retained for audit trail)
3 P2 VERSION + install.sh + Dockerfile ARG + AGENTS.md → v19.0.5 v19.0.4 v19.0.5

No runtime change

No compose image tags, no script logic, no config values were modified. This release is purely header/comment version alignment. All v19.0.4 runtime fixes (SGLang, Whisper, Buzz, generate-env.sh, multilib, go.mod, dynamic banners) remain untouched and verified.

Pre-release gate (unchanged from v19.0.4)

The remaining gate is NOT another code review — it is a real hardware lifecycle test on the target RTX 5060 Ti + RTX 4060 Ti box:

EndeavourOS → install.sh → NVIDIA → Docker GPU → docker pull SGLang
→ SGLang startup → Qwen3-14B-AWQ load → scarlix-mode ai → healthcheck
→ inference request → fallback chain → reboot → doctor

🆕 What's New in v19.0.4 (vs v19.0.3)

Final docs cleanup — release-candidate for hardware lifecycle test. 3 fixes.

Independent audit of v19.0.3 confirmed all P0/P1 runtime fixes are in place (SGLang, Whisper, Buzz, generate-env.sh, multilib, go.mod, Dockerfile, dynamic banners). Only 2 documentation legacy items remained: a stale v12 header on docs/NETWORK.md and deprecated image refs in the body of docs/COMPLETE_INSTALL_GUIDE.md. v19.0.4 closes both.

v19.0.4 fixes (3)

# Severity Fix Was Now
1 P2 docs/NETWORK.md header v12 → v19 Header said "SCARLIX OS v12 — Network Topology" (content was still valid — ports, VPN, domains all match v19 runtime) Header → v19 + note that topology/ports are unchanged (ScarliHQ :8090, SGLang :30000, Ollama :11435, LiteLLM :4001)
2 P2 docs/COMPLETE_INSTALL_GUIDE.md moved to docs/archive/ 2364-line deprecated v12 Ubuntu-ISO guide still in active docs/ root — body contained stale image refs (ghcr.io/sgl-project/sglang:v0.5.5-cu124, fedirz/faster-whisper-server:0.10.0, ghcr.io/block/buzz-relay:latest) that could confuse a grep audit Moved to docs/archive/COMPLETE_INSTALL_GUIDE_v12_UBUNTU_DEPRECATED.md — cleanly separated from active docs, docs/archive/ already exists for historical manifests (V15, V16.1)
3 P2 VERSION + install.sh + Dockerfile + AGENTS.md → v19.0.4 v19.0.3 v19.0.4

Audit confirmation (v19.0.3 fixes verified by independent reviewer)

Area Status
SGLang image exists (lmsysorg/sglang:v0.4.9.post6-cu128-b200) 🟢 verified (manifest sha256, linux/amd64, CUDA 12.8.1, Blackwell build)
Whisper image exists (sha-307e23f-cuda) 🟢 verified (amd64 + arm64)
Buzz image (ghcr.io/block/buzz:latest) 🟢 verified
multilib ordering, Telegram env, cp -a dotfiles 🟢 confirmed
Go module path (github.com/MoZoHuJa/OS/scarlihq) 🟢 confirmed
Dockerfile SCARLIX_VERSION=19.0.3 🟢 confirmed (now 19.0.4)
HARDWARE.md + TROUBLESHOOTING.md v12 → v19 🟢 confirmed
Bash syntax (all scripts) 🟢 PASS
Compose YAML (25 files) 🟢 25/25 PASS
ollama-main runtime refs 🟢 0 in active config (historical comment refs in archive only)

Remaining pre-release checks (require GPU host — NOT code fixes)

  • docker pull lmsysorg/sglang:v0.4.9.post6-cu128-b200 on RTX 5060 Ti + RTX 4060 Ti — confirm SGLang starts + --disable-flashinfer accepted
  • Full lifecycle: EndeavourOS → install.sh → NVIDIA → Docker GPU → SGLang startup → Qwen3-14B-AWQ load → scarlix-mode ai → healthcheck → inference → fallback chain → reboot → doctor

Verdict: v19.0.4 is a release-candidate. No more code fixes needed — the remaining gate is a real hardware lifecycle test on the target RTX 5060 Ti + 4060 Ti box.


🆕 What's New in v19.0.3 (vs v19.0.2)

Release-gate fixes: SGLang P0 blocker + image registry audit + version-drift cleanup. 9 fixes.

A stricter independent audit of v19.0.2 found that the SGLang Tier-1 image was a real P0 blocker — the tag ghcr.io/sgl-project/sglang:v0.4.6.post1-cu128 does NOT exist on any registry (ghcr returns 403/DENIED, Docker Hub lmsysorg/sglang returns 404). The v19.0.1 comment already admitted it "could NOT be verified" but it stayed in release. v19.0.3 fixes this + two more dead images + all version-drift.

v19.0.3 fixes (9)

# Severity Fix Was Now
1 P0 SGLang Tier-1 image (BLOCKER — scarlix-mode ai pulls this) ghcr.io/sgl-project/sglang:v0.4.6.post1-cu128 (ghcr 403/DENIED; tag 404 on Docker Hub lmsysorg/sglang — cu128 variant only exists from v0.4.8+ as -b200/-gb200) lmsysorg/sglang:v0.4.9.post6-cu128-b200 (verified HTTP 200 + manifest via registry API; latest stable 0.4.9 cu128-b200 = consumer Blackwell sm_120; supports Qwen3)
2 P0 Whisper image tag fedirz/faster-whisper-server:0.10.0 (tag does NOT exist — 0.10.x tags = [] on Docker Hub) fedirz/faster-whisper-server:sha-307e23f-cuda (verified exists; note: upstream moved to "Speaches" — migrate in future)
3 P1 Buzz image registry ghcr.io/block/buzz-relay:latest (ghcr DENIED — repo doesn't exist) ghcr.io/block/buzz:latest (verified: latest, main, sha-* tags exist; profile-gated [buzz])
4 P2 All script banners now dynamic (version-drift fix) scarlix-doctor / scarlix-mode status+VRAM / download-models / model-manager banners hardcoded v19.0.0 (drifted every release) Read /etc/scarlix/VERSION dynamically → v${SCARLIX_VER} (never drifts again)
5 P2 Dockerfile SCARLIX_VERSION ARG SCARLIX_VERSION=19.0.0 (manual docker build without --build-arg produced binary reporting v19.0.0) ARG SCARLIX_VERSION=19.0.3 (matches VERSION file)
6 P2 Go module path module github.com/MoZoHuJa/scarlix-os-v12/scarlihq (stale scarlix-os-v12 path; actual repo is MoZoHuJa/OS) module github.com/MoZoHuJa/OS/scarlihq (all .go imports updated)
7 P2 docs/HARDWARE.md + docs/TROUBLESHOOTING.md v12 headers Both said "v12"; TROUBLESHOOTING had Ubuntu apt purge/apt install commands (wrong OS — EndeavourOS uses pacman) Headers → v19; TROUBLESHOOTING NVIDIA driver reinstall rewritten with pacman -S nvidia-open + mkinitcpio -P
8 P2 smg/config.yaml comment grep false-positive Historical comment contained literal ollama-main → grep ollama-main false-positived on the config (review P1-1/P2-5) Comment reworded to not contain the dead hostname literally; plain grep now returns 0 matches
9 P2 VERSION + install.sh + AGENTS.md → v19.0.3 v19.0.2 v19.0.3 (AGENTS.md SGLang version also updated to v0.4.9.post6-cu128-b200)

Image-registry verification (v19.0.3)

lmsysorg/sglang:v0.4.9.post6-cu128-b200     → HTTP 200 + manifest (Docker Hub)
fedirz/faster-whisper-server:sha-307e23f-cuda → exists (Docker Hub tags list)
ghcr.io/block/buzz:latest                   → tags: latest, main, sha-* (ghcr API)

Remaining pre-release checks (NOT fixed in v19.0.3 — require GPU host)

  • docker pull lmsysorg/sglang:v0.4.9.post6-cu128-b200 on a Blackwell GPU host — confirm SGLang starts + --disable-flashinfer accepted
  • docker pull fedirz/faster-whisper-server:sha-307e23f-cuda — confirm Whisper starts
  • Full clean-install lifecycle test (EndeavourOS → install.sh → scarlix-mode ai → all fallbacks → reboot → doctor)

🆕 What's New in v19.0.2 (vs v19.0.1)

Verified fixes from an independent code review + stale-docs cleanup. 6 fixes.

An independent review of the v19.0.0 ZIP surfaced ~23 claimed bugs. On verification against the live repo (post-v19.0.1), only 3 were real (~74% false positives — the review was run on a stale snapshot and didn't check runtime facts like HuggingFace/Docker Hub APIs). v19.0.2 fixes the 3 real bugs + rewrites stale v12-era docs.

v19.0.2 fixes (6)

# Severity Fix Was Now
1 P1 scarlix-wizard Telegram token silently lost via sudo env_reset sudo /etc/systemd/system/generate-env.sh — sudo's default env_reset strips TELEGRAM_BOT_TOKEN / TELEGRAM_ZMOR_CHAT_ID (not in env_keep) → generate-env.sh saw them EMPTY → token the user typed into the wizard was silently discarded sudo --preserve-env=TELEGRAM_BOT_TOKEN,TELEGRAM_ZMOR_CHAT_ID (scoped, not bare -E which would leak the whole env)
2 P0 smg/config.yaml dead upstream hostnames Referenced ollama-main (no such service — only ollama-agent exists) and llamacpp (renamed to beellama in v18.5) → 2 of 4 fallback targets never resolved on scarlix-net Removed dead ollama-main; llamacpp-cpu → beellama-cpu (http://beellama:8080); fallback_chain now [sglang-main, ollama-agent, beellama-cpu] (matches the actual service names, consistent with the v18.8 LiteLLM config fix)
3 P0 docs/ARCHITECTURE.md rewritten (was v12/Ubuntu) Said "v12 / Ubuntu 24.04 LTS / Ollama GPU 1 qwen3.6:14b / llama.cpp" — completely stale from the abandoned Ubuntu-ISO era Rewritten for v19 EndeavourOS/Arch: real inference tiers (SGLang AWQ + vLLM TP=1 + BeeLlama + Ollama CPU), Secure Host-Bridge architecture, 4-tier + 3-tier failover diagrams, GPU arbitration table matching actual scarlix-mode modes
4 P2 docs/COMPLETE_INSTALL_GUIDE.md deprecation banner 2364-line guide describing the obsolete v12 Ubuntu-ISO workflow (Rufus, Ubuntu 24.04 Server ISO, apt steps) — misleading for v19 users Prominent DEPRECATED banner at top pointing to README.md "🚀 Install (NO ISO)"; header metadata marked zastarané. Full body retained as historical reference (rewriting 2364 lines of dead Ubuntu steps has no value)
5 P2 AGENTS.md inference-stack table stale image versions Table said SGLang v0.4.4-cu128, vLLM v0.8.0, Ollama 0.5.4, "llama.cpp official" Updated to SGLang v0.4.6.post1-cu128 (--disable-flashinfer), vLLM v0.8.5, BeeLlama (the llama.cpp tier), real service names (sglang/vllm/beellama/ollama-agent); added note that ollama-main/llamacpp were removed as dead references
6 P2 VERSION + install.sh header → v19.0.2 v19.0.1 v19.0.2

Review-claims verified FALSE (not bugs — left unchanged)

Claim Verdict Evidence
/etc/scarlix/VERSION never created FALSE install.sh:679 creates it (v19.0.0 P1-7)
host-bridge timer flock -n silent skip FALSE Intentional oneshot+flock design (v19.0.0 P1-13 documented)
HF model IDs Qwen/Qwen3-14B-AWQ / -GGUF don't exist FALSE HuggingFace API confirms both repos + files exist
download-models.sh Ollama pull race FALSE download-models.sh:298-312 waits for Ollama API (max 60s)
Video Dockerfile Wan2GP clone needs auth FALSE Public repo, commit pinned (f3f204e50f6e)
bridge-reader UID 65532 may not exist FALSE scarlihq/Dockerfile:46,57 creates nonroot UID 65532 (intentional security design)
scarlix-doctor checks ollama-main FALSE scarlix-doctor:336 checks ollama-agent (correct)
jellyfin 12.1 doesn't exist FALSE Docker Hub API confirms tag 12.1 exists

Verification (v19.0.2)

bash -n  install.sh scarlix-wizard scarlix-mode generate-env.sh ...  → PASS
python3 yaml.safe_load ai/smg/config.yaml                            → OK
git grep -n "ollama-main\|http://llamacpp:" ai/ docs/ AGENTS.md       → 0 matches (dead refs removed)

🆕 What's New in v19.0.1 (vs v19.0.0)

Sandbox-verified bug fixes + image hygiene. 11 fixes from two independent sandbox reviews of v19.0.0.

v19.0.0 passed bash -n and YAML lint, but live sandbox runs surfaced three real generate-env.sh data-corruption bugs, a multilib DB-sync ordering issue, stale image tags, and documentation drift. v19.0.1 closes all of them with the fixes verified in-sandbox.

v19.0.1 fixes (11)

# Fix Was (v19.0.0) Now (v19.0.1)
P1 generate-env.sh duplicate detection ignores comments cut -d= -f1 counted comments/blank lines → two identical # note lines → ERROR exit rc=1 → scarlix-doctor (calls this via fix_regenerate_env) infinite loop grep -E '^[A-Z_][A-Z0-9_]*=' before cut; duplicates auto-resolved keeping LAST occurrence (no hard-abort)
P1 Telegram token update preserves = in value awk FS=OFS="=" $2=v left field-3+ in place → token abc=def= became NEW123=def= index($0,k"=")==1 line-match + ENVIRON[] for value (also safe vs backslashes)
P1 Orphan-line / truncated-secret detection Old base64 multiline writes left orphan continuation lines; the orphan was dropped but the half KEY= stayed as a silently-valid (wrong) secret WARN on orphan-after-assignment; JWT_SECRET auto-regenerated (rotate-safe); STORAGE_ENCRYPTION_KEY flagged for manual review (rotating breaks existing data)
P1 [multilib] enabled BEFORE first pacman -Syu Multilib enabled in Phase 1 AFTER the first -Syu → multilib DB never synced → lib32-* installs could fail on clean EndeavourOS (comment falsely claimed "db synced by initial -Syu") New ensure_multilib() called in pre-flight before any -Syu; single full -Syu (Phase 1 skips if pre-flight already synced)
P1 openlit image: wrong registry openlit/openlit:latest (Docker Hub) — no official repo exists there → docker pull would fail on first start ghcr.io/openlit/openlit:1.5.0 (verified via ghcr API; latest stable)
P1 LiteLLM image bumped + false comment fixed main-v1.16.19 with a comment falsely claiming "latest stable" (1.17–1.23 already existed) main-v1.21.7 (conservative bump, config schema unchanged); pre-release healthcheck note added
P1 SGLang image pre-release verification note v0.4.6.post1-cu128 tag could not be verified (ghcr anonymous token returns 403); reviewer unsure about cu128 variant Tag left unchanged (changing CUDA suffix blindly risks GPU breakage); prominent pre-release docker pull + --disable-flashinfer --help check added
P2 scarlix-wizard cp -r glob copy cp -r /opt/scarlix-src/* /opt/scarlix/ || true — (1) * skips dotfiles, (2) || true masks copy failures cp -a /opt/scarlix-src/. /opt/scarlix/ (copies dotfiles, preserves attrs) + fail-closed
P2 scarlix-mode .env header version Hardcoded v18.9.0 in the generated .env header — drifted from runtime version Reads /etc/scarlix/VERSION (falls back to unknown)
P2 CI shellcheck severity -S error → -S warning Only shellcheck errors blocked CI; warnings (unused vars, redirect issues) silently passed -S warning + all 6 resulting warnings fixed (4 unused loop vars, 1 test -x SC2065 real bug in model-manager, 1 false-positive SC2024 documented)
P2 install.sh header + VERSION Header comment said v18.9.8, VERSION="19.0.0" Header v19.0.1, VERSION="19.0.1" (VERSION file also bumped)

Sandbox verification (v19.0.1)

bash -n  install.sh scarlix-wizard scarlix-mode scarlix-doctor generate-env.sh ...  → PASS
shellcheck -S warning <all CI scripts>                                              → CLEAN
python3 yaml.safe_load <all docker-compose.yml>                                    → 25/25 OK
generate-env.sh dup test (# note ×2 + dupe SCARLIHQ_TOKEN) → keeps last, no abort  → PASS
generate-env.sh token test (TELEGRAM_BOT_TOKEN=abc=def= → NEW123:XY=Z=)            → PASS
generate-env.sh orphan test (JWT_SECRET half + STORAGE_ENCRYPTION_KEY half)         → WARN + JWT regen

🆕 What's New in v19.0.0 (vs v18.9.8)

Config validation, .env hardening, and README refresh. 6 fixes from the v19.0.0 review (P1 + P2).

This release tightens the dynamic-config pipeline (models.yaml → generate-litellm-config.sh → LiteLLM config.yaml) and the secrets file (/etc/scarlix/.env) that the backup hook sources under set -euo pipefail. Several latent issues inherited from v18.8.x sed rewrites are closed with explicit validation, atomic writes, and a doctor-side diagnostic.

v19.0.0 fixes (6)

# Fix Was (v18.8.x) Now (v19.0.0)
P1 model-manager: restart after update + staging dir Updated model moved into place but the running engine kept serving the old one; no staging dir → in-place overwrite risk during download Restart engine after a successful update; stage the new model in a temp dir, then atomic mv into final path
P1 LiteLLM config change detection → restart generate-litellm-config.sh wrote config.yaml but LiteLLM kept the old routing in memory → routing stayed stale until manual restart Detect config change (checksum/mtime) and restart the LiteLLM container so the new routing takes effect
P1 generate-litellm-config charset validation Model IDs from models.yaml were written into YAML unvalidated — a model_path containing :, #, [, ] could produce an invalid config.yaml validate_model_id() enforces ^[A-Za-z0-9._/@:-]+$ for SGLang / BeeLlama / Ollama IDs; exit 1 on violation
P1 .env orphan line check in scarlix-doctor Old .env files from base64-64 multiline writes left orphan lines; the backup hook sources .env under set -euo pipefail → silent abort scarlix-doctor scans for non-assignment lines and offers fix_regenerate_env (calls generate-env.sh or strips bad lines)
P2 atomic .env write (mktemp + mv) cat > .env left a truncated file if interrupted mid-write; next source .env failed under set -e Write to mktemp then mv -f into place — readers always see a complete file
P2 model-manager YAML validation yq errors on individual keys gave confusing "model_path missing" errors when the real problem was invalid YAML syntax Explicit yq -e '.' validation up front with a clear error message

🆕 What's New in v18.4 (vs v18.3)

Critical security + upgrade reliability. 12 fixes from expert review.

v18.3 had a P0 Local Privilege Escalation: chown -R REAL_USER /etc/scarlix /opt/scarlix gave user ownership of .env files that root services later sourced → user writes $(touch /root/PWNED) into .env → root executes it via source = full root compromise.

P0 — local privilege escalation (5)

# Fix v18.3 Problem v18.4 Solution
P0 No chown /etc/scarlix or /opt/scarlix to user chown -R REAL_USER /etc/scarlix /opt/scarlix → user owns .env files that root sources Only chown /var/lib/scarlix + /mnt (user data). /etc/scarlix + /opt/scarlix stay root:root 755
P0 source → load_env_safe() in model-manager set -a; source /etc/scarlix/.env; set +a — root executes user-controlled shell load_env_safe(): parse KEY=VALUE via while IFS='=' read + export — NO shell evaluation
P0 source → grep in install.sh Phase 5 set -a; source /etc/scarlix/.env for SCARLIHQ_TOKEN — LPE on re-run grep '^SCARLIHQ_TOKEN=' | cut -d= -f2 — safe single-key extraction
P0 /opt/scarlix/.env root:root 600 scarlix-mode chown "$real_user" "$ENV_FILE" → user-owned .env chown root:root "$ENV_FILE" + chmod 600 — root-only write
P0 SMG_MASTER_KEY not logged echo "SMG_MASTER_KEY: $SMG_KEY" → install.log (permanent secret leak) echo "SMG_MASTER_KEY: generated (in /etc/scarlix/.env)" — no value in log

P1 — upgrade reliability (7)

# Fix v18.3 Problem v18.5 Solution
P1 Checkpoint tracks VERSION is_checkpoint_valid() didn't check version → upgrade skipped phases with old scripts Added scarlix_version=${VERSION} to checkpoint + comparison
P1 Checkpoint tracks git commit hash Same version different commit → checkpoint valid → new files not installed Added repo_hash=$(git rev-parse --short HEAD) to checkpoint + comparison
P1 rsync --delete for /opt/scarlix cp -r left stale files from old versions (deleted compose files persisted) rsync -a --delete (with rm -rf+cp fallback if rsync unavailable)
P1 model-manager flock on /models No lock → race between model-manager download + scarlix-mode start Shared flock (flock -s) on /var/lock/scarlix-models.lock
P1 download-models flock on /models Same race Same shared flock
P1 scarlix-mode exclusive flock on /models No lock → start SGLang during model download → corrupt model Exclusive flock (flock -x) for write ops, shared (flock -s) for read ops
P1 host-status.json mktemp Fixed .tmp name — predictable path (TOCTOU risk) mktemp "${STATUS_FILE}.XXXXXX" — random suffix

Security regression test

# 1. As user, modify /opt/scarlix/.env (should be root:root 600 → permission denied)
echo '$(touch /root/PWNED)' >> /opt/scarlix/.env  # → Permission denied

# 2. Run model-manager as root
systemctl start model-manager.service

# 3. Verify /root/PWNED does NOT exist (LPE closed)
ls /root/PWNED  # → No such file

🆕 What's New in v18.3 (vs v18.2)

Critical correctness fixes. 11 fixes from 4 expert reviews.

v18.2 had a P0 octal permission bug: stat -c %a returns "600" (string), but $((perms & 022)) interpreted 600 as decimal 600 → 600 & 022 = 16 (not 0) → host bridge rejected legit 0600 files created by Go os.CreateTemp() → mode switch from dashboard was completely broken.

P0 — critical (3)

# Fix v18.2 Problem v18.5 Solution
P0 Octal permission bug $((perms & 022)) interpreted "600" as decimal → 600 & 022 = 16 → REJECTED all 0600 files from Go CreateTemp → mode switch broken Explicit case "$mode" in 600|640|700) whitelist — no arithmetic, no octal/decimal confusion
P0 local outside function local pending_mode in install.sh migration block (not in a function) — bash error on set -e Removed local — plain variable assignment
P0 TOCTOU mitigation validate → then later read (window for attacker to swap file) Read content immediately after validate (minimizes window; full atomicity needs Go helper)

P1 — correctness (5)

# Fix v18.2 Problem v18.5 Solution
P1 scarlix-mode ai returns failure start_verified_ai always implicit exit 0 → host-bridge saw "applied" even with no engine → dashboard lied Returns 1 if no engine (sglang+vllm+beellama+ollama all 0). ai/turbo write "failed" to state + exit 1
P1 CI go test mask removed go test ./... || echo "(no tests yet — OK)" — hid test failures if go list ./... | grep -q .; then go test ./...; else echo "(no tests)" — real failures fail CI
P1 host bridge fail-closed chown root:root ... || true + chmod 700 ... || true — continued on failure → security boundary broken if ! chown/chmod; then exit 1 — aborts if can't enforce root:root 700 on bridge-state/
P1 scarlix-doctor token auth http_ok /api/health without token → 401 → "not responding" false negative http_ok_with_token with Bearer from /etc/scarlix/.env → correct 200 check + version match
P1 status.Read() returns error _ = json.Unmarshal(data, &s) — silent ignore → API returned zero values on corrupt JSON Read() (HostStatus, error) + ReadOrStale() wrapper + Stale field in JSON

P1 — locale + quality (3)

# Fix v18.2 Problem v18.5 Solution
P1 df locale-independent df -m /models without --output — breaks on LANG=sk_SK (localized headers) df -m --output=size,avail /models — deterministic columns
P1 model-manager.service header Description=SCARLIX OS v17.5 Model Manager — stale version Updated to v18.5
P1 host-bridge header comment Said "v18.0.0" — stale Updated to v18.5

Verification

  • bash -n on 8 scripts: OK
  • YAML validation on 26 files: 0 errors
  • Go: var Version (ldflags works), ReadOrStale() replaces old Read()
  • Grep confirms: 0 local outside functions, 0 || echo masking go test, 0 || true on mkdir/chown/chmod

🆕 What's New in v18.2 (vs v18.1)

Security + UX fixes. 13 fixes from 2 expert reviews.

v18.1 had a P0 real_user bug: when systemd timer ran scarlix-mode as root, $SUDO_USER was empty + $USER was "root" → chown root /opt/scarlix → user CLI broke. Also const Version in Go couldn't be overridden by ldflags.

P0 — security (1)

# Fix v18.1 Problem v18.5 Solution
P0 real_user detection under systemd ${SUDO_USER:-${USER:-scarlix}} → when systemd runs as root, $USER=root → chown root /opt/scarlix → user scarlix-mode ai fails "Permission denied" Detect from /opt/scarlix ownership (set by install.sh) or UID 1000 fallback. Works under systemd, sudo, and direct user CLI.

P1 — correctness + security (6)

# Fix v18.1 Problem v18.5 Solution
P1 const Version → var Version -ldflags "-X main.Version" doesn't work on const → version always "18.1" in binary even with --build-arg SCARLIX_VERSION=18.2 Changed to var Version in main.go + api/rest.go. main.go passes Version to NewHandler. ldflags now overrides correctly.
P1 scarlix-mode systemctl sudo fallback v18.1 removed sudo → user scarlix-mode game fails silently on systemctl start sunshine if [ "$(id -u)" -eq 0 ]; then systemctl...; else sudo systemctl...; fi — works for both root (host-bridge) and user (CLI)
P1 token only on TTY info "Dashboard login token: $TOKEN" → tee to install.log (644) → token readable by all local users if [ -t 1 ]; then echo to /dev/tty; else info "token in /etc/scarlix/.env" + chmod 600 install.log
P1 FIFO/pipe/socket rejection validate_input_file checked symlink + owner, but not FIFO → mkfifo desired-mode would block bridge timer (DoS) Explicit [ -p ], [ -S ], [ -b ], [ -c ] checks (in addition to existing -f which already rejects non-regular)
P1 models.yaml schema validation yq '.sglang.hf_repo' silently returned empty on typo → script continued with empty paths validate_models_yaml() checks 5 required keys before download. Fail-fast with clear error.
P1 go.sum handling go.sum was 0 bytes → non-deterministic build, offline install impossible Removed go.sum from repo (generated by Dockerfile go mod download). CI go mod verify checks integrity.

P2 — UX + consistency (6)

# Fix v18.1 Problem v18.5 Solution
P2 CI smoke test chown 65532 CI created bridge-input/ as root → nonroot container couldn't write desired-mode → mode POST 202 test failed sudo chown -R 65532:65532 /tmp/scarlix-test/bridge-input before test
P2 migration applies pending desired-mode rm -rf /var/lib/scarlix/bridge deleted pending desired-mode → lost mode switch request Read + apply pending mode via scarlix-mode "$pending" BEFORE rm -rf
P2 whiptail ESC/Cancel handling IP=$(whiptail ...) without `
P2 bridge restores retry_count + last_error last-transition file stored retry_count + error, but bridge didn't restore them for status Read retry_count + error from last-transition when transition="none"
P2 creative/tv ensure .env exists creative mode called $DC without .env → compose fails if no prior scarlix-mode ai `[ -f "$ENV_FILE" ]
P2 .env.template updated Header said "v15" + no SCARLIHQ_TOKEN Updated to v18.5 + added SCARLIHQ_TOKEN + note that install.sh auto-generates

Review false-positives (verified already-correct in v18.1)

  • Review 2 P0-1 (|| true masking) — already fixed in v18.1 (reviewer checked v18.0.0)
  • Review 2 P1-5 (host-status.json mount) — already mounted via /var/lib/scarlix:ro
  • Review 2 P2-6 (guard dead code) — Guard IS used (in main.go, rest.go, mcp/server.go)
  • Review 1 #5 (host-bridge input validation) — validate_input_file already exists since v18.0.0

Decisions made (where I couldn't decide automatically)

  • go.sum: Committed empty (0 bytes) vs generated at build time → chose generated (no local Go to create deterministic hashes; Dockerfile go mod download generates it; CI go mod verify validates)
  • scarlix-mode sudo: Removed entirely vs conditional → chose conditional (if root: systemctl; else: sudo systemctl) — works for both host-bridge (root) and user CLI (sudo)

🆕 What's New in v18.1 (vs v18.0.0)

Correctness fixes — retry actually works now. 6 fixes from self-audit.

v18.0.0 had a P0 logic bug: mode_output=$(... 2>&1) || true masked the exit code to 0, so mode_rc=$? was always 0 → transition always "applied" even when scarlix-mode failed → retry mechanism never triggered.

P0 — correctness (1)

# Fix v18.0.0 Problem v18.5 Solution
P0 mode_rc exit code capture `

P1 — quality (5)

# Fix v18.0.0 Problem v18.5 Solution
P1 install.sh migration rm -rf rmdir /var/lib/scarlix/bridge fails if dir has hidden files (.retry) → migration stuck rm -rf (safe — only the bridge/ dir being migrated)
P1 Dockerfile header comments Header said "v17.9.9" + "bridge/desired-mode" (stale v18.0.0 didn't update) Updated to v18.5 + "bridge-input/desired-mode"
P1 scarlix_mode.Set() dir check If bridge-input/ dir doesn't exist, os.CreateTemp fails with cryptic error Explicit os.Stat(dir) check → clear 503 error message
P1 Removed unused Transition() method scarlix_mode.Transition() defined but never called → status import only for it Removed method + import (cleaner, avoids go vet warning)
P1 host-bridge last_error sanitization last_error from scarlix-mode output could contain newlines → broke env var passing to python3 tr '\n' ' ' + tr -d '\r' before storing in last_error

Verification

  • bash -n on all 8 scripts: OK
  • YAML validation on 26 compose + models.yaml: 0 errors
  • Go imports verified: no unused imports (removed status from scarlix_mode)
  • Grep verified: 0 occurrences of || true masking exit codes in host-bridge

🆕 What's New in v18.0.0 (vs v17.9.9)

Secure Host-Bridge — privilege boundary hardened. 10 fixes from 2 reviews.

v17.9.9 had a P0 symlink vulnerability: ScarliHQ owned bridge/ dir, root wrote .retry to it. If ScarliHQ created a symlink bridge/.retry -> /etc/shadow, root would write to /etc/shadow. v18.0.0 separates writable input dir from root-owned state dir.

P0 — privilege boundary (2)

# Fix v17.9.9 Problem v18.0.0 Solution
P0 Separate bridge-input/ from bridge-state/ Single bridge/ dir owned by 65532 + root wrote .retry there = symlink attack bridge-input/ (65532:65532, 700) — ScarliHQ writes ONLY desired-mode. bridge-state/ (root:root, 700) — root writes retry/last-transition. ScarliHQ cannot create symlinks in bridge-state/.
P0 Single scarlix-mode execution On failure, host bridge ran scarlix-mode AGAIN to capture error output → double mode transition (e.g. 2× compose up, 2× stop) Capture output once: output=$("$MODE_BIN" "$mode" 2>&1) then check $?

P1 — security + state machine (8)

# Fix v17.9.9 Problem v18.0.0 Solution
P1 Concurrent Mode.Set() race Two concurrent POST /api/mode used same .tmp file → race (A writes, B writes, A renames, B renames → "no such file") os.CreateTemp(dir, ".desired-mode-*") — unique temp per request
P1 GET /api/mode returns transition state Only returned {"mode":"ai"} — dashboard couldn't show retry/failed Returns full: mode, requested_mode, transition_state, retry_count, last_error, last_timestamp
P1 Persistent last-transition After max retries, state cleared on next timer tick → diagnostic info lost last-transition file in bridge-state/ (root 700) — survives timer cycles
P1 scarlix-mode .env permission /opt/scarlix/.env owned root:root → user scarlix-mode ai fails "Permission denied" If root: chown /opt/scarlix to REAL_USER before write. If user: warn with fix command.
P1 crypto/subtle.ConstantTimeCompare Custom secureCompare() with early len(a) != len(b) return — not truly constant-time subtle.ConstantTimeCompare() from crypto/subtle (standard library, audited)
P1 Host bridge input validation Read desired-mode without checks → root could follow symlink created by nonroot validate_input_file(): rejects symlinks, checks owner=65532, no world-writable, max 100 bytes
P1 No hardcoded version in host bridge "version": "17.9.9" hardcoded in JSON → duplicate source of truth Reads scarlix_version from VERSION file (single source)
P1 JSON via env vars + single python3 Nested string interpolation python3 -c "...\"$last_error\"..." — broke on quotes/backslashes in error messages Pass all values as env vars, single python3 -c reads os.environ — no interpolation

P2 — CI (1)

# Fix v17.9.9 Problem v18.0.0 Solution
P2 CI uses VERSION as build-arg + integration test CI used Dockerfile default (hardcoded 17.9.9); smoke test only checked health endpoint CI reads VERSION file as --build-arg; smoke test now POST /api/mode → checks desired-mode file written → checks 202/400 error codes

Breaking change: bridge/ → bridge-input/ + bridge-state/

Existing v17.9.9 installs: install.sh auto-migrates (removes old bridge/ dir, creates new layout). ScarliHQ compose mount updated from bridge:rw to bridge-input:rw.

Review false-positives (verified already-correct in v17.9.9)

  • internal/api/ and internal/profiles/ packages DO exist on remote main + tag v17.9.9 (verified via git ls-tree -r origin/main). Reviewer checked stale GitHub web UI cache.
  • yq -r '.key' works with python-kislyuk (jq wrapper, -r is jq flag).

🆕 What's New in v17.9.9 (vs v17.9.8)

Host-Bridge stabilization + state machine — 13 fixes from 3 reviews.

v17.9.8 introduced the host-bridge architecture (correct direction) but had 3 P0 bugs that prevented ScarliHQ from actually working: nonroot user didn't exist in alpine, bridge/ directory was root-owned (nonroot couldn't write), and df parsing had swapped columns.

P0 — ScarliHQ actually works now (3)

# Fix v17.9.8 Problem v17.9.9 Solution
P0 Dockerfile nonroot user USER nonroot:nonroot — alpine has no such user → container crash on start RUN adduser -D -u 65532 nonroot creates the user before USER nonroot
P0 bridge/ directory permissions chmod 755 root:root → nonroot container couldn't write desired-mode → mode switch always failed chown 65532:65532 + chmod 775 — nonroot can write, root bridge can read
P0 host-bridge df parsing read -r _ _ total used — columns swapped (total=Used, free=Used-Avail) read -r _ size used avail _ — correct columns per df -m output

P1 — reliability + security (8)

# Fix v17.9.8 Problem v17.9.9 Solution
P1 HTTP error codes /api/mode returned 200 OK + {"status":"error"} on failure writeJSONError() with proper codes: 400 (invalid mode), 503 (fs error), 500 (internal)
P1 desired-mode retry on failure rm -f desired-mode always — even on failure → lost retry info Keep on failure + retry counter (max 3); only delete on success or max-retries
P1 host-bridge JSON via python3 Shell heredoc cat <<EOF — broke on container names with quotes/backslashes python3 -c json.dumps() — safe escaping for all string values
P1 State machine fields mode_applied: "ERROR: ..." (unstructured string) mode_transition: {requested, state, retry_count, last_error} — frontend renders status
P1 MCP secureCompare if token != s.authToken (timing attack risk) secureCompare() constant-time comparison (same as REST API)
P1 WS origin CIDR check strings.HasPrefix(origin, "http://10.") — crude, rejected https, no real IP validation net.ParseIP + net.IPNet.Contains with proper CIDR ranges (127.0.0.1/32, 10/8, 172.16/12, 192.168/16)
P1 scarlix-mode: removed sudo 5× sudo systemctl — bridge runs as root, sudo is unnecessary dependency Direct systemctl (bridge is already root)
P1 scarlix-mode turbo dump_vram turbo called start_verified_ai without VRAM cleanup → CUDA OOM if creative/game held VRAM dump_vram before start_verified_ai (VRAM safety gate)

P1 — host-bridge systemd hardening

NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
RestrictRealtime=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_NET_RAW CAP_DAC_OVERRIDE CAP_KILL

(ProtectSystem=strict NOT used — scarlix-mode writes to /var/lib/scarlix + needs docker socket)

P2 — hardening (2)

# Fix v17.9.8 Problem v17.9.9 Solution
P2 mem-fraction configurable SGLang/vLLM --mem-fraction 0.85 hardcoded → OOM on 8GB GPU ${SGLANG_MEM_FRACTION} + ${VLLM_GPU_UTIL} env vars from models.yaml
P2 Version via -ldflags const Version = "17.9.8" hardcoded in 3 Go files -ldflags "-X main.Version=$VERSION" in Dockerfile (single source: VERSION file)

CI: runtime smoke test

Added scarlihq-docker-build job now runs the container + tests:

  • Container starts without crash (catches nonroot/permission issues)
  • GET /api/health without token → 401 (auth works)
  • GET /api/health with token → 200 (full path works)

Review false-positives (verified already-correct in v17.9.8)

  • internal/api/ and internal/profiles/ packages DO exist (rest.go 6159B, loader.go 2619B) — reviewer 2 checked stale GitHub cache
  • yq -r '.key' works with python-kislyuk (it's a jq wrapper, -r is a jq flag) — reviewer 3 was wrong about syntax mismatch

🆕 What's New in v17.9.8 (vs v17.9.7)

ScarliHQ privilege boundary + reliability — 16 fixes from 3 reviews.

v17.9.7 introduced a ScarliHQ dashboard that mounted docker.sock + scarlix-mode + nvidia runtime into the container — functional but a privilege-escalation nightmare (any LAN client with the token could run arbitrary Docker commands). v17.9.8 replaces this with a host-bridge architecture.

🏗️ Architecture change: ScarliHQ Host-Bridge

┌─────────────────┐         ┌──────────────────────┐
│  ScarliHQ       │  reads  │ /var/lib/scarlix/    │
│  (alpine, ~20MB,│ ◀────── │   host-status.json    │ ◀── scarlix-host-bridge.timer
│   non-root,     │         │ /var/lib/scarlix/     │     (host, root, every 5s:
│   NO docker.sock│ writes  │   bridge/desired-mode │     nvidia-smi + docker ps +
│   NO nvidia,    │ ──────▶ │                       │     scarlix-mode)
│   NO scarlix-mode│        └──────────────────────┘
└─────────────────┘

ScarliHQ has zero privileged host access. It reads a JSON status file and writes a desired-mode file. The host-side scarlix-host-bridge systemd timer (runs as root, every 5s) does all privileged work: nvidia-smi, docker ps, scarlix-mode <mode>.

P0 — install/build blockers (6)

# Fix v17.9.7 Problem v17.9.8 Solution
P0 Dockerfile build order COPY . . before go mod download → fragile go.sum COPY go.mod → go mod download → COPY . . → go build (standard Go Docker pattern)
P0 go.mod cleaned 9 unused heavy deps (nostr, zerolog, cobra, viper, otel, crypto, sqlite, chi, uuid) → 400MB go mod download, slow builds Only gorilla/websocket + gopkg.in/yaml.v3 (2 deps, <10s download)
P0 ScarliHQ privilege boundary Mounted docker.sock:ro + scarlix-mode:ro + nvidia runtime → any token-holder = root on host Host-bridge architecture: ScarliHQ has NO docker.sock, NO scarlix-mode, NO nvidia. Only file read/write
P0 API authentication /api/mode?set=ai accepted ANY request (no auth) → LAN anyone could switch modes All /api/* + /ws require Authorization: Bearer $SCARLIHQ_TOKEN (generated by generate-env.sh)
P0 Mode API/CLI/UI unified Go Set() accepted only ai/game/turbo/offline; UI had stop; CLI had stop/creative/tv All 7 modes valid everywhere: ai/stop/game/creative/turbo/offline/tv
P0 Go/MCP version 12.0 → 17.9.8 main.go + rest.go + mcp/server.go hardcoded "v12.0" const Version = "17.9.8" in main, passed to api + mcp

P0 — Go correctness (4)

# Fix v17.9.7 Problem v17.9.8 Solution
P0 profiles YAML parsed Get()/List() used filename as Name+DisplayName, ignored YAML fields yaml.Unmarshal — reads display_name, role, hud_theme, token_budget from .yaml files
P0 MCP = real JSON-RPC 2.0 /mcp returned static JSON {"protocol":"mcp/v1"} — not a protocol implementation /rpc endpoint: initialize + tools/list + tools/call with proper request IDs + error codes (-32601 etc.)
P0 Current() TrimSpace Returned "ai\n" (state file has trailing newline) → mode != "ai" always true in Go strings.TrimSpace(string(data))
P0 main.go dead code removed getGPUStatus() + writeJSON() duplicated (also in rest.go) — unused Removed from main.go; rest.go is the single source

P1 — reliability (6)

# Fix v17.9.7 Problem v17.9.8 Solution
P1 crit() aborts immediately Set CRITICAL_FAIL=1 + continued → cascading secondary errors (toolkit fail → Docker start → network → Phase 4…) crit() calls exit 1 after logging — clean abort, re-runnable
P1 Phase 1 critical ops = crit pacman -Syu / package install were fail() (non-crit) → checkpoint written despite broken state → re-run skips broken phase Both are now crit() → abort before checkpoint
P1 multilib scoped + synced Aggressive sed uncommented EVERY Include= line; no pacman -Sy after enable Scoped awk (only [multilib] block) + explicit pacman -Sy (without it: "target not found")
P1 WebSocket real status + origin Pushed only {"time":"..."} every 2s; CheckOrigin: return true (any origin) Pushes full host-status JSON (GPU/mode/containers/disk); origin check: localhost + LAN private ranges only
P1 download-models fail-hard Missing Ollama compose = silent skip → "complete"; missing SGLang hf_repo = warn → "complete" Both now FAILED=$((FAILED+1)) → exit 1
P1 model-manager Ollama via docker Used command -v ollama (host binary — never installed; Ollama runs in container) docker exec ollama-agent ollama pull + checks container running

P2 — hardening (3)

# Fix v17.9.7 Problem v17.9.8 Solution
P2 ScarliHQ runtime slim nvidia/cuda:12.8.0-base + docker.io = ~2.5GB image alpine:3.20 + ca-certificates + tzdata = ~20MB (100× smaller)
P2 .env permissions 600 chmod 664 (group-readable; .env may hold HF_TOKEN in future) chmod 600 (owner-only)
P2 CI: go build + docker build CI only did shellcheck + YAML + bash -n — Go compile errors + Dockerfile issues slipped through Added go-build job (go vet + go build + go test) + scarlihq-docker-build job (real docker build)

Review false-positives (verified already-correct in v17.9.7)

  • internal/api/ and internal/profiles/ packages DO exist (not 404 — reviewer may have checked a stale GitHub cache).
  • yq -r '.key' works with both python-kislyuk-yq (Arch package) AND go-yq — no syntax mismatch.
  • usermod -aG docker is in Phase 3 (after docker group created in Phase 1).

🆕 What's New in v17.9.7 (vs v17.9.6)

Reliability + real dashboard — 11 fixes. (See git history for full table.)

Key: ScarliHQ image tag unified, --disable-flashinfer, network disconnect loop, multilib pre-check, download-models FAILED counter, http_ok() fallback, /models chmod 750, git tag pushed.


🎮 GPU Modes (CLI + Dashboard)

# CLI (host, direct):
scarlix-mode ai        # Verified AI path + healthcheck
scarlix-mode turbo      # Max throughput
scarlix-mode offline    # BeeLlama CPU (q4_0 KV, 32k context)
scarlix-mode game       # Native Steam/Sunshine (needs host systemd — NOT from dashboard)
scarlix-mode creative   # ComfyUI + Video + Music
scarlix-mode tv         # Docker Sunshine
scarlix-mode stop       # Stop AI stack (keeps dashboard/infra)
scarlix-mode vram       # VRAM health
scarlix-mode status     # System summary
scarlix-doctor          # Self-diagnostic (--fix for auto-fix)

Dashboard (http://<ip>:8090/ — login with SCARLIHQ_TOKEN from /etc/scarlix/.env): all 7 modes clickable. game/tv request the switch but systemctl sunshine runs on host (bridge applies it) — see Known Limitations.


🧪 Model-Agnostic (Single Source of Truth)

Edit /etc/scarlix/models.yaml:

sglang:
  model_path: "/models/Qwen3-14B-AWQ"
  hf_repo: "Qwen/Qwen3-14B-AWQ"
  # Change to ANY HuggingFace model:
  # model_path: "/models/Meta-Llama-3.1-8B-Instruct"
  # hf_repo: "meta-llama/Llama-3.1-8B-Instruct"

Then:

download-models.sh   # download new model (disk-space pre-checked)
scarlix-mode ai      # restart with new model (hash change → force-recreate)

🔧 NVIDIA Rollback

sudo downgrade nvidia-open      # rollback driver
sudo grub-set-default 1 && sudo reboot   # boot LTS kernel

📁 File Layout (v17.9.8)

OS/
├── install.sh                              # v17.9.8: Bootstrap (5 phases, host-bridge setup, multilib, disk-check)
├── README.md
├── AGENTS.md
├── VERSION                                 # 17.9.8 (single source of truth)
├── models.yaml                             # Single source of truth (env vars → compose)
├── packages.x86_64                         # Package list (yq, curl, wget, steam, wine, …)
├── .github/workflows/ci.yml               # CI: shellcheck + bash + YAML + compose + go-build + docker-build
├── files/
│   ├── usr/local/bin/
│   │   ├── scarlix-wizard                  # Creates .experimental for 2+ GPU, gateway prompt
│   │   ├── scarlix-mode                    # Healthcheck + env var parsing + wait_for_healthy
│   │   ├── scarlix-doctor                  # Self-diagnostic (--fix, http_ok -k, host-bridge check)
│   │   ├── scarlix-host-bridge             # v17.9.8 NEW: privileged ops for dashboard (systemd timer)
│   │   ├── download-models.sh              # HF download (FAILED counter + disk + missing-config fail)
│   │   └── model-manager.sh                # Weekly HF auto-pull + Ollama via docker exec + Telegram
│   └── etc/
│       ├── systemd/
│       │   ├── zram-generator.conf
│       │   └── system/
│       │       ├── model-manager.{service,timer}
│       │       ├── scarlix-host-bridge.{service,timer}   # v17.9.8 NEW: 5s status refresh
│       │       └── generate-env.sh         # /etc/scarlix/.env (secrets + SCARLIHQ_TOKEN, guarded)
│       └── pacman.d/hooks/
├── ai/                                     # Docker stacks (all scarlix-net)
│   ├── sglang/docker-compose.yml          # cu128, --disable-flashinfer, env vars
│   ├── vllm/docker-compose.yml            # TP=1, no LoRA
│   ├── llamacpp/docker-compose.yml        # Official llama.cpp, q4_0 KV, wget healthcheck
│   ├── ollama/docker-compose.yml          # Fallback (starter qwen2.5:3b), CPU, mem_limit 6g
│   └── comfyui/docker-compose.yml         # Creative profile, absolute volumes
├── scarlihq/                               # Dashboard (host-bridge architecture, alpine)
│   ├── Dockerfile                          # Multi-stage: go build → alpine (NO docker.sock, NO nvidia)
│   ├── docker-compose.yml                  # image: scarlihq:latest, ro mounts + bridge/ rw
│   ├── go.mod / go.sum                    # v17.9.8: only gorilla/websocket + yaml.v3 (was 11 heavy deps)
│   ├── cmd/scarlihq/
│   │   ├── main.go                          # Version const, serves HTML + registers routes
│   │   └── frontend/dist/index.html        # Real dashboard (token auth, all 7 modes, live WS)
│   └── internal/
│       ├── api/rest.go                      # /api/* (auth middleware, reads host-status.json)
│       ├── scarlix_mode/mode.go             # Current() TrimSpace, Set() writes desired-mode
│       ├── profiles/loader.go               # Real yaml.Unmarshal
│       ├── webui/ws.go                      # Real status push + origin check
│       ├── mcp/server.go                    # Real JSON-RPC 2.0 (initialize/tools/list/tools/call)
│       ├── guard/guard.go                   # Command safety patterns
│       └── status/status.go                 # Shared HostStatus type + reader
└── agents/, gaming/, voice/, network/, security/, monitoring/, ...

🏗️ Verified AI Path (v17.9.8)

GPU 0: RTX 5060 Ti 16GB (Blackwell sm_120)
└── Tier-1: SGLang v0.4.4-cu128 (--disable-flashinfer, mem-fraction 0.85)

GPU 1: RTX 4060 Ti 16GB (Ada sm_89)
└── Tier-2: vLLM v0.8.0 (TP=1, mixed-arch safe)

CPU:
└── Tier-4: llama.cpp (official image, q4_0 KV cache, 32k context)

Fallback (always ready):
└── Ollama 0.5.4 + qwen2.5:3b (starter model auto-downloaded)

🔄 Fallback Chain

  1. scarlix-mode ai → SGLang (+ vLLM if .experimental)
  2. SGLang unhealthy after 300s → docker stop sglang → try vLLM
  3. vLLM unhealthy after 300s → docker stop vllm → try BeeLlama (CPU)
  4. BeeLlama fails → Ollama (qwen2.5:3b, always ready)
  5. Re-run scarlix-mode ai → healthchecks + restarts unhealthy

ScarliHQ Dashboard (:8090)

Host-bridge architecture: dashboard reads JSON status, writes desired-mode. No privileged host access.

  • GPU cards (temp, util, VRAM, power, compute_cap)
  • Mode switcher (all 7 modes — async via host bridge, ~5s)
  • Container table (name, status, ports)
  • Live updates via WebSocket (every 2s)
  • Token auth on all API + WS endpoints

📜 Version History

Version Date Key Changes
v18.5 2026-10 Parent dir boundary + user CLI + WS hardening. P0: /var/lib/scarlix root:root 755 (was user-owned → symlink attack), scarlix-mode sudo re-exec (CLI was dead after LPE fix), download-models sudo re-exec, /opt/scarlix/.env chowned root on upgrade, flock -n (was blocking for hours). P1: models.yaml preserved on upgrade, exclusive locks for model writes, WS write deadline + 16 connection limit, status.Read() real stale check (time.Parse), mem_fraction in hash, fallback JSON Error field. 10 fixes from 2 reviews.
v18.4 2026-10 Security + upgrade reliability. P0: Local Privilege Escalation fixed (chown -R user /etc+opt → root:root; source .env → load_env_safe; SMG_KEY not logged). P1: checkpoint version+hash, rsync --delete stale files, flock on /models (model-manager + download-models + scarlix-mode), mktemp for host-status.json. 12 fixes.
v18.3 2026-10 Critical correctness. P0: octal permission bug (600&022=16 → rejected all 0600 files → mode switch broken!), local outside function, TOCTOU mitigation. P1: scarlix-mode returns failure if no engine healthy, CI go test no mask, host bridge fail-closed, scarlix-doctor token auth, status.Read() error on corrupt JSON, df locale-independent, stale version headers. 11 fixes from 4 reviews.
v18.2 2026-10 Security + UX. P0: real_user detection under systemd (was: $USER=root → chown root). P1: const→var Version (ldflags fix), systemctl sudo fallback, token TTY-only + log 600, FIFO/pipe rejection, models.yaml schema validation, go.sum generated at build. P2: CI chown 65532, migration applies pending mode, whiptail ESC, bridge retry_count restore, creative/tv .env check, .env.template v18.2.
v18.1 2026-10 Correctness fixes. P0: host-bridge mode_rc exit code capture (was `
v18.0.0 2026-10 Secure Host-Bridge. 10 fixes: bridge-input/+bridge-state/ separation (P0 symlink attack fix), single scarlix-mode execution (P0 double-run fix), concurrent Mode.Set os.CreateTemp, GET /api/mode transition state, persistent last-transition, scarlix-mode .env permission, crypto/subtle, input validation (symlink/owner/size), no hardcoded version, CI VERSION build-arg + mode integration test. Breaking: bridge/ → bridge-input/ + bridge-state/.
v17.9.9 2026-10 Host-Bridge stabilization + state machine. 13 fixes: Dockerfile nonroot user (P0), bridge/ chown 65532+775 (P0), df parsing fixed (P0), HTTP error codes, desired-mode retry, JSON via python3, state machine fields, MCP secureCompare, WS CIDR origin, scarlix-mode sudo removed, turbo dump_vram, systemd hardening, mem-fraction env var, version via ldflags. CI: runtime smoke test.
v17.9.8 2026-10 Host-bridge architecture + reliability. 16 fixes: ScarliHQ privilege boundary (P0), Dockerfile build order, go.mod cleaned, API auth, mode API/CLI/UI unified, Go/MCP v12→17.9.8, profiles YAML parsed, real JSON-RPC MCP, Current() TrimSpace, crit() aborts, Phase 1 crit ops, multilib scoped+synced, WS real status+origin, download-models fail-hard, model-manager Ollama via docker, alpine runtime 20MB, .env 600, CI go+docker build.
v17.9.7 2026-10 Reliability + real dashboard. 11 fixes: ScarliHQ image tag, Dockerfile reorder, nvidia/cuda runtime, real dashboard HTML, git tag, SGLang --disable-flashinfer, network disconnect loop, multilib, disk checks, /models 750, http_ok fallback.
v17.9.6 2026-10 Reliability. 15 fixes: duplicate networks, scarlix_net→scarlix-net, VERSION, default paths, download fail-hard, doctor unhealthy=FAIL, hash after recreate, $DC up -d, healthchecks, version unified, docs.
v17.9.1 2026-10 Hotfix. 10 fixes: MODE pred flock, hash healthcheck, HF repo IDs, checkpoint nvidia_open, schema, scarlix_net, SGLang pin, generate-env guard, version, scarlix-doctor.
v17.5–17.9 2026-10 EndeavourOS re-base, bootstrap installer, model-agnostic, fail-hard, TP=1.
v16.x 2026-08 Garuda Linux.

⚠️ Known Limitations

  • CI covers syntax + build, not runtime: .github/workflows/ci.yml runs shellcheck + bash -n + YAML + compose + go build + docker build. QEMU doesn't test NVIDIA/CUDA (no GPU in CI).
  • Single maintainer: One person maintaining full stack.
  • vLLM TP=1: Separate model per GPU (less efficient than TP=2 but mixed-arch safe).
  • ScarliHQ game/tv modes: scarlix-mode game calls systemctl start sunshine on the HOST (via host-bridge, which runs as root — sudo removed in v17.9.9). If sunshine.service isn't installed, mode switch fails — dashboard now shows ✗ game failed in mode transition field (v17.9.9 state machine). Check /var/log/scarlix-host-bridge.log.
  • ScarliHQ auth = single shared token: No per-user auth. Token in /etc/scarlix/.env (chmod 600). v18.7.5: token no longer accepted via ?token= URL param — dashboard reads it only from sessionStorage (paste-once-per-session via the auth gate); previously the URL ?token= could leak via browser history/proxy logs (known limitation, now resolved). For production, put a reverse proxy with session auth in front.
  • First install is slow: pacman -Syu + NVIDIA + CUDA + cuDNN + Steam/Wine + docker images + ScarliHQ Go build + model download (50-150GB) = hours. Reboots + re-login required for NVIDIA driver + docker group.
  • host-status.json 5s latency: Dashboard data is up to 5s stale (host-bridge timer interval). Mode transition feedback (applied/retrying/failed) appears within 5s. Not for real-time control.

⚠️ Known Arch/EndeavourOS Risks

These are platform-level risks, not SCARLIX bugs. EndeavourOS/Arch is a rolling release — always test after pacman -Syu.

Risk Impact Mitigation
Calamares + existing BTRFS subvolumes Install fails if disk has existing @/@home Use clean disk or rename existing subvolumes before install
nvidia-container-toolkit via AUR GPU Docker runtime may break Install from Docker official repo (install.sh Phase 3 already does this)
nvidia-open on Turing (RTX 20xx) Power management gaps, flicker Use nvidia (proprietary) if flicker occurs; nvidia-open is for Ampere+
SGLang AUR packaging gaps Tier-1 may need manual fixes We use Docker image (not AUR) — avoids this
Ollama CUDA regressions after update Tier-3 breaks model-manager.sh tests health after pull; pinned to 0.5.4
snap-pac backup hook fragility Docker volumes may not snapshot scarlix-docker-backup.sh runs BEFORE pacman; restic used as fallback
nvidia-open-lts + nvidia-dkms conflict LTS fallback fails install.sh uses atomic single pacman -S call to avoid this
model-manager.timer auto-update May pull incompatible models Timer tests health post-pull; rollback via Snapper if needed

🗺️ Roadmap

  • v18.9: LiteLLM E2E inference CI test, Go unit tests (ReserveWSTicket, Mode.Set O_EXCL), CI artifact sharing between jobs (faster), scarlix-doctor LiteLLM + model identity checks.
  • v19.0: Per-user auth (OIDC/LDAP), real GPU telemetry via DCGM, mode-switch history, Incus dev workspaces.

About

ScarLiXoS - Self-Hosted AI Home Cloud

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages