Upload install.sh with huggingface_hub
Browse files- install.sh +172 -45
install.sh
CHANGED
|
@@ -1,21 +1,30 @@
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
-
# Install the Lain parser service: one
|
| 3 |
-
# systemd user unit. Everything lands under --prefix
|
| 4 |
-
# ~/.local/share/lain-parser) and ~/.config/systemd/user; no sudo
|
|
|
|
| 5 |
#
|
| 6 |
-
# bash install.sh
|
| 7 |
-
# bash install.sh --
|
| 8 |
-
# bash install.sh --
|
|
|
|
|
|
|
| 9 |
#
|
| 10 |
-
#
|
|
|
|
|
|
|
|
|
|
| 11 |
set -euo pipefail
|
| 12 |
|
| 13 |
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 14 |
REPO="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
| 19 |
SNAPSHOT_FILES=(anifilebert config.json model.safetensors tokenizer_config.json \
|
| 20 |
vocab.json vocab.char.json label_schema.json dapt.json)
|
| 21 |
PREFIX="${LAIN_PARSER_PREFIX:-${HOME}/.local/share/lain-parser}"
|
|
@@ -29,7 +38,6 @@ HOSTS="${LAIN_PARSER_HOST:-127.0.0.1,172.17.0.1}"
|
|
| 29 |
SOCKET="${LAIN_PARSER_SOCKET:-${HOME}/.local/share/lain/parser.sock}"
|
| 30 |
START=1
|
| 31 |
VERIFY=0
|
| 32 |
-
FORCE=0
|
| 33 |
|
| 34 |
while [[ $# -gt 0 ]]; do
|
| 35 |
case "$1" in
|
|
@@ -37,54 +45,163 @@ while [[ $# -gt 0 ]]; do
|
|
| 37 |
--port) PORT="$2"; shift 2 ;;
|
| 38 |
--host) HOSTS="$2"; shift 2 ;;
|
| 39 |
--socket) SOCKET="$2"; shift 2 ;;
|
|
|
|
|
|
|
| 40 |
--no-start) START=0; shift ;;
|
| 41 |
-
--force) FORCE=1; shift ;;
|
| 42 |
--verify) VERIFY=1; shift ;;
|
| 43 |
-
-h|--help) sed -n '2,
|
| 44 |
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
| 45 |
esac
|
| 46 |
done
|
| 47 |
|
| 48 |
step() { printf '\n== %s\n' "$*"; }
|
| 49 |
die() { echo "error: $*" >&2; exit 1; }
|
| 50 |
-
|
| 51 |
sha_of() { sha256sum "$1" | cut -d' ' -f1; }
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
| 66 |
|
| 67 |
if [[ "$VERIFY" == 1 ]]; then
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
exit 0
|
| 70 |
fi
|
| 71 |
|
| 72 |
-
[[
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
[[ -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
step "installing into ${PREFIX}"
|
| 79 |
-
mkdir -p "${PREFIX}/app/fp" "${PREFIX}/artifact
|
| 80 |
cp -f "${SCRIPT_DIR}/app/serve.py" "${PREFIX}/app/serve.py"
|
| 81 |
cp -f "${SCRIPT_DIR}"/app/fp/*.py "${PREFIX}/app/fp/"
|
| 82 |
cp -f "${SCRIPT_DIR}/requirements.txt" "${PREFIX}/requirements.txt"
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
step "recording hashes"
|
| 90 |
{
|
|
@@ -113,6 +230,13 @@ else
|
|
| 113 |
fi
|
| 114 |
"${PREFIX}/venv/bin/python" -c "import torch, transformers; print('torch', torch.__version__, '| transformers', transformers.__version__)"
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
step "systemd user unit"
|
| 117 |
mkdir -p "${UNIT_DIR}"
|
| 118 |
unit_tmp="$(mktemp)"
|
|
@@ -137,18 +261,18 @@ if [[ ! -f "${UNIT_DIR}/${UNIT}" ]]; then
|
|
| 137 |
echo "wrote ${UNIT_DIR}/${UNIT}"
|
| 138 |
elif diff -q "$unit_tmp" "${UNIT_DIR}/${UNIT}" >/dev/null; then
|
| 139 |
echo "unit already up to date"
|
| 140 |
-
elif [[ "$FORCE" == 1 ]]; then
|
| 141 |
-
install -m 0644 "$unit_tmp" "${UNIT_DIR}/${UNIT}"
|
| 142 |
-
echo "unit replaced (--force)"
|
| 143 |
else
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
fi
|
| 147 |
rm -f "$unit_tmp"
|
| 148 |
|
| 149 |
if [[ "$START" == 1 ]] && command -v systemctl >/dev/null 2>&1; then
|
| 150 |
systemctl --user daemon-reload
|
| 151 |
systemctl --user enable --now "$UNIT" || true
|
|
|
|
| 152 |
if command -v curl >/dev/null 2>&1; then
|
| 153 |
ok=""
|
| 154 |
for _ in $(seq 1 20); do
|
|
@@ -168,10 +292,13 @@ fi
|
|
| 168 |
step "done"
|
| 169 |
cat <<SUMMARY
|
| 170 |
prefix ${PREFIX}
|
| 171 |
-
artifact ${ARTIFACT_NAME}
|
|
|
|
| 172 |
unit ${UNIT_DIR}/${UNIT}
|
| 173 |
native URL http://127.0.0.1:${PORT}
|
| 174 |
docker URL http://172.17.0.1:${PORT} (or host.docker.internal with host-gateway)
|
| 175 |
docker UDS unix:///data/parser.sock (socket at ${SOCKET})
|
|
|
|
|
|
|
| 176 |
verify bash install.sh --verify
|
| 177 |
SUMMARY
|
|
|
|
| 1 |
#!/usr/bin/env bash
|
| 2 |
+
# Install or update the Lain parser service: one pinned model artifact, a
|
| 3 |
+
# private venv and a systemd user unit. Everything lands under --prefix
|
| 4 |
+
# (default ~/.local/share/lain-parser) and ~/.config/systemd/user; no sudo
|
| 5 |
+
# anywhere.
|
| 6 |
#
|
| 7 |
+
# bash install.sh # install, or update to the channel default
|
| 8 |
+
# bash install.sh --model e5-s2 # pin a specific model version
|
| 9 |
+
# bash install.sh --local # install from this repo's artifacts/ (dev)
|
| 10 |
+
# bash install.sh --no-start # install files only
|
| 11 |
+
# bash install.sh --verify # re-check installed hashes + a live parse
|
| 12 |
#
|
| 13 |
+
# Artifacts come from the Hugging Face repo `Enrell/lain-parser`. manifest.json
|
| 14 |
+
# there is the release channel: `default` names the model, and every file's
|
| 15 |
+
# sha256 is verified against it before the running unit is touched. A failed
|
| 16 |
+
# download or hash check leaves the installed service untouched.
|
| 17 |
set -euo pipefail
|
| 18 |
|
| 19 |
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 20 |
REPO="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
| 21 |
+
HF_REPO="${LAIN_PARSER_HF_REPO:-Enrell/lain-parser}"
|
| 22 |
+
HF_BASE="https://huggingface.co/${HF_REPO}/resolve/main"
|
| 23 |
+
SERVICE_VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' \
|
| 24 |
+
"${SCRIPT_DIR}/app/PROVENANCE.json" 2>/dev/null || echo 0.0.0)"
|
| 25 |
+
# --local installs from the repository's artifacts/ tree (development).
|
| 26 |
+
LOCAL=0
|
| 27 |
+
MODEL_ARG=""
|
| 28 |
SNAPSHOT_FILES=(anifilebert config.json model.safetensors tokenizer_config.json \
|
| 29 |
vocab.json vocab.char.json label_schema.json dapt.json)
|
| 30 |
PREFIX="${LAIN_PARSER_PREFIX:-${HOME}/.local/share/lain-parser}"
|
|
|
|
| 38 |
SOCKET="${LAIN_PARSER_SOCKET:-${HOME}/.local/share/lain/parser.sock}"
|
| 39 |
START=1
|
| 40 |
VERIFY=0
|
|
|
|
| 41 |
|
| 42 |
while [[ $# -gt 0 ]]; do
|
| 43 |
case "$1" in
|
|
|
|
| 45 |
--port) PORT="$2"; shift 2 ;;
|
| 46 |
--host) HOSTS="$2"; shift 2 ;;
|
| 47 |
--socket) SOCKET="$2"; shift 2 ;;
|
| 48 |
+
--model) MODEL_ARG="$2"; shift 2 ;;
|
| 49 |
+
--local) LOCAL=1; shift ;;
|
| 50 |
--no-start) START=0; shift ;;
|
|
|
|
| 51 |
--verify) VERIFY=1; shift ;;
|
| 52 |
+
-h|--help) sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
| 53 |
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
| 54 |
esac
|
| 55 |
done
|
| 56 |
|
| 57 |
step() { printf '\n== %s\n' "$*"; }
|
| 58 |
die() { echo "error: $*" >&2; exit 1; }
|
|
|
|
| 59 |
sha_of() { sha256sum "$1" | cut -d' ' -f1; }
|
| 60 |
|
| 61 |
+
command -v python3 >/dev/null || die "python3 is required"
|
| 62 |
+
command -v curl >/dev/null || [[ "$LOCAL" == 1 ]] || die "curl is required for downloads (or use --local)"
|
| 63 |
+
|
| 64 |
+
# --- resolve the model + snapshot -----------------------------------------
|
| 65 |
+
|
| 66 |
+
MANIFEST_LOCAL="${PREFIX}/manifest.installed.json"
|
| 67 |
+
|
| 68 |
+
hf_fetch() { # hf_fetch <repo-path> <dest> — anonymous resolve-URL download
|
| 69 |
+
curl -fSL --retry 3 --connect-timeout 10 "${HF_BASE}/$1" -o "$2"
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
read_manifest() { # read_manifest <file> <python-expression-over-m>
|
| 73 |
+
python3 - "$1" "$2" <<'PY'
|
| 74 |
+
import json, sys
|
| 75 |
+
m = json.load(open(sys.argv[1]))
|
| 76 |
+
print(eval(sys.argv[2])) # manifest is our own release file; trusted shape
|
| 77 |
+
PY
|
| 78 |
}
|
| 79 |
|
| 80 |
if [[ "$VERIFY" == 1 ]]; then
|
| 81 |
+
step "verifying ${PREFIX}"
|
| 82 |
+
[[ -x "${PREFIX}/venv/bin/python" ]] || die "venv missing under ${PREFIX}"
|
| 83 |
+
[[ -f "$MANIFEST_LOCAL" ]] || die "installed manifest missing — was this installed by this installer?"
|
| 84 |
+
installed_name="$(read_manifest "$MANIFEST_LOCAL" 'm["installed_model"]')"
|
| 85 |
+
want="$(read_manifest "$MANIFEST_LOCAL" "m['models']['${installed_name}']['files']['model.int8.pt']")"
|
| 86 |
+
sum="$(sha_of "${PREFIX}/artifact/${installed_name}/model.int8.pt")"
|
| 87 |
+
[[ "$sum" == "$want" ]] || die "artifact hash mismatch: ${sum} != ${want}"
|
| 88 |
+
echo "artifact ${installed_name} hash ok"
|
| 89 |
+
"${PREFIX}/venv/bin/python" "${PREFIX}/app/serve.py" --check \
|
| 90 |
+
"Example.Show.S01E02.1080p-GROUP.mkv" \
|
| 91 |
+
--artifact "${PREFIX}/artifact/${installed_name}" \
|
| 92 |
+
--snapshot "${PREFIX}/anifilebert" >/dev/null
|
| 93 |
+
echo "model loads and parses"
|
| 94 |
exit 0
|
| 95 |
fi
|
| 96 |
|
| 97 |
+
if [[ "$LOCAL" == 1 ]]; then
|
| 98 |
+
ARTIFACT_NAME="${MODEL_ARG:-e5-s2}"
|
| 99 |
+
ARTIFACT_SRC="${REPO}/artifacts/${ARTIFACT_NAME}"
|
| 100 |
+
SNAPSHOT_SRC="${ANIFILEBERT_SNAPSHOT:-${REPO}/artifacts/anifilebert-dapt512-long}"
|
| 101 |
+
[[ -f "${ARTIFACT_SRC}/model.int8.pt" ]] || die "local artifact not found at ${ARTIFACT_SRC}"
|
| 102 |
+
[[ -f "${ARTIFACT_SRC}/config.json" ]] || die "local artifact config not found"
|
| 103 |
+
[[ -d "$SNAPSHOT_SRC" ]] || die "local snapshot not found at ${SNAPSHOT_SRC}"
|
| 104 |
+
MANIFEST_TMP=""
|
| 105 |
+
else
|
| 106 |
+
step "fetching release manifest (${HF_REPO})"
|
| 107 |
+
mkdir -p "${PREFIX}/.download"
|
| 108 |
+
MANIFEST_TMP="${PREFIX}/.download/manifest.json"
|
| 109 |
+
hf_fetch "manifest.json" "$MANIFEST_TMP" || die "manifest download failed"
|
| 110 |
+
ARTIFACT_NAME="${MODEL_ARG:-$(read_manifest "$MANIFEST_TMP" 'm["default"]')}"
|
| 111 |
+
read_manifest "$MANIFEST_TMP" "'${ARTIFACT_NAME}' in m['models']" >/dev/null 2>&1 || \
|
| 112 |
+
die "model ${ARTIFACT_NAME} not in manifest"
|
| 113 |
+
# Refuse cleanly when a model needs a newer service than the vendored app ships.
|
| 114 |
+
need="$(read_manifest "$MANIFEST_TMP" "m['models']['${ARTIFACT_NAME}'].get('min_service','0')")"
|
| 115 |
+
python3 - "$need" "$SERVICE_VERSION" <<'PY' || die "model requires service >= $need; update the installer first"
|
| 116 |
+
import sys
|
| 117 |
+
key = lambda v: [int(x) for x in v.split('.')]
|
| 118 |
+
sys.exit(0 if key(sys.argv[2]) >= key(sys.argv[1]) else 1)
|
| 119 |
+
PY
|
| 120 |
+
SNAP_NAME="$(read_manifest "$MANIFEST_TMP" "m['models']['${ARTIFACT_NAME}']['snapshot']")"
|
| 121 |
+
echo "channel default: $(read_manifest "$MANIFEST_TMP" 'm["default"]') -> installing ${ARTIFACT_NAME} (snapshot ${SNAP_NAME})"
|
| 122 |
+
fi
|
| 123 |
|
| 124 |
step "installing into ${PREFIX}"
|
| 125 |
+
mkdir -p "${PREFIX}/app/fp" "${PREFIX}/artifact" "${PREFIX}/anifilebert"
|
| 126 |
cp -f "${SCRIPT_DIR}/app/serve.py" "${PREFIX}/app/serve.py"
|
| 127 |
cp -f "${SCRIPT_DIR}"/app/fp/*.py "${PREFIX}/app/fp/"
|
| 128 |
cp -f "${SCRIPT_DIR}/requirements.txt" "${PREFIX}/requirements.txt"
|
| 129 |
+
|
| 130 |
+
if [[ "$LOCAL" == 1 ]]; then
|
| 131 |
+
cp -f "${ARTIFACT_SRC}/model.int8.pt" "${ARTIFACT_SRC}/config.json" \
|
| 132 |
+
"${PREFIX}/artifact/${ARTIFACT_NAME}/" 2>/dev/null || {
|
| 133 |
+
mkdir -p "${PREFIX}/artifact/${ARTIFACT_NAME}"
|
| 134 |
+
cp -f "${ARTIFACT_SRC}/model.int8.pt" "${ARTIFACT_SRC}/config.json" \
|
| 135 |
+
"${PREFIX}/artifact/${ARTIFACT_NAME}/"; }
|
| 136 |
+
for entry in "${SNAPSHOT_FILES[@]}"; do
|
| 137 |
+
cp -RL "${SNAPSHOT_SRC}/${entry}" "${PREFIX}/anifilebert/" 2>/dev/null || true
|
| 138 |
+
done
|
| 139 |
+
else
|
| 140 |
+
# Download each needed file to a staging dir, verify sha256 against the
|
| 141 |
+
# manifest, then move into place. Nothing below $PREFIX/artifact or
|
| 142 |
+
# $PREFIX/anifilebert changes until every hash checks out.
|
| 143 |
+
stage="${PREFIX}/.download/${ARTIFACT_NAME}"
|
| 144 |
+
mkdir -p "${stage}/model" "${stage}/snapshot"
|
| 145 |
+
while read -r f sha; do
|
| 146 |
+
[[ "$f" == "model.int8.pt" || "$f" == "config.json" ]] || continue
|
| 147 |
+
dest="${stage}/model/${f}"
|
| 148 |
+
installed="${PREFIX}/artifact/${ARTIFACT_NAME}/${f}"
|
| 149 |
+
if [[ -f "$dest" && "$(sha_of "$dest")" == "$sha" ]] \
|
| 150 |
+
|| { [[ -f "$installed" && "$(sha_of "$installed")" == "$sha" ]] \
|
| 151 |
+
&& cp -f "$installed" "$dest"; }; then
|
| 152 |
+
echo "already have ${f}"; continue
|
| 153 |
+
fi
|
| 154 |
+
echo "fetching models/${ARTIFACT_NAME}/${f}"
|
| 155 |
+
hf_fetch "models/${ARTIFACT_NAME}/${f}" "$dest" || die "download failed: $f"
|
| 156 |
+
[[ "$(sha_of "$dest")" == "$sha" ]] || die "hash mismatch after download: $f"
|
| 157 |
+
done < <(read_manifest "$MANIFEST_TMP" \
|
| 158 |
+
"'\n'.join(f'{k} {v}' for k,v in m['models']['${ARTIFACT_NAME}']['files'].items())")
|
| 159 |
+
# snapshot files the service needs: the anifilebert/ package plus the
|
| 160 |
+
# top-level files listed in SNAPSHOT_FILES (dapt.json tolerated if absent).
|
| 161 |
+
needed="$(printf '%s\n' "${SNAPSHOT_FILES[@]}" | grep -v '^anifilebert$')"
|
| 162 |
+
while read -r f sha; do
|
| 163 |
+
[[ -n "$f" ]] || continue
|
| 164 |
+
dest="${stage}/snapshot/${f}"
|
| 165 |
+
mkdir -p "$(dirname "$dest")"
|
| 166 |
+
installed="${PREFIX}/anifilebert/${f}"
|
| 167 |
+
if [[ -f "$dest" && "$(sha_of "$dest")" == "$sha" ]] \
|
| 168 |
+
|| { [[ -f "$installed" && "$(sha_of "$installed")" == "$sha" ]] \
|
| 169 |
+
&& cp -f "$installed" "$dest"; }; then
|
| 170 |
+
echo "already have snapshot/${f}"; continue
|
| 171 |
+
fi
|
| 172 |
+
echo "fetching snapshots/${SNAP_NAME}/${f}"
|
| 173 |
+
if hf_fetch "snapshots/${SNAP_NAME}/${f}" "$dest" && [[ "$(sha_of "$dest")" == "$sha" ]]; then
|
| 174 |
+
continue
|
| 175 |
+
fi
|
| 176 |
+
if [[ "$f" == "dapt.json" ]]; then
|
| 177 |
+
echo "warning: ${f} unavailable — skipping (provenance only)"
|
| 178 |
+
rm -f "$dest"; continue
|
| 179 |
+
fi
|
| 180 |
+
die "snapshot file ${f} failed to fetch or verify"
|
| 181 |
+
done < <(python3 - "$MANIFEST_TMP" "$SNAP_NAME" $needed <<'PY'
|
| 182 |
+
import json, sys
|
| 183 |
+
m = json.load(open(sys.argv[1]))
|
| 184 |
+
snap = m["snapshots"][sys.argv[2]]
|
| 185 |
+
want = set(sys.argv[3:])
|
| 186 |
+
for rel, sha in sorted(snap["sha256"].items()):
|
| 187 |
+
if rel in want or rel.startswith("anifilebert/"):
|
| 188 |
+
print(rel, sha)
|
| 189 |
+
PY
|
| 190 |
+
)
|
| 191 |
+
mkdir -p "${PREFIX}/artifact/${ARTIFACT_NAME}"
|
| 192 |
+
cp -f "${stage}/model/model.int8.pt" "${stage}/model/config.json" \
|
| 193 |
+
"${PREFIX}/artifact/${ARTIFACT_NAME}/"
|
| 194 |
+
cp -Rf "${stage}/snapshot/." "${PREFIX}/anifilebert/"
|
| 195 |
+
fi
|
| 196 |
+
|
| 197 |
+
# Record the manifest used for this install (offline verify + update checks).
|
| 198 |
+
if [[ -n "${MANIFEST_TMP}" ]]; then
|
| 199 |
+
python3 - "$MANIFEST_TMP" "$MANIFEST_LOCAL" "$ARTIFACT_NAME" <<'PY'
|
| 200 |
+
import json, sys
|
| 201 |
+
m = json.load(open(sys.argv[1])); m["installed_model"] = sys.argv[3]
|
| 202 |
+
json.dump(m, open(sys.argv[2], "w"), indent=2)
|
| 203 |
+
PY
|
| 204 |
+
fi
|
| 205 |
|
| 206 |
step "recording hashes"
|
| 207 |
{
|
|
|
|
| 230 |
fi
|
| 231 |
"${PREFIX}/venv/bin/python" -c "import torch, transformers; print('torch', torch.__version__, '| transformers', transformers.__version__)"
|
| 232 |
|
| 233 |
+
step "smoke test before touching the service"
|
| 234 |
+
"${PREFIX}/venv/bin/python" "${PREFIX}/app/serve.py" --check \
|
| 235 |
+
"Example.Show.S01E02.1080p-GROUP.mkv" \
|
| 236 |
+
--artifact "${PREFIX}/artifact/${ARTIFACT_NAME}" \
|
| 237 |
+
--snapshot "${PREFIX}/anifilebert" >/dev/null \
|
| 238 |
+
|| die "new artifact failed the smoke parse — the running service was left alone"
|
| 239 |
+
|
| 240 |
step "systemd user unit"
|
| 241 |
mkdir -p "${UNIT_DIR}"
|
| 242 |
unit_tmp="$(mktemp)"
|
|
|
|
| 261 |
echo "wrote ${UNIT_DIR}/${UNIT}"
|
| 262 |
elif diff -q "$unit_tmp" "${UNIT_DIR}/${UNIT}" >/dev/null; then
|
| 263 |
echo "unit already up to date"
|
|
|
|
|
|
|
|
|
|
| 264 |
else
|
| 265 |
+
# A model bump changes ExecStart; keep the old unit as .bak for inspection.
|
| 266 |
+
cp -f "${UNIT_DIR}/${UNIT}" "${UNIT_DIR}/${UNIT}.bak"
|
| 267 |
+
install -m 0644 "$unit_tmp" "${UNIT_DIR}/${UNIT}"
|
| 268 |
+
echo "unit updated (old copy: ${UNIT}.bak)"
|
| 269 |
fi
|
| 270 |
rm -f "$unit_tmp"
|
| 271 |
|
| 272 |
if [[ "$START" == 1 ]] && command -v systemctl >/dev/null 2>&1; then
|
| 273 |
systemctl --user daemon-reload
|
| 274 |
systemctl --user enable --now "$UNIT" || true
|
| 275 |
+
systemctl --user restart "$UNIT" || true
|
| 276 |
if command -v curl >/dev/null 2>&1; then
|
| 277 |
ok=""
|
| 278 |
for _ in $(seq 1 20); do
|
|
|
|
| 292 |
step "done"
|
| 293 |
cat <<SUMMARY
|
| 294 |
prefix ${PREFIX}
|
| 295 |
+
artifact ${ARTIFACT_NAME}
|
| 296 |
+
source $([[ "$LOCAL" == 1 ]] && echo "local artifacts/" || echo "${HF_REPO} (manifest)")
|
| 297 |
unit ${UNIT_DIR}/${UNIT}
|
| 298 |
native URL http://127.0.0.1:${PORT}
|
| 299 |
docker URL http://172.17.0.1:${PORT} (or host.docker.internal with host-gateway)
|
| 300 |
docker UDS unix:///data/parser.sock (socket at ${SOCKET})
|
| 301 |
+
update bash install.sh (re-run: installs the channel default)
|
| 302 |
+
rollback bash install.sh --model <previous-name>
|
| 303 |
verify bash install.sh --verify
|
| 304 |
SUMMARY
|