Version 1 checkpoint

This commit is contained in:
Codex
2026-03-31 18:35:14 -06:00
parent d860318d43
commit a50172594b
14 changed files with 5007 additions and 137 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ For non-Ubuntu WSL distros, install the CUDA toolkit manually before running the
- The host-side install path assumes modern local tooling, but TransformerLab itself is provisioned from a pinned classic single-user layout.
- TransformerLab is intentionally pinned to the older single-user `v0.28.2` release because newer upstream releases changed the project structure and behavior in ways that break this courseware.
- This project does not rely on TransformerLab's upstream `install.sh`; the Ansible role provisions the pinned release directly so web assets, env layout, and runtime behavior stay reproducible.
- The scripts do not patch TransformerLab plugins or preserve the VM's special-case fixes.
- The courseware repairs installed TransformerLab Fastchat plugin manifests so Fastchat-gated features such as Model Architecture and Visualize Logprobs stay available on pinned installs.
- No Ollama models are pulled during `./labctl up`; students pull models manually as part of the courseware.
- WhiteRabbitNeo GGUFs are no longer pulled during `./labctl up`. After base setup, run `state/lab2/download_whiterabbitneo-gguf.sh` to fetch only the `BF16`, `Q8_0`, `Q4_K_M`, and `Q2_K` files from `bartowski/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF` and register local Ollama models `WhiteRabbitNeo`, `WhiteRabbitNeo-BF16`, `WhiteRabbitNeo-Q8`, `WhiteRabbitNeo-Q4`, and `WhiteRabbitNeo-Q2`.
- TransformerLab and Unsloth homes are redirected into this project's `state/` tree via symlinks.
+1 -1
View File
@@ -1,5 +1,5 @@
[defaults]
inventory = inventory/localhost.yml
inventory = inventory
roles_path = roles
host_key_checking = False
interpreter_python = auto_silent
+25 -1
View File
@@ -11,7 +11,9 @@ courseware_apps_dir: "{{ courseware_state_dir }}/apps"
courseware_downloads_dir: "{{ courseware_state_dir }}/downloads"
courseware_lab2_dir: "{{ courseware_state_dir }}/lab2"
courseware_lab6_dir: "{{ courseware_state_dir }}/lab6"
courseware_transformerlab_home: "{{ courseware_state_dir }}/transformerlab-home"
courseware_transformerlab_legacy_home: "{{ courseware_state_dir }}/transformerlab-home"
courseware_safe_homes_dir: "{{ lookup('env', 'HOME') }}/.local/share/local-lab-deployment"
courseware_transformerlab_home: "{{ (courseware_safe_homes_dir ~ '/transformerlab-home') if ' ' in courseware_root else courseware_transformerlab_legacy_home }}"
courseware_unsloth_home: "{{ courseware_state_dir }}/unsloth-home"
courseware_ollama_models_dir: "{{ courseware_models_dir }}/ollama"
courseware_node_runtime_dir: "{{ courseware_tools_dir }}/node-runtime"
@@ -32,8 +34,30 @@ courseware_ports:
promptfoo: 15500
wiki: 80
courseware_transformerlab_install_mode: "single-user-pinned"
courseware_transformerlab_version: "v0.28.2"
courseware_transformerlab_version_dir: "{{ courseware_transformerlab_version | regex_replace('^v', '') }}"
courseware_transformerlab_source_archive: "{{ courseware_downloads_dir }}/transformerlab-app-{{ courseware_transformerlab_version_dir }}.tar.gz"
courseware_transformerlab_web_archive: "{{ courseware_downloads_dir }}/transformerlab-web-{{ courseware_transformerlab_version_dir }}.tar.gz"
courseware_transformerlab_miniforge_installer: "{{ courseware_downloads_dir }}/transformerlab-miniforge-installer.sh"
courseware_transformerlab_default_user_email: "student@zuccaro.me"
courseware_transformerlab_default_user_password: "student"
courseware_transformerlab_default_user_first_name: "Student"
courseware_transformerlab_default_user_last_name: ""
courseware_transformerlab_required_loader_plugins:
- "fastchat_server"
courseware_transformerlab_required_supports_fastchat:
- "chat"
- "completion"
- "visualize_model"
- "model_layers"
- "rag"
- "tools"
- "template"
- "embeddings"
- "tokenize"
- "logprobs"
- "batched"
courseware_llama_cpp_commit: "51fa458a92d6a3f305f8fd76fc8f702e3e87ddb5"
courseware_chunkviz_commit: "a891eacafda1f28a12373ad3b00102e68f07c57f"
courseware_promptfoo_version: "0.119.0"
+13
View File
@@ -192,6 +192,19 @@
or courseware_down_transformerlab_marker.stat.exists
failed_when: false
- name: Remove managed TransformerLab home
file:
path: "{{ courseware_transformerlab_home }}"
state: absent
failed_when: false
- name: Remove legacy managed TransformerLab home
file:
path: "{{ courseware_transformerlab_legacy_home }}"
state: absent
when: courseware_transformerlab_legacy_home != courseware_transformerlab_home
failed_when: false
- name: Remove managed Unsloth path
file:
path: "{{ ansible_env.HOME }}/.unsloth"
+1
View File
@@ -16,6 +16,7 @@
- "{{ courseware_apps_dir }}"
- "{{ courseware_downloads_dir }}"
- "{{ courseware_lab2_dir }}"
- "{{ courseware_safe_homes_dir }}"
- "{{ courseware_transformerlab_home }}"
- "{{ courseware_unsloth_home }}"
- "{{ courseware_ollama_models_dir }}"
+2 -2
View File
@@ -7,7 +7,7 @@
- name: Create Open WebUI virtual environment
command:
argv:
- "{{ courseware_python_bin }}"
- "{{ courseware_transformerlab_home }}/envs/transformerlab/bin/python"
- -m
- venv
- "{{ courseware_venvs_dir }}/open-webui"
@@ -39,7 +39,7 @@
- name: Create Embedding Atlas virtual environment
command:
argv:
- "{{ courseware_python_bin }}"
- "{{ courseware_transformerlab_home }}/envs/transformerlab/bin/python"
- -m
- venv
- "{{ courseware_venvs_dir }}/embedding-atlas"
+277 -126
View File
@@ -1,139 +1,290 @@
- name: Bootstrap TransformerLab release files
shell: |
set -euo pipefail
cd "{{ courseware_transformerlab_home }}"
curl -L "https://github.com/transformerlab/transformerlab-app/archive/refs/tags/{{ courseware_transformerlab_version }}.tar.gz" -o transformerlab.tar.gz
tar -xzf transformerlab.tar.gz
rm -f transformerlab.tar.gz
rm -rf src
mv "transformerlab-app-{{ courseware_transformerlab_version_dir }}/api" src
echo "{{ courseware_transformerlab_version }}" > src/LATEST_VERSION
curl -L "https://github.com/transformerlab/transformerlab-app/releases/download/{{ courseware_transformerlab_version }}/transformerlab_web.tar.gz" -o transformerlab_web.tar.gz
rm -rf webapp
mkdir -p webapp
tar -xzf transformerlab_web.tar.gz -C webapp
rm -f transformerlab_web.tar.gz
args:
executable: /bin/bash
creates: "{{ courseware_transformerlab_home }}/src/install.sh"
- name: Require pinned single-user TransformerLab install mode
assert:
that:
- courseware_transformerlab_install_mode == "single-user-pinned"
fail_msg: "This courseware only supports the pinned single-user TransformerLab install mode."
- name: Add TransformerLab Miniforge Python path for space-safe bootstrap
replace:
path: "{{ courseware_transformerlab_home }}/src/install.sh"
regexp: 'CONDA_BIN=\$\{MINIFORGE_ROOT\}/bin/conda\n'
replace: |
CONDA_BIN=${MINIFORGE_ROOT}/bin/conda
CONDA_PYTHON_BIN=${MINIFORGE_ROOT}/bin/python
when: "' ' in courseware_transformerlab_home"
- name: Inject space-safe TransformerLab conda runner
blockinfile:
path: "{{ courseware_transformerlab_home }}/src/install.sh"
insertbefore: '^check_conda\(\) \{$'
marker: '# {mark} courseware conda runner'
block: |
conda_direct_exec_works() {
"${CONDA_BIN}" --version >/dev/null 2>&1
}
run_conda() {
if conda_direct_exec_works; then
"${CONDA_BIN}" "$@"
else
"${CONDA_PYTHON_BIN}" "${CONDA_BIN}" "$@"
fi
}
when: "' ' in courseware_transformerlab_home"
- name: Rewrite TransformerLab installer to use the space-safe conda runner
replace:
path: "{{ courseware_transformerlab_home }}/src/install.sh"
regexp: 'eval "\$\(\$\{CONDA_BIN\} shell\.bash hook\)"'
replace: 'eval "$(run_conda shell.bash hook)"'
when: "' ' in courseware_transformerlab_home"
- name: Rewrite TransformerLab doctor output to use the space-safe conda runner
replace:
path: "{{ courseware_transformerlab_home }}/src/install.sh"
regexp: '\$\(\$\{CONDA_BIN\} --version\)'
replace: '$(run_conda --version)'
when: "' ' in courseware_transformerlab_home"
- name: Install TransformerLab
shell: |
set -euo pipefail
./src/install.sh 2>&1 | tee "{{ courseware_logs_dir }}/transformerlab_install.log"
touch "{{ courseware_transformerlab_home }}/.courseware-managed"
args:
executable: /bin/bash
chdir: "{{ courseware_transformerlab_home }}"
creates: "{{ courseware_transformerlab_home }}/miniforge3/bin/conda"
- name: Rewrite TransformerLab Miniforge entrypoints to a space-safe shebang path
shell: |
set -euo pipefail
actual_prefix="{{ courseware_transformerlab_home }}/miniforge3/bin/"
safe_prefix="{{ ansible_env.HOME }}/.transformerlab/miniforge3/bin/"
find "{{ courseware_transformerlab_home }}/miniforge3/bin" -maxdepth 1 -type f -print0 |
while IFS= read -r -d '' file; do
first_line=$(head -n 1 "$file" || true)
case "$first_line" in
"#!${actual_prefix}"*)
suffix=${first_line#\#!}
suffix=${suffix#"${actual_prefix}"}
replacement="#!${safe_prefix}${suffix}"
tmp_file=$(mktemp)
{
printf '%s\n' "$replacement"
tail -n +2 "$file"
} >"$tmp_file"
chmod --reference="$file" "$tmp_file"
mv "$tmp_file" "$file"
;;
esac
done
args:
executable: /bin/bash
when: "' ' in courseware_transformerlab_home"
- name: Install TransformerLab multiuser dependencies
shell: |
set -euo pipefail
./src/install.sh multiuser_setup 2>&1 | tee "{{ courseware_logs_dir }}/transformerlab_multiuser_setup.log"
touch "{{ courseware_transformerlab_home }}/.courseware-managed"
args:
executable: /bin/bash
chdir: "{{ courseware_transformerlab_home }}"
creates: "{{ courseware_transformerlab_home }}/envs/general-uv/bin/python"
- name: Check TransformerLab general uv environment
- name: Check pinned TransformerLab source tree
stat:
path: "{{ courseware_transformerlab_home }}/envs/general-uv/bin/python"
register: courseware_transformerlab_general_uv
path: "{{ courseware_transformerlab_home }}/src/install.sh"
register: courseware_transformerlab_src_install
- name: Retry TransformerLab multiuser setup after source refresh
- name: Check pinned TransformerLab version file
stat:
path: "{{ courseware_transformerlab_home }}/src/LATEST_VERSION"
register: courseware_transformerlab_src_version
- name: Check pinned TransformerLab web app
stat:
path: "{{ courseware_transformerlab_home }}/webapp/index.html"
register: courseware_transformerlab_web_index
- name: Check pinned TransformerLab conda runtime
stat:
path: "{{ courseware_transformerlab_home }}/miniforge3/bin/conda"
register: courseware_transformerlab_conda
- name: Check pinned TransformerLab Python runtime
stat:
path: "{{ courseware_transformerlab_home }}/envs/transformerlab/bin/python"
register: courseware_transformerlab_env_python
- name: Check pinned TransformerLab install marker
stat:
path: "{{ courseware_transformerlab_home }}/.install_complete"
register: courseware_transformerlab_install_marker
- name: Read pinned TransformerLab version file
slurp:
src: "{{ courseware_transformerlab_home }}/src/LATEST_VERSION"
register: courseware_transformerlab_version_contents
when: courseware_transformerlab_src_version.stat.exists
- name: Determine pinned TransformerLab drift state
set_fact:
courseware_transformerlab_installed_version: "{{ (courseware_transformerlab_version_contents.content | b64decode | trim) if courseware_transformerlab_src_version.stat.exists else '' }}"
courseware_transformerlab_refresh_required: >-
{{
(not courseware_transformerlab_src_install.stat.exists) or
(not courseware_transformerlab_src_version.stat.exists) or
(not courseware_transformerlab_web_index.stat.exists) or
((courseware_transformerlab_version_contents.content | b64decode | trim) != courseware_transformerlab_version)
}}
courseware_transformerlab_env_refresh_required: >-
{{
(not courseware_transformerlab_conda.stat.exists) or
(not courseware_transformerlab_env_python.stat.exists) or
(not courseware_transformerlab_install_marker.stat.exists)
}}
- name: Download pinned TransformerLab source archive
get_url:
url: "https://github.com/transformerlab/transformerlab-app/archive/refs/tags/{{ courseware_transformerlab_version }}.tar.gz"
dest: "{{ courseware_transformerlab_source_archive }}"
mode: "0644"
when: courseware_transformerlab_refresh_required
- name: Download pinned TransformerLab web archive
get_url:
url: "https://github.com/transformerlab/transformerlab-app/releases/download/{{ courseware_transformerlab_version }}/transformerlab_web.tar.gz"
dest: "{{ courseware_transformerlab_web_archive }}"
mode: "0644"
when: courseware_transformerlab_refresh_required
- name: Refresh pinned TransformerLab release layout
shell: |
set -euo pipefail
./src/install.sh multiuser_setup 2>&1 | tee "{{ courseware_logs_dir }}/transformerlab_multiuser_setup_retry.log"
home_dir="{{ courseware_transformerlab_home }}"
source_archive="{{ courseware_transformerlab_source_archive }}"
web_archive="{{ courseware_transformerlab_web_archive }}"
version="{{ courseware_transformerlab_version }}"
version_dir="{{ courseware_transformerlab_version_dir }}"
extract_dir="${home_dir}/transformerlab-app-${version_dir}"
rm -rf "${extract_dir}"
tar -xzf "${source_archive}" -C "${home_dir}"
if [ ! -d "${extract_dir}/api" ]; then
echo "Pinned TransformerLab source archive did not contain api/." >&2
exit 1
fi
rm -rf "${home_dir}/src" "${home_dir}/lab-sdk" "${home_dir}/webapp"
mv "${extract_dir}/api" "${home_dir}/src"
if [ -d "${extract_dir}/lab-sdk" ]; then
mv "${extract_dir}/lab-sdk" "${home_dir}/lab-sdk"
fi
printf '%s\n' "${version}" > "${home_dir}/src/LATEST_VERSION"
mkdir -p "${home_dir}/webapp"
tar -xzf "${web_archive}" -C "${home_dir}/webapp"
if [ -d "${home_dir}/webapp/transformerlab_web" ]; then
shopt -s dotglob nullglob
mv "${home_dir}/webapp/transformerlab_web/"* "${home_dir}/webapp/"
rmdir "${home_dir}/webapp/transformerlab_web"
fi
rm -rf "${extract_dir}"
rm -f "${home_dir}/.install_complete"
args:
executable: /bin/bash
chdir: "{{ courseware_transformerlab_home }}"
when: not courseware_transformerlab_general_uv.stat.exists
when: courseware_transformerlab_refresh_required
- name: Recheck TransformerLab general uv environment
stat:
path: "{{ courseware_transformerlab_home }}/envs/general-uv/bin/python"
register: courseware_transformerlab_general_uv
- name: Ensure pinned TransformerLab environment file exists
file:
path: "{{ courseware_transformerlab_home }}/.env"
state: touch
mode: "0600"
- name: Mark TransformerLab multiuser setup complete
- name: Ensure pinned TransformerLab JWT secrets exist
shell: |
set -euo pipefail
env_file="{{ courseware_transformerlab_home }}/.env"
if ! grep -q '^TRANSFORMERLAB_JWT_SECRET=' "$env_file"; then
printf 'TRANSFORMERLAB_JWT_SECRET=%s\n' "$(od -An -N 64 -tx1 /dev/urandom | tr -d ' \n')" >> "$env_file"
fi
if ! grep -q '^TRANSFORMERLAB_REFRESH_SECRET=' "$env_file"; then
printf 'TRANSFORMERLAB_REFRESH_SECRET=%s\n' "$(od -An -N 64 -tx1 /dev/urandom | tr -d ' \n')" >> "$env_file"
fi
args:
executable: /bin/bash
- name: Remove stale TransformerLab general-uv environment
file:
path: "{{ courseware_transformerlab_home }}/envs/general-uv"
state: absent
- name: Remove stale TransformerLab multiuser marker
file:
path: "{{ courseware_transformerlab_home }}/.multiuser_setup_complete"
state: touch
mode: "0644"
when: courseware_transformerlab_general_uv.stat.exists
state: absent
- name: Fail if TransformerLab general uv environment is missing
- name: Determine Miniforge platform suffix
set_fact:
courseware_transformerlab_miniforge_platform: "{{ 'Linux-x86_64' if ansible_system == 'Linux' and ansible_architecture == 'x86_64' else 'Linux-aarch64' if ansible_system == 'Linux' and ansible_architecture in ['aarch64', 'arm64'] else 'MacOSX-arm64' if ansible_system == 'Darwin' and ansible_architecture == 'arm64' else 'MacOSX-x86_64' if ansible_system == 'Darwin' and ansible_architecture == 'x86_64' else 'unsupported' }}"
- name: Fail for unsupported Miniforge platform
fail:
msg: "TransformerLab multiuser setup completed without creating {{ courseware_transformerlab_home }}/envs/general-uv/bin/python."
when: not courseware_transformerlab_general_uv.stat.exists
msg: "Unsupported TransformerLab Miniforge platform: {{ ansible_system }} {{ ansible_architecture }}"
when: courseware_transformerlab_miniforge_platform == "unsupported"
- name: Download Miniforge installer for pinned TransformerLab
get_url:
url: "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-{{ courseware_transformerlab_miniforge_platform }}.sh"
dest: "{{ courseware_transformerlab_miniforge_installer }}"
mode: "0755"
when: not courseware_transformerlab_conda.stat.exists
- name: Install Miniforge for pinned TransformerLab
shell: |
set -euo pipefail
rm -rf "{{ courseware_transformerlab_home }}/miniforge3"
bash "{{ courseware_transformerlab_miniforge_installer }}" -b -p "{{ courseware_transformerlab_home }}/miniforge3"
args:
executable: /bin/bash
when: not courseware_transformerlab_conda.stat.exists
- name: Create pinned TransformerLab environment
shell: |
set -euo pipefail
"{{ courseware_transformerlab_home }}/miniforge3/bin/conda" create -y -k --prefix "{{ courseware_transformerlab_home }}/envs/transformerlab" python=3.11
args:
executable: /bin/bash
when: not courseware_transformerlab_env_python.stat.exists
- name: Detect pinned TransformerLab accelerator profile
shell: |
set -euo pipefail
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi --query-gpu=name --format=csv,noheader,nounits >/dev/null 2>&1; then
printf '%s\n' nvidia
elif command -v rocminfo >/dev/null 2>&1; then
printf '%s\n' rocm
else
printf '%s\n' cpu
fi
args:
executable: /bin/bash
register: courseware_transformerlab_accelerator
changed_when: false
- name: Install pinned TransformerLab Python dependencies
shell: |
set -euo pipefail
env_python="{{ courseware_transformerlab_home }}/envs/transformerlab/bin/python"
conda_bin="{{ courseware_transformerlab_home }}/miniforge3/bin/conda"
accelerator="{{ courseware_transformerlab_accelerator.stdout | trim }}"
project_dir="{{ courseware_transformerlab_home }}/src"
wheel_args=()
extra="cpu"
"$env_python" -m pip install --upgrade pip
"$env_python" -m pip install --upgrade uv
if [ "$accelerator" = "nvidia" ]; then
"$conda_bin" install -y --prefix "{{ courseware_transformerlab_home }}/envs/transformerlab" cuda==12.8.1 --force-reinstall -c nvidia/label/cuda-12.8.1
extra="nvidia"
elif [ "$accelerator" = "rocm" ]; then
wheel_args+=(--index https://download.pytorch.org/whl/rocm6.4 --index-strategy unsafe-best-match)
extra="rocm"
elif [ "{{ ansible_system }}" != "Darwin" ]; then
wheel_args+=(--index https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match)
fi
cd "$project_dir"
"$env_python" -m uv pip install --python "$env_python" "${wheel_args[@]}" ".[${extra}]"
if [ -d "{{ courseware_transformerlab_home }}/lab-sdk" ]; then
"$env_python" -m uv pip install --python "$env_python" "{{ courseware_transformerlab_home }}/lab-sdk"
fi
"$env_python" -c "import uvicorn"
touch "{{ courseware_transformerlab_home }}/.install_complete"
args:
executable: /bin/bash
when: courseware_transformerlab_refresh_required or courseware_transformerlab_env_refresh_required
- name: Install TransformerLab plugin repair helper
copy:
src: "{{ playbook_dir }}/../../scripts/repair_transformerlab_plugin_supports.py"
dest: "{{ courseware_state_dir }}/repair_transformerlab_plugin_supports.py"
mode: "0755"
- name: Install TransformerLab default-user helper
copy:
src: "{{ playbook_dir }}/../../scripts/ensure_transformerlab_user.py"
dest: "{{ courseware_state_dir }}/ensure_transformerlab_user.py"
mode: "0755"
- name: Repair installed Fastchat plugin supports
command:
argv:
- python3
- "{{ courseware_state_dir }}/repair_transformerlab_plugin_supports.py"
- --transformerlab-dir
- "{{ courseware_transformerlab_home }}"
- --plugin
- fastchat_server
- --required-support
- chat
- --required-support
- completion
- --required-support
- visualize_model
- --required-support
- model_layers
- --required-support
- rag
- --required-support
- tools
- --required-support
- template
- --required-support
- embeddings
- --required-support
- tokenize
- --required-support
- logprobs
- --required-support
- batched
changed_when: false
- name: Ensure pinned TransformerLab default user when database already exists
command:
argv:
- "{{ courseware_transformerlab_home }}/envs/transformerlab/bin/python"
- "{{ courseware_state_dir }}/ensure_transformerlab_user.py"
- --transformerlab-dir
- "{{ courseware_transformerlab_home }}"
- --email
- "{{ courseware_transformerlab_default_user_email }}"
- --password
- "{{ courseware_transformerlab_default_user_password }}"
- --first-name
- "{{ courseware_transformerlab_default_user_first_name }}"
- --last-name
- "{{ courseware_transformerlab_default_user_last_name }}"
changed_when: false
+4
View File
@@ -20,6 +20,10 @@ EMBEDDING_ATLAS_VENV="{{ courseware_venvs_dir }}/embedding-atlas"
TTPS_DATASET_PATH="{{ courseware_datasets_dir }}/ttps_dataset.parquet"
WIKI_TEST_RAW_PATH="{{ courseware_datasets_dir }}/wiki.test.raw"
TRANSFORMERLAB_DIR="{{ courseware_transformerlab_home }}"
TRANSFORMERLAB_DEFAULT_USER_EMAIL="{{ courseware_transformerlab_default_user_email }}"
TRANSFORMERLAB_DEFAULT_USER_PASSWORD="{{ courseware_transformerlab_default_user_password }}"
TRANSFORMERLAB_DEFAULT_USER_FIRST_NAME="{{ courseware_transformerlab_default_user_first_name }}"
TRANSFORMERLAB_DEFAULT_USER_LAST_NAME="{{ courseware_transformerlab_default_user_last_name }}"
UNSLOTH_BIN="{{ ansible_env.HOME }}/.local/bin/unsloth"
PROMPTFOO_DIR="{{ courseware_promptfoo_dir }}"
PROMPTFOO_BIN="{{ courseware_tools_dir }}/promptfoo/node_modules/.bin/promptfoo"
File diff suppressed because it is too large Load Diff
Binary file not shown.
+8 -1
View File
@@ -21,6 +21,10 @@ load_runtime_env() {
: "${PROMPTFOO_DIR:=$COURSEWARE_STATE_DIR/lab6}"
: "${WIKI_DIR:=$COURSEWARE_STATE_DIR/repos/LLM-Labs}"
: "${LLAMA_CPP_BIN_DIR:=$COURSEWARE_STATE_DIR/repos/llama.cpp/build/bin}"
if [ -n "${OLLAMA_BIN:-}" ] && [[ "$OLLAMA_BIN" != */* ]] && command -v "$OLLAMA_BIN" >/dev/null 2>&1; then
OLLAMA_BIN=$(command -v "$OLLAMA_BIN")
fi
}
ensure_runtime_env() {
@@ -97,7 +101,10 @@ service_command() {
"$COURSEWARE_OPEN_WEBUI_PORT"
;;
transformerlab)
printf 'cd "%s/src" && exec ./run.sh -h %s -p %s' \
printf 'export PATH="%s/envs/transformerlab/bin:$PATH"; export VIRTUAL_ENV="%s/envs/transformerlab"; export CONDA_PREFIX="%s/envs/transformerlab"; cd "%s/src" && exec ./run.sh -c -h %s -p %s' \
"$TRANSFORMERLAB_DIR" \
"$TRANSFORMERLAB_DIR" \
"$TRANSFORMERLAB_DIR" \
"$TRANSFORMERLAB_DIR" \
"$COURSEWARE_BIND_HOST" \
"$COURSEWARE_TRANSFORMERLAB_PORT"
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import asyncio
import os
import sys
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Ensure a known verified TransformerLab user exists for single-user courseware installs."
)
parser.add_argument("--transformerlab-dir", required=True, help="Path to the managed TransformerLab home directory.")
parser.add_argument("--email", required=True, help="Email address for the default user.")
parser.add_argument("--password", required=True, help="Password for the default user.")
parser.add_argument("--first-name", default="Student", help="Optional first name for the default user.")
parser.add_argument("--last-name", default="", help="Optional last name for the default user.")
return parser.parse_args()
def bootstrap_source(transformerlab_dir: Path) -> None:
source_dir = transformerlab_dir / "src"
if not source_dir.is_dir():
raise SystemExit(f"TransformerLab source directory not found: {source_dir}")
sys.path.insert(0, str(source_dir))
env_file = transformerlab_dir / ".env"
if env_file.exists():
for line in env_file.read_text().splitlines():
if not line or line.lstrip().startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
async def ensure_user(args: argparse.Namespace) -> int:
from sqlalchemy import select
from transformerlab.db.constants import DATABASE_FILE_NAME
from transformerlab.shared.models.models import OAuthAccount, TeamRole, User, UserTeam
from transformerlab.shared.models.user_model import (
AsyncSessionLocal,
SQLAlchemyUserDatabaseWithOAuth,
create_personal_team,
)
from transformerlab.models.users import UserCreate, UserManager
database_path = Path(DATABASE_FILE_NAME)
if not database_path.exists():
print(f"TransformerLab database is not ready yet at {database_path}; skipping default-user sync.")
return 0
async with AsyncSessionLocal() as session:
user_db = SQLAlchemyUserDatabaseWithOAuth(session, User, OAuthAccount)
user_manager = UserManager(user_db)
stmt = select(User).where(User.email == args.email)
result = await session.execute(stmt)
user = result.unique().scalar_one_or_none()
created = False
changed = False
if user is None:
user = await user_manager.create(
UserCreate(
email=args.email,
password=args.password,
is_active=True,
is_superuser=True,
is_verified=True,
first_name=args.first_name or None,
last_name=args.last_name or None,
),
safe=False,
request=None,
)
created = True
changed = True
else:
verified, new_hash = user_manager.password_helper.verify_and_update(args.password, user.hashed_password)
if not verified:
user.hashed_password = user_manager.password_helper.hash(args.password)
changed = True
elif new_hash:
user.hashed_password = new_hash
changed = True
if not user.is_active:
user.is_active = True
changed = True
if not user.is_verified:
user.is_verified = True
changed = True
if not user.is_superuser:
user.is_superuser = True
changed = True
if args.first_name and user.first_name != args.first_name:
user.first_name = args.first_name
changed = True
desired_last_name = args.last_name or None
if user.last_name != desired_last_name:
user.last_name = desired_last_name
changed = True
if changed:
session.add(user)
await session.commit()
await session.refresh(user)
team_stmt = select(UserTeam).where(UserTeam.user_id == str(user.id))
team_result = await session.execute(team_stmt)
user_team = team_result.scalar_one_or_none()
if user_team is None:
personal_team = await create_personal_team(session, user)
user_team = UserTeam(user_id=str(user.id), team_id=personal_team.id, role=TeamRole.OWNER.value)
session.add(user_team)
await session.commit()
print(f"Created personal team '{personal_team.name}' for {args.email}.")
elif user_team.role != TeamRole.OWNER.value:
user_team.role = TeamRole.OWNER.value
session.add(user_team)
await session.commit()
print(f"Updated team role to owner for {args.email}.")
action = "Created" if created else "Verified"
print(f"{action} default TransformerLab user {args.email}.")
return 0
def main() -> int:
args = parse_args()
args.email = args.email.strip()
args.password = args.password.strip()
args.first_name = args.first_name.strip()
args.last_name = args.last_name.strip()
bootstrap_source(Path(args.transformerlab_dir))
return asyncio.run(ensure_user(args))
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Repair installed TransformerLab plugin manifests from the pinned source manifest."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
def load_json(path: Path) -> dict:
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)
def write_json(path: Path, payload: dict) -> None:
with path.open("w", encoding="utf-8") as handle:
json.dump(payload, handle, indent=2, sort_keys=False)
handle.write("\n")
def candidate_manifest_paths(root: Path, plugin: str) -> list[Path]:
candidates = []
patterns = [
f"workspace/plugins/{plugin}/index.json",
f"orgs/*/workspace/plugins/{plugin}/index.json",
f"orgs/*/plugins/{plugin}/index.json",
]
for pattern in patterns:
candidates.extend(root.glob(pattern))
unique = []
seen = set()
for path in candidates:
resolved = path.resolve()
if resolved in seen:
continue
seen.add(resolved)
unique.append(path)
return unique
def repair_manifest(path: Path, required_supports: list[str], source_supports: list[str]) -> bool:
payload = load_json(path)
existing = payload.get("supports", [])
if not isinstance(existing, list):
existing = []
desired = []
seen = set()
for item in source_supports:
if isinstance(item, str) and item not in seen:
desired.append(item)
seen.add(item)
for item in required_supports:
if item not in seen:
desired.append(item)
seen.add(item)
if existing == desired:
return False
payload["supports"] = desired
write_json(path, payload)
return True
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--transformerlab-dir", required=True)
parser.add_argument("--plugin", required=True)
parser.add_argument("--required-support", action="append", default=[])
args = parser.parse_args()
root = Path(args.transformerlab_dir).expanduser().resolve()
source_manifest = root / "src" / "transformerlab" / "plugins" / args.plugin / "index.json"
if not source_manifest.exists():
print(f"missing source plugin manifest: {source_manifest}", file=sys.stderr)
return 1
source_payload = load_json(source_manifest)
source_supports = source_payload.get("supports", [])
if not isinstance(source_supports, list):
print(f"invalid supports array in {source_manifest}", file=sys.stderr)
return 1
missing_required = [item for item in args.required_support if item not in source_supports]
if missing_required:
print(
f"source plugin manifest {source_manifest} is missing required supports: {', '.join(missing_required)}",
file=sys.stderr,
)
return 1
updated = 0
for manifest in candidate_manifest_paths(root, args.plugin):
if repair_manifest(manifest, args.required_support, source_supports):
updated += 1
print(f"repaired {manifest}")
if updated == 0:
print(f"no installed {args.plugin} manifests needed repair")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+62 -5
View File
@@ -9,6 +9,25 @@ load_runtime_env
mkdir -p "$STATE_DIR/run" "$STATE_DIR/logs"
ensure_transformerlab_default_user() {
local helper_python="${TRANSFORMERLAB_DIR}/envs/transformerlab/bin/python"
if [ ! -x "$helper_python" ]; then
return 0
fi
if [ -z "${TRANSFORMERLAB_DEFAULT_USER_EMAIL:-}" ] || [ -z "${TRANSFORMERLAB_DEFAULT_USER_PASSWORD:-}" ]; then
return 0
fi
"$helper_python" "$SCRIPT_DIR/ensure_transformerlab_user.py" \
--transformerlab-dir "$TRANSFORMERLAB_DIR" \
--email "$TRANSFORMERLAB_DEFAULT_USER_EMAIL" \
--password "$TRANSFORMERLAB_DEFAULT_USER_PASSWORD" \
--first-name "${TRANSFORMERLAB_DEFAULT_USER_FIRST_NAME:-Student}" \
--last-name "${TRANSFORMERLAB_DEFAULT_USER_LAST_NAME:-}" >>"$STATE_DIR/logs/transformerlab_default_user.log" 2>&1 || true
}
resolve_targets() {
if [ $# -eq 0 ]; then
echo "No target specified." >&2
@@ -57,10 +76,13 @@ service_ready() {
ollama)
curl -fsS "$(service_url "$service")/api/tags" >/dev/null 2>&1
;;
transformerlab)
curl -fsS "$(service_url "$service")/healthz" >/dev/null 2>&1
;;
promptfoo)
curl -fsS "$(service_url "$service")/health" >/dev/null 2>&1
;;
open-webui|transformerlab|chunkviz|embedding-atlas|unsloth|wiki)
open-webui|chunkviz|embedding-atlas|unsloth|wiki)
curl -fsS "$(service_url "$service")" >/dev/null 2>&1
;;
*)
@@ -75,13 +97,20 @@ start_one() {
local log_file
local pid_file
local attempt
local pid_grace_attempts=5
if has_live_pid "$service"; then
if [ "$service" = "transformerlab" ]; then
ensure_transformerlab_default_user
fi
echo "$service already running"
return 0
fi
if service_ready "$service"; then
if [ "$service" = "transformerlab" ]; then
ensure_transformerlab_default_user
fi
echo "$service already available"
return 0
fi
@@ -90,6 +119,24 @@ start_one() {
open-webui)
start_one ollama
;;
transformerlab)
if command -v python3 >/dev/null 2>&1; then
python3 "$SCRIPT_DIR/repair_transformerlab_plugin_supports.py" \
--transformerlab-dir "$TRANSFORMERLAB_DIR" \
--plugin "fastchat_server" \
--required-support "chat" \
--required-support "completion" \
--required-support "visualize_model" \
--required-support "model_layers" \
--required-support "rag" \
--required-support "tools" \
--required-support "template" \
--required-support "embeddings" \
--required-support "tokenize" \
--required-support "logprobs" \
--required-support "batched" >>"$STATE_DIR/logs/transformerlab_plugin_supports.log" 2>&1 || true
fi
;;
*)
;;
esac
@@ -98,7 +145,12 @@ start_one() {
log_file=$(service_log_file "$service")
pid_file=$(service_pid_file "$service")
if command -v setsid >/dev/null 2>&1; then
if [ "$service" = "ollama" ]; then
env \
OLLAMA_HOST="${COURSEWARE_BIND_HOST}:${COURSEWARE_OLLAMA_PORT}" \
OLLAMA_MODELS="$OLLAMA_MODELS_DIR" \
"$OLLAMA_BIN" serve </dev/null >>"$log_file" 2>&1 &
elif command -v setsid >/dev/null 2>&1; then
nohup setsid bash -lc "$cmd" </dev/null >>"$log_file" 2>&1 &
else
nohup bash -lc "$cmd" </dev/null >>"$log_file" 2>&1 &
@@ -107,14 +159,19 @@ start_one() {
for attempt in $(seq 1 60); do
if service_ready "$service"; then
if [ "$service" = "transformerlab" ]; then
ensure_transformerlab_default_user
fi
echo "started $service"
return 0
fi
if ! has_live_pid "$service"; then
rm -f "$pid_file"
echo "failed to start $service; check $log_file" >&2
exit 1
if [ "$attempt" -ge "$pid_grace_attempts" ]; then
rm -f "$pid_file"
echo "failed to start $service; check $log_file" >&2
exit 1
fi
fi
sleep 1