mars(kittentts): review round 2 — setpriv delivery, staged hub tree, ordering fix
1: stageVenv declared with the other path vars before the early-exit reads it (set -u no longer kills the self-heal path on later boots). 2: delivery is rm+cp as the container uid via setpriv — no mv -Tf rename-replace on a non-empty directory (that failed every re-provision); chown -R step gone with it. 3: network-online.target back in after= — this unit CAN download at boot, so uv must not run pre-network. 4: root never writes into hermesHome at all. refs/main, snapshots and venv staging happen under root-owned staging; delivery drops to uid 986 via setpriv, rm -rf + cp -a from the staging sources. Symlink-takeover of refs/install targets and the rm/cp race on the delivered tree are structurally gone. Minor: sanity check now constructs KittenTTS against the staged hub tree, not just imports; fetchurls pinned to the commit sha matching refs/main; fast-path comment matches behavior (staging venv skips rebuild on damaged delivery).
This commit is contained in:
+70
-45
@@ -92,15 +92,15 @@ let
|
|||||||
builtins.readFile ./kittentts/kitten-misaki-stub.py
|
builtins.readFile ./kittentts/kitten-misaki-stub.py
|
||||||
);
|
);
|
||||||
kittenttsModelOnnx = pkgs.fetchurl {
|
kittenttsModelOnnx = pkgs.fetchurl {
|
||||||
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/main/kitten_tts_mini_v0_8.onnx";
|
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/kitten_tts_mini_v0_8.onnx";
|
||||||
sha256 = "sha256-D1u65PxIAMmNvFRKh+z6eVEN4vuCItsw0S5b/pF335E=";
|
sha256 = "sha256-D1u65PxIAMmNvFRKh+z6eVEN4vuCItsw0S5b/pF335E=";
|
||||||
};
|
};
|
||||||
kittenttsModelVoices = pkgs.fetchurl {
|
kittenttsModelVoices = pkgs.fetchurl {
|
||||||
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/main/voices.npz";
|
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/voices.npz";
|
||||||
sha256 = "sha256-QK0mOJUrd7ey8wEn4mCOFp/GndJWtTvYqqNAmjMZPEI=";
|
sha256 = "sha256-QK0mOJUrd7ey8wEn4mCOFp/GndJWtTvYqqNAmjMZPEI=";
|
||||||
};
|
};
|
||||||
kittenttsModelConfig = pkgs.fetchurl {
|
kittenttsModelConfig = pkgs.fetchurl {
|
||||||
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/main/config.json";
|
url = "https://huggingface.co/KittenML/kitten-tts-mini-0.8/resolve/c02725660cea441db4c383af69f1f26f5cd00947/config.json";
|
||||||
sha256 = "sha256-axYLybGeJOyyHoS8FPin2iH99H7HLUJFC8XPUUthgEo=";
|
sha256 = "sha256-axYLybGeJOyyHoS8FPin2iH99H7HLUJFC8XPUUthgEo=";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -524,10 +524,16 @@ in
|
|||||||
wants = [ "network-online.target" ];
|
wants = [ "network-online.target" ];
|
||||||
# after prepare-dirs (review #6): on a fresh state dir this unit must not
|
# after prepare-dirs (review #6): on a fresh state dir this unit must not
|
||||||
# create hermesHome root-owned before prepare-dirs sets the ownership
|
# create hermesHome root-owned before prepare-dirs sets the ownership
|
||||||
# layout — same ordering contract the mnemosyne unit has.
|
# layout — same ordering contract the mnemosyne unit has. PLUS
|
||||||
after = [ "hermes-agent-prepare-dirs.service" ];
|
# network-online ordering (review2 #3): this unit CAN download at boot
|
||||||
|
# (unlike mnemosyne's store-path build), so uv must not run before the
|
||||||
|
# network is actually up.
|
||||||
|
after = [
|
||||||
|
"network-online.target"
|
||||||
|
"hermes-agent-prepare-dirs.service"
|
||||||
|
];
|
||||||
requires = [ "hermes-agent-prepare-dirs.service" ];
|
requires = [ "hermes-agent-prepare-dirs.service" ];
|
||||||
path = [ pkgs.uv pkgs.coreutils ];
|
path = [ pkgs.uv pkgs.coreutils pkgs.setpriv ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
TimeoutStartSec = 600;
|
TimeoutStartSec = 600;
|
||||||
@@ -549,8 +555,12 @@ in
|
|||||||
# into hermesHome, and only after being validated. Root doesn't follow
|
# into hermesHome, and only after being validated. Root doesn't follow
|
||||||
# any uid-986-writable path while running as root.
|
# any uid-986-writable path while running as root.
|
||||||
stageDir=/var/lib/hermes-kittentts
|
stageDir=/var/lib/hermes-kittentts
|
||||||
# Root-side staging of the model files (hash-pinned store paths; the
|
# Root-side staging of the DELIVERED TREE (venv + full HF hub layout
|
||||||
# delivered copy under hermesHome is compared against these).
|
# including refs/main) — everything root writes lives here, 0700.
|
||||||
|
# review2 #4: root never writes into hermesHome; delivery happens as
|
||||||
|
# the container uid via setpriv, copying from these root-owned sources.
|
||||||
|
stageVenv=$stageDir/venv
|
||||||
|
stageHub=$stageDir/kittentts-hf
|
||||||
stageSnap=$stageDir/hf-model
|
stageSnap=$stageDir/hf-model
|
||||||
# Input key: requirements + wheel + stub + model files + resolved script.
|
# Input key: requirements + wheel + stub + model files + resolved script.
|
||||||
# Review #5 — a miss on the old requirements-only stamp let a changed
|
# Review #5 — a miss on the old requirements-only stamp let a changed
|
||||||
@@ -577,17 +587,23 @@ in
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- staging venv: root-owned path, uv cache included. Root runs
|
# ---- staging venv + staged hub tree: root-owned path, uv cache
|
||||||
# python from HERE (container can write nothing in this tree), then the
|
# included. Root runs python from HERE (container can write nothing in
|
||||||
# FINISHED result is copied into hermesHome. Review #1: a stamp symlink
|
# this tree); the FINISHED result is copied into hermesHome AS THE
|
||||||
# into /etc/shadow can't redirect us — the stamp dir is root-only. ----
|
# CONTAINER USER via setpriv (review2 #4) — root never writes into
|
||||||
stageVenv=$stageDir/venv
|
# hermesHome, so no uid-986-controlled path is ever followed while
|
||||||
mkdir -p "$stageDir" "$stageSnap"
|
# running as root; symlink-takeover of stamps/refs/install targets is
|
||||||
|
# structurally impossible. ----
|
||||||
|
mkdir -p "$stageDir" "$stageSnap" "$stageVenv" "$stageHub"
|
||||||
chmod 0700 "$stageDir"
|
chmod 0700 "$stageDir"
|
||||||
install -m 0444 ${kittenttsModelConfig} "$stageSnap/config.json"
|
install -m 0444 ${kittenttsModelConfig} "$stageSnap/config.json"
|
||||||
install -m 0444 ${kittenttsModelOnnx} "$stageSnap/kitten_tts_mini_v0_8.onnx"
|
install -m 0444 ${kittenttsModelOnnx} "$stageSnap/kitten_tts_mini_v0_8.onnx"
|
||||||
install -m 0444 ${kittenttsModelVoices} "$stageSnap/voices.npz"
|
install -m 0444 ${kittenttsModelVoices} "$stageSnap/voices.npz"
|
||||||
|
|
||||||
|
# Skip the venv REBUILD when staging is still valid (review2 minor:
|
||||||
|
# damaged delivery should be a copy, not a rebuild).
|
||||||
|
if ! [ -x "$stageVenv/bin/python" ] \
|
||||||
|
|| ! [ -f "$stageVenv/lib/python3.13/site-packages/kitten_tts_stub.py" ]; then
|
||||||
rm -rf "$stageVenv"
|
rm -rf "$stageVenv"
|
||||||
UV_CACHE_DIR=$stageDir/uv-cache \
|
UV_CACHE_DIR=$stageDir/uv-cache \
|
||||||
uv venv "$stageVenv" --python ${pkgs.python313}/bin/python3 --quiet
|
uv venv "$stageVenv" --python ${pkgs.python313}/bin/python3 --quiet
|
||||||
@@ -600,46 +616,55 @@ in
|
|||||||
uv pip install --python "$stageVenv/bin/python" --quiet --no-deps \
|
uv pip install --python "$stageVenv/bin/python" --quiet --no-deps \
|
||||||
${kittenttsWheel}
|
${kittenttsWheel}
|
||||||
|
|
||||||
# Dead-import shim: .pth auto-loads kitten_tts_stub at interpreter start
|
# Dead-import shim: .pth auto-loads kitten_tts_stub at interpreter
|
||||||
# so `from misaki import en, espeak` resolves without the real misaki.en.
|
# start so `from misaki import en, espeak` resolves without the real
|
||||||
|
# misaki.en.
|
||||||
siteDir=$("$stageVenv/bin/python" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
|
siteDir=$("$stageVenv/bin/python" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
|
||||||
cp ${kittenttsStub} "$siteDir/kitten_tts_stub.py"
|
cp ${kittenttsStub} "$siteDir/kitten_tts_stub.py"
|
||||||
printf 'import kitten_tts_stub\n' > "$siteDir/zz-kitten-stub.pth"
|
printf 'import kitten_tts_stub\n' > "$siteDir/zz-kitten-stub.pth"
|
||||||
|
fi
|
||||||
|
|
||||||
# Root-side sanity: the venv python must import the plugin entrypoint
|
# Stage the full delivered hub tree (exact hf_hub_download layout:
|
||||||
# cleanly offline; a failing build aborts BEFORE anything lands in
|
# refs/main -> snapshots/<sha>; review1 #3) under root-owned staging.
|
||||||
# hermesHome (fail closed, not half-delivered).
|
stageModelDir=$stageHub/hub/models--KittenML--kitten-tts-mini-0.8
|
||||||
HF_HOME=$stageDir/hf-cache \
|
mkdir -p "$stageModelDir/refs" "$stageModelDir/snapshots/$modelSha"
|
||||||
|
printf '%s' "$modelSha" > "$stageModelDir/refs/main"
|
||||||
|
install -m 0444 ${kittenttsModelOnnx} "$stageModelDir/snapshots/$modelSha/kitten_tts_mini_v0_8.onnx"
|
||||||
|
install -m 0444 ${kittenttsModelVoices} "$stageModelDir/snapshots/$modelSha/voices.npz"
|
||||||
|
install -m 0444 ${kittenttsModelConfig} "$stageModelDir/snapshots/$modelSha/config.json"
|
||||||
|
# No blobs/ indirection: kittentts reads paths RETURNED by
|
||||||
|
# hf_hub_download, which serves the resolved snapshot file directly
|
||||||
|
# (prefer-dir layout works offline for fully-materialized files).
|
||||||
|
|
||||||
|
# Root-side sanity: the staged interpreter must construct the model
|
||||||
|
# END-TO-END offline (review2 minor — import alone doesn't exercise
|
||||||
|
# hf_hub_download; a broken cache layout must fail HERE, not in the
|
||||||
|
# gateway). Points HF_HOME at the staged hub tree itself.
|
||||||
|
HF_HOME=$stageHub \
|
||||||
HF_HUB_OFFLINE=1 \
|
HF_HUB_OFFLINE=1 \
|
||||||
PHONEMIZER_ESPEAK_LIBRARY="$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_library_path()))')" \
|
PHONEMIZER_ESPEAK_LIBRARY="$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_library_path()))')" \
|
||||||
PHONEMIZER_ESPEAK_DATA_PATH=$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_data_path()))') \
|
PHONEMIZER_ESPEAK_DATA_PATH="$("$stageVenv/bin/python" -c 'import espeakng_loader,pathlib;print(pathlib.Path(espeakng_loader.get_data_path()))')" \
|
||||||
"$stageVenv/bin/python" -c 'from kittentts import KittenTTS; print("kittentts import ok")' >/dev/null
|
"$stageVenv/bin/python" -c 'from kittentts import KittenTTS; KittenTTS("KittenML/kitten-tts-mini-0.8"); print("kittentts offline build ok")' >/dev/null
|
||||||
|
|
||||||
# ---- deliver: only finished artifacts into hermesHome. Copy, not
|
# ---- deliver AS THE CONTAINER USER (review2 #4): root never writes
|
||||||
# move — staging stays root-owned for the integrity check above. ----
|
# into hermesHome, so no symlink race and no `chown` step. setpriv
|
||||||
hubDir=${hermesHome}/kittentts-hf/hub/models--KittenML--kitten-tts-mini-0.8
|
# drops to uid 986, rm -rf's the old delivered copies and copies the
|
||||||
snap=$hubDir/snapshots/$modelSha
|
# fresh staging tree in. cp-as-986 also fixes review2 #2: rm+cp in one
|
||||||
rm -rf "$venv.tmp"
|
# step, no mv -Tf rename-replace on a non-empty directory.
|
||||||
cp -a "$stageVenv" "$venv.tmp"
|
setpriv --reuid=${hermesUid} --regid=${hermesGid} --clear-groups \
|
||||||
mv -Tf "$venv.tmp" "$venv" # atomic-ish swap of the delivered venv
|
env HOME="$hermesHome" sh -c '
|
||||||
|
set -eu
|
||||||
mkdir -p "$hubDir/refs" "$snap" "$hubDir/blobs"
|
rm -rf "$1" "$2"
|
||||||
printf '%s' "$modelSha" > "$hubDir/refs/main" # review #3: resolves offline
|
cp -a "$3" "$1"
|
||||||
install -m 0444 ${kittenttsModelOnnx} "$snap/kitten_tts_mini_v0_8.onnx"
|
cp -a "$4" "$2"
|
||||||
install -m 0444 ${kittenttsModelVoices} "$snap/voices.npz"
|
' _ \
|
||||||
install -m 0444 ${kittenttsModelConfig} "$snap/config.json"
|
"${hermesHome}/kittentts-venv" \
|
||||||
# HF hub layout puts the blob under blobs/<sha256> with snapshot
|
"${hermesHome}/kittentts-hf" \
|
||||||
# symlinks pointing back — but kittentts reads config/model paths
|
"$stageVenv" \
|
||||||
# RETURNED by hf_hub_download, which serves the resolved snapshot file
|
"$stageHub"
|
||||||
# directly. Files-as-plain-content suffices (no LFS indirection here:
|
|
||||||
# pre-materialized), no blob indirection needed.
|
|
||||||
|
|
||||||
# Hand ownership to the container uid BEFORE any python runs in
|
|
||||||
# hermesHome's copy — root does its python in $stageVenv, not here.
|
|
||||||
chown -R ${hermesUid}:${hermesGid} "$venv" "$hubDir"
|
|
||||||
|
|
||||||
# Stamp LAST, root-owned outside hermesHome — luna can delete it (which
|
# Stamp LAST, root-owned outside hermesHome — luna can delete it (which
|
||||||
# just forces a cheap re-provision copy), not tamper via symlink.
|
# forces a cheap re-delivery on next boot), not tamper via symlink.
|
||||||
printf '%s' "$inputHash" > "$stampFile"
|
printf '%s' "$inputHash" > "$stampFile"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user