Add managed Lab 3 browser terminal deployment

This commit is contained in:
c4ch3c4d3
2026-04-13 16:40:14 -06:00
parent 30b919c0b9
commit cbf6c3fad3
12 changed files with 361 additions and 2 deletions
+19
View File
@@ -103,6 +103,25 @@ Default endpoints:
- Unsloth Studio: `http://127.0.0.1:8888` - Unsloth Studio: `http://127.0.0.1:8888`
- Promptfoo UI: `http://127.0.0.1:15500` - Promptfoo UI: `http://127.0.0.1:15500`
- Wiki: `http://127.0.0.1:80` - Wiki: `http://127.0.0.1:80`
- Lab 3 Terminal: `http://127.0.0.1:7681/wetty`
## Lab 3 Browser Terminal
Linux and WSL deployments now require a managed `student` password hash before `./labctl up` or `./labctl preflight`.
Example:
```bash
export COURSEWARE_STUDENT_PASSWORD_HASH="$(openssl passwd -6 'student-password')"
./labctl up
```
The deployment will:
- create the managed `student` account
- create `/home/student/lab3`
- bind `sshd` to `127.0.0.1:22` only
- install WeTTY and expose it at `http://127.0.0.1:7681/wetty`
## Notes ## Notes
+9
View File
@@ -18,9 +18,12 @@ courseware_unsloth_home: "{{ courseware_state_dir }}/unsloth-home"
courseware_ollama_models_dir: "{{ courseware_models_dir }}/ollama" courseware_ollama_models_dir: "{{ courseware_models_dir }}/ollama"
courseware_node_runtime_dir: "{{ courseware_tools_dir }}/node-runtime" courseware_node_runtime_dir: "{{ courseware_tools_dir }}/node-runtime"
courseware_node_runtime_bin_dir: "{{ courseware_node_runtime_dir }}/node_modules/node/bin" courseware_node_runtime_bin_dir: "{{ courseware_node_runtime_dir }}/node_modules/node/bin"
courseware_wetty_dir: "{{ courseware_tools_dir }}/wetty"
courseware_promptfoo_dir: "{{ courseware_lab6_dir }}" courseware_promptfoo_dir: "{{ courseware_lab6_dir }}"
courseware_wiki_repo_dir: "{{ courseware_repos_dir }}/LLM-Labs" courseware_wiki_repo_dir: "{{ courseware_repos_dir }}/LLM-Labs"
courseware_wiki_runtime_config_path: "{{ courseware_wiki_repo_dir }}/public/courseware-runtime.json"
courseware_llama_cpp_bin_dir: "{{ courseware_repos_dir }}/llama.cpp/build/bin" courseware_llama_cpp_bin_dir: "{{ courseware_repos_dir }}/llama.cpp/build/bin"
courseware_lab3_dir: "/home/student/lab3"
courseware_bind_host: "0.0.0.0" courseware_bind_host: "0.0.0.0"
courseware_url_host: "127.0.0.1" courseware_url_host: "127.0.0.1"
@@ -33,6 +36,7 @@ courseware_ports:
unsloth: 8888 unsloth: 8888
promptfoo: 15500 promptfoo: 15500
wiki: 80 wiki: 80
wetty: 7681
courseware_transformerlab_install_mode: "single-user-pinned" courseware_transformerlab_install_mode: "single-user-pinned"
courseware_transformerlab_version: "v0.28.2" courseware_transformerlab_version: "v0.28.2"
@@ -63,7 +67,11 @@ courseware_chunkviz_commit: "a891eacafda1f28a12373ad3b00102e68f07c57f"
courseware_promptfoo_version: "0.119.0" courseware_promptfoo_version: "0.119.0"
courseware_kiln_release_tag: "v0.18.1" courseware_kiln_release_tag: "v0.18.1"
courseware_node_runtime_version: "20.20.2" courseware_node_runtime_version: "20.20.2"
courseware_wetty_spec: "wetty@2.5.0"
courseware_wetty_base_path: "/wetty"
courseware_wiki_repo: "https://git.zuccaro.me/bzuccaro/LLM-Labs.git" courseware_wiki_repo: "https://git.zuccaro.me/bzuccaro/LLM-Labs.git"
courseware_student_username: "student"
courseware_student_password_hash: "{{ lookup('env', 'COURSEWARE_STUDENT_PASSWORD_HASH') | default('', true) }}"
courseware_open_webui_spec: "open-webui" courseware_open_webui_spec: "open-webui"
courseware_embedding_atlas_spec: "embedding-atlas" courseware_embedding_atlas_spec: "embedding-atlas"
@@ -151,3 +159,4 @@ courseware_services:
- "unsloth" - "unsloth"
- "promptfoo" - "promptfoo"
- "wiki" - "wiki"
- "wetty"
+1
View File
@@ -8,6 +8,7 @@
- packages - packages
- lab_assets - lab_assets
- node_runtime - node_runtime
- { role: terminal, when: ansible_system == "Linux" }
- llama_cpp - llama_cpp
- transformerlab - transformerlab
- open_webui - open_webui
+200
View File
@@ -0,0 +1,200 @@
- name: Fail when student password hash is not configured
fail:
msg: >-
Set COURSEWARE_STUDENT_PASSWORD_HASH in the environment before running ./labctl up.
Example:
export COURSEWARE_STUDENT_PASSWORD_HASH="$(openssl passwd -6 'student-password')"
when: courseware_student_password_hash | trim | length == 0
- name: Install terminal prerequisites
become: true
apt:
name:
- openssh-server
state: present
update_cache: true
- name: Ensure sshd drop-in directory exists
become: true
file:
path: /etc/ssh/sshd_config.d
state: directory
mode: "0755"
- name: Configure courseware loopback-only sshd policy
become: true
template:
src: sshd-courseware-terminal.conf.j2
dest: /etc/ssh/sshd_config.d/50-courseware-terminal.conf
mode: "0644"
register: courseware_terminal_sshd_config
- name: Validate sshd configuration
become: true
command:
argv:
- /usr/sbin/sshd
- -t
- -f
- /etc/ssh/sshd_config
changed_when: false
- name: Ensure sshd runtime directory exists
become: true
file:
path: /run/sshd
state: directory
mode: "0755"
- name: Start and enable sshd with systemd when available
become: true
systemd:
name: ssh
state: started
enabled: true
when: ansible_service_mgr == "systemd"
- name: Check for running sshd when systemd is unavailable
become: true
command: pgrep -x sshd
register: courseware_terminal_sshd_pid
changed_when: false
failed_when: false
when: ansible_service_mgr != "systemd"
- name: Reload running sshd when config changed outside systemd
become: true
command: pkill -HUP -x sshd
when:
- ansible_service_mgr != "systemd"
- courseware_terminal_sshd_pid.rc == 0
- courseware_terminal_sshd_config.changed
- name: Start sshd when it is not already running outside systemd
become: true
command:
argv:
- /usr/sbin/sshd
when:
- ansible_service_mgr != "systemd"
- courseware_terminal_sshd_pid.rc != 0
- name: Ensure managed terminal user exists
become: true
user:
name: "{{ courseware_student_username }}"
password: "{{ courseware_student_password_hash }}"
shell: /bin/bash
create_home: true
state: present
- name: Ensure lab 3 workspace root exists
become: true
file:
path: "{{ courseware_lab3_dir }}"
state: directory
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
mode: "0755"
- name: Ensure lab 3 WhiteRabbitNeo workspace exists
become: true
file:
path: "{{ courseware_lab3_dir }}/WhiteRabbitNeo"
state: directory
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
mode: "0755"
- name: Write lab 3 workspace README
become: true
template:
src: lab3-workspace-readme.txt.j2
dest: "{{ courseware_lab3_dir }}/README.txt"
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
mode: "0644"
- name: Check for repo-local WhiteRabbitNeo base repo
stat:
path: "{{ courseware_root }}/assets/lab2/WhiteRabbitNeo-V3-7B"
register: courseware_lab3_base_repo_stat
- name: Check for repo-local WhiteRabbitNeo GGUF directory
stat:
path: "{{ courseware_root }}/assets/lab2/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF"
register: courseware_lab3_gguf_repo_stat
- name: Link WhiteRabbitNeo base repo into the student workspace when repo-local assets exist
become: true
file:
src: "{{ courseware_root }}/assets/lab2/WhiteRabbitNeo-V3-7B"
dest: "{{ courseware_lab3_dir }}/WhiteRabbitNeo/WhiteRabbitNeo-V3-7B"
state: link
force: true
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
when: courseware_lab3_base_repo_stat.stat.exists
- name: Link WhiteRabbitNeo GGUF directory into the student workspace when repo-local assets exist
become: true
file:
src: "{{ courseware_root }}/assets/lab2/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF"
dest: "{{ courseware_lab3_dir }}/WhiteRabbitNeo/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF"
state: link
force: true
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
when: courseware_lab3_gguf_repo_stat.stat.exists
- name: Link WhiteRabbitNeo download helper into the student workspace
become: true
file:
src: "{{ courseware_lab2_dir }}/download_whiterabbitneo-gguf.sh"
dest: "{{ courseware_lab3_dir }}/download_whiterabbitneo-gguf.sh"
state: link
force: true
owner: "{{ courseware_student_username }}"
group: "{{ courseware_student_username }}"
- name: Create contained WeTTY directory
file:
path: "{{ courseware_wetty_dir }}"
state: directory
mode: "0755"
- name: Install contained WeTTY runtime
command:
argv:
- npm
- install
- "{{ courseware_wetty_spec }}"
args:
chdir: "{{ courseware_wetty_dir }}"
creates: "{{ courseware_wetty_dir }}/node_modules/.bin/wetty"
environment:
PATH: "{{ courseware_node_runtime_bin_dir }}:{{ ansible_env.PATH }}"
- name: Check loopback sshd listener
become: true
command: ss -ltn
register: courseware_terminal_ss_listeners
changed_when: false
- name: Assert sshd is loopback-only
assert:
that:
- "'127.0.0.1:22' in courseware_terminal_ss_listeners.stdout"
- "'0.0.0.0:22' not in courseware_terminal_ss_listeners.stdout"
- "'[::]:22' not in courseware_terminal_ss_listeners.stdout"
fail_msg: "sshd must listen only on 127.0.0.1:22 for the browser terminal deployment."
- name: Assert WeTTY binary exists
stat:
path: "{{ courseware_wetty_dir }}/node_modules/.bin/wetty"
register: courseware_wetty_bin_stat
- name: Fail when WeTTY installation is incomplete
fail:
msg: "WeTTY was not installed under {{ courseware_wetty_dir }}."
when: not courseware_wetty_bin_stat.stat.exists
@@ -0,0 +1,14 @@
This workspace is managed for the Lab 3 browser terminal.
You should log in as:
- username: {{ courseware_student_username }}
Start here:
- working directory: {{ courseware_lab3_dir }}
Helpful paths:
- WhiteRabbitNeo helper: {{ courseware_lab3_dir }}/download_whiterabbitneo-gguf.sh
- repo-local base repo symlink: {{ courseware_lab3_dir }}/WhiteRabbitNeo/WhiteRabbitNeo-V3-7B
- repo-local GGUF symlink: {{ courseware_lab3_dir }}/WhiteRabbitNeo/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF
Some symlinks only appear after the corresponding repo-local lab assets are present.
@@ -0,0 +1,12 @@
# Managed by Local Courseware Deployment.
ListenAddress 127.0.0.1
AddressFamily inet
PermitRootLogin no
PasswordAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
AllowUsers {{ courseware_student_username }}
AllowTcpForwarding no
X11Forwarding no
PrintMotd no
+6
View File
@@ -36,6 +36,12 @@
environment: environment:
PATH: "{{ courseware_node_runtime_bin_dir }}:{{ ansible_env.PATH }}" PATH: "{{ courseware_node_runtime_bin_dir }}:{{ ansible_env.PATH }}"
- name: Render wiki runtime config
template:
src: courseware-runtime.json.j2
dest: "{{ courseware_wiki_runtime_config_path }}"
mode: "0644"
- name: Stat wiki build output - name: Stat wiki build output
stat: stat:
path: "{{ courseware_wiki_repo_dir }}/.next/BUILD_ID" path: "{{ courseware_wiki_repo_dir }}/.next/BUILD_ID"
@@ -0,0 +1,5 @@
{
"lab3TerminalUrl": "http://{{ courseware_url_host }}:{{ courseware_ports.wetty }}{{ courseware_wetty_base_path }}",
"lab3Username": "{{ courseware_student_username }}",
"lab3WorkingDirectory": "{{ courseware_lab3_dir }}"
}
+6
View File
@@ -11,9 +11,12 @@ COURSEWARE_EMBEDDING_ATLAS_PORT="{{ courseware_ports.embedding_atlas }}"
COURSEWARE_UNSLOTH_PORT="{{ courseware_ports.unsloth }}" COURSEWARE_UNSLOTH_PORT="{{ courseware_ports.unsloth }}"
COURSEWARE_PROMPTFOO_PORT="{{ courseware_ports.promptfoo }}" COURSEWARE_PROMPTFOO_PORT="{{ courseware_ports.promptfoo }}"
COURSEWARE_WIKI_PORT="{{ courseware_ports.wiki }}" COURSEWARE_WIKI_PORT="{{ courseware_ports.wiki }}"
COURSEWARE_WETTY_PORT="{{ courseware_ports.wetty }}"
OLLAMA_BIN="{{ courseware_ollama_bin }}" OLLAMA_BIN="{{ courseware_ollama_bin }}"
OLLAMA_MODELS_DIR="{{ courseware_ollama_models_dir }}" OLLAMA_MODELS_DIR="{{ courseware_ollama_models_dir }}"
NODE_RUNTIME_BIN_DIR="{{ courseware_node_runtime_bin_dir }}" NODE_RUNTIME_BIN_DIR="{{ courseware_node_runtime_bin_dir }}"
WETTY_BIN="{{ courseware_wetty_dir }}/node_modules/.bin/wetty"
COURSEWARE_WETTY_BASE_PATH="{{ courseware_wetty_base_path }}"
OPEN_WEBUI_VENV="{{ courseware_venvs_dir }}/open-webui" OPEN_WEBUI_VENV="{{ courseware_venvs_dir }}/open-webui"
OPEN_WEBUI_DATA_DIR="{{ courseware_state_dir }}/open-webui" OPEN_WEBUI_DATA_DIR="{{ courseware_state_dir }}/open-webui"
CHUNKVIZ_DIR="{{ courseware_repos_dir }}/ChunkViz" CHUNKVIZ_DIR="{{ courseware_repos_dir }}/ChunkViz"
@@ -25,10 +28,13 @@ TRANSFORMERLAB_DEFAULT_USER_EMAIL="{{ courseware_transformerlab_default_user_ema
TRANSFORMERLAB_DEFAULT_USER_PASSWORD="{{ courseware_transformerlab_default_user_password }}" TRANSFORMERLAB_DEFAULT_USER_PASSWORD="{{ courseware_transformerlab_default_user_password }}"
TRANSFORMERLAB_DEFAULT_USER_FIRST_NAME="{{ courseware_transformerlab_default_user_first_name }}" TRANSFORMERLAB_DEFAULT_USER_FIRST_NAME="{{ courseware_transformerlab_default_user_first_name }}"
TRANSFORMERLAB_DEFAULT_USER_LAST_NAME="{{ courseware_transformerlab_default_user_last_name }}" TRANSFORMERLAB_DEFAULT_USER_LAST_NAME="{{ courseware_transformerlab_default_user_last_name }}"
COURSEWARE_STUDENT_USERNAME="{{ courseware_student_username }}"
COURSEWARE_LAB3_DIR="{{ courseware_lab3_dir }}"
UNSLOTH_BIN="{{ ansible_env.HOME }}/.local/bin/unsloth" UNSLOTH_BIN="{{ ansible_env.HOME }}/.local/bin/unsloth"
PROMPTFOO_DIR="{{ courseware_promptfoo_dir }}" PROMPTFOO_DIR="{{ courseware_promptfoo_dir }}"
PROMPTFOO_BIN="{{ courseware_tools_dir }}/promptfoo/node_modules/.bin/promptfoo" PROMPTFOO_BIN="{{ courseware_tools_dir }}/promptfoo/node_modules/.bin/promptfoo"
WIKI_DIR="{{ courseware_wiki_repo_dir }}" WIKI_DIR="{{ courseware_wiki_repo_dir }}"
WIKI_RUNTIME_CONFIG_PATH="{{ courseware_wiki_runtime_config_path }}"
LLAMA_CPP_BIN_DIR="{{ courseware_llama_cpp_bin_dir }}" LLAMA_CPP_BIN_DIR="{{ courseware_llama_cpp_bin_dir }}"
KILN_LINUX_BIN="{{ courseware_apps_dir }}/kiln/Kiln" KILN_LINUX_BIN="{{ courseware_apps_dir }}/kiln/Kiln"
KILN_MAC_APP="{{ courseware_apps_dir }}/Kiln.app" KILN_MAC_APP="{{ courseware_apps_dir }}/Kiln.app"
Executable → Regular
+27
View File
@@ -49,6 +49,31 @@ Pinned component versions:
EOF EOF
} }
require_student_password_hash() {
local current_host_profile
current_host_profile=$(host_profile)
if [ "$current_host_profile" = "macos" ]; then
return
fi
if [ -n "${COURSEWARE_STUDENT_PASSWORD_HASH:-}" ]; then
return
fi
cat <<'EOF' >&2
Missing COURSEWARE_STUDENT_PASSWORD_HASH.
Set a password hash for the managed `student` login before running this command. Example:
export COURSEWARE_STUDENT_PASSWORD_HASH="$(openssl passwd -6 'student-password')"
Then rerun:
./labctl up
EOF
exit 1
}
confirm_installation() { confirm_installation() {
local response local response
local tlab_version local tlab_version
@@ -527,6 +552,7 @@ main() {
case "$cmd" in case "$cmd" in
up) up)
confirm_installation confirm_installation
require_student_password_hash
run_playbook up.yml run_playbook up.yml
run_project_script "$ROOT_DIR/scripts/service_manager.sh" start all run_project_script "$ROOT_DIR/scripts/service_manager.sh" start all
;; ;;
@@ -536,6 +562,7 @@ main() {
;; ;;
preflight) preflight)
confirm_installation confirm_installation
require_student_password_hash
run_playbook up.yml --tags preflight run_playbook up.yml --tags preflight
;; ;;
versions) versions)
+19 -1
View File
@@ -17,9 +17,15 @@ load_runtime_env() {
: "${COURSEWARE_URL_HOST:=127.0.0.1}" : "${COURSEWARE_URL_HOST:=127.0.0.1}"
: "${COURSEWARE_PROMPTFOO_PORT:=15500}" : "${COURSEWARE_PROMPTFOO_PORT:=15500}"
: "${COURSEWARE_WIKI_PORT:=80}" : "${COURSEWARE_WIKI_PORT:=80}"
: "${COURSEWARE_WETTY_PORT:=7681}"
: "${COURSEWARE_WETTY_BASE_PATH:=/wetty}"
: "${COURSEWARE_STUDENT_USERNAME:=student}"
: "${COURSEWARE_LAB3_DIR:=/home/student/lab3}"
: "${NODE_RUNTIME_BIN_DIR:=$COURSEWARE_STATE_DIR/tools/node-runtime/node_modules/node/bin}" : "${NODE_RUNTIME_BIN_DIR:=$COURSEWARE_STATE_DIR/tools/node-runtime/node_modules/node/bin}"
: "${WETTY_BIN:=$COURSEWARE_STATE_DIR/tools/wetty/node_modules/.bin/wetty}"
: "${PROMPTFOO_DIR:=$COURSEWARE_STATE_DIR/lab6}" : "${PROMPTFOO_DIR:=$COURSEWARE_STATE_DIR/lab6}"
: "${WIKI_DIR:=$COURSEWARE_STATE_DIR/repos/LLM-Labs}" : "${WIKI_DIR:=$COURSEWARE_STATE_DIR/repos/LLM-Labs}"
: "${WIKI_RUNTIME_CONFIG_PATH:=$WIKI_DIR/public/courseware-runtime.json}"
: "${LLAMA_CPP_BIN_DIR:=$COURSEWARE_STATE_DIR/repos/llama.cpp/build/bin}" : "${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 if [ -n "${OLLAMA_BIN:-}" ] && [[ "$OLLAMA_BIN" != */* ]] && command -v "$OLLAMA_BIN" >/dev/null 2>&1; then
@@ -43,7 +49,8 @@ service_list() {
"embedding-atlas" \ "embedding-atlas" \
"unsloth" \ "unsloth" \
"promptfoo" \ "promptfoo" \
"wiki" "wiki" \
"wetty"
} }
service_pid_file() { service_pid_file() {
@@ -64,6 +71,7 @@ service_port() {
unsloth) printf '%s\n' "${COURSEWARE_UNSLOTH_PORT}" ;; unsloth) printf '%s\n' "${COURSEWARE_UNSLOTH_PORT}" ;;
promptfoo) printf '%s\n' "${COURSEWARE_PROMPTFOO_PORT}" ;; promptfoo) printf '%s\n' "${COURSEWARE_PROMPTFOO_PORT}" ;;
wiki) printf '%s\n' "${COURSEWARE_WIKI_PORT}" ;; wiki) printf '%s\n' "${COURSEWARE_WIKI_PORT}" ;;
wetty) printf '%s\n' "${COURSEWARE_WETTY_PORT}" ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
@@ -78,6 +86,7 @@ service_url() {
unsloth) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_UNSLOTH_PORT" ;; unsloth) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_UNSLOTH_PORT" ;;
promptfoo) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_PROMPTFOO_PORT" ;; promptfoo) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_PROMPTFOO_PORT" ;;
wiki) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_WIKI_PORT" ;; wiki) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_WIKI_PORT" ;;
wetty) printf 'http://%s:%s%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_WETTY_PORT" "$COURSEWARE_WETTY_BASE_PATH" ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
@@ -144,6 +153,15 @@ service_command() {
"$COURSEWARE_BIND_HOST" \ "$COURSEWARE_BIND_HOST" \
"$COURSEWARE_WIKI_PORT" "$COURSEWARE_WIKI_PORT"
;; ;;
wetty)
printf 'cd "%s" && PATH="%s:$PATH" exec "%s" --host %s --port %s --base %s --allow-iframe --ssh-host 127.0.0.1 --ssh-port 22 --ssh-auth password' \
"$COURSEWARE_ROOT" \
"$NODE_RUNTIME_BIN_DIR" \
"$WETTY_BIN" \
"$COURSEWARE_BIND_HOST" \
"$COURSEWARE_WETTY_PORT" \
"$COURSEWARE_WETTY_BASE_PATH"
;;
*) *)
return 1 return 1
;; ;;
Executable → Regular
+43 -1
View File
@@ -28,6 +28,44 @@ ensure_transformerlab_default_user() {
--last-name "${TRANSFORMERLAB_DEFAULT_USER_LAST_NAME:-}" >>"$STATE_DIR/logs/transformerlab_default_user.log" 2>&1 || true --last-name "${TRANSFORMERLAB_DEFAULT_USER_LAST_NAME:-}" >>"$STATE_DIR/logs/transformerlab_default_user.log" 2>&1 || true
} }
check_wetty_prereqs() {
if ! id "$COURSEWARE_STUDENT_USERNAME" >/dev/null 2>&1; then
echo "Missing terminal user '$COURSEWARE_STUDENT_USERNAME'. Re-run ./labctl up after setting COURSEWARE_STUDENT_PASSWORD_HASH." >&2
exit 1
fi
if [ ! -x "$WETTY_BIN" ]; then
echo "Missing WeTTY binary at $WETTY_BIN. Re-run ./labctl up." >&2
exit 1
fi
if [ ! -f "$WIKI_RUNTIME_CONFIG_PATH" ]; then
echo "Missing wiki runtime config at $WIKI_RUNTIME_CONFIG_PATH. Re-run ./labctl up." >&2
exit 1
fi
if [ ! -d "$COURSEWARE_LAB3_DIR" ]; then
echo "Missing lab workspace at $COURSEWARE_LAB3_DIR. Re-run ./labctl up." >&2
exit 1
fi
if ! python3 - <<'PY'
import socket, sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(1)
try:
sock.connect(("127.0.0.1", 22))
except OSError:
sys.exit(1)
finally:
sock.close()
PY
then
echo "Loopback sshd is not reachable on 127.0.0.1:22." >&2
exit 1
fi
}
resolve_targets() { resolve_targets() {
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
echo "No target specified." >&2 echo "No target specified." >&2
@@ -82,7 +120,7 @@ service_ready() {
promptfoo) promptfoo)
curl -fsS "$(service_url "$service")/health" >/dev/null 2>&1 curl -fsS "$(service_url "$service")/health" >/dev/null 2>&1
;; ;;
open-webui|chunkviz|embedding-atlas|unsloth|wiki) open-webui|chunkviz|embedding-atlas|unsloth|wiki|wetty)
curl -fsS "$(service_url "$service")" >/dev/null 2>&1 curl -fsS "$(service_url "$service")" >/dev/null 2>&1
;; ;;
*) *)
@@ -176,6 +214,9 @@ start_one() {
--required-support "batched" >>"$STATE_DIR/logs/transformerlab_plugin_supports.log" 2>&1 || true --required-support "batched" >>"$STATE_DIR/logs/transformerlab_plugin_supports.log" 2>&1 || true
fi fi
;; ;;
wetty)
check_wetty_prereqs
;;
*) *)
;; ;;
esac esac
@@ -289,6 +330,7 @@ Unsloth Studio: $(service_url unsloth)
Promptfoo CLI: $PROMPTFOO_BIN Promptfoo CLI: $PROMPTFOO_BIN
Promptfoo UI: $(service_url promptfoo) Promptfoo UI: $(service_url promptfoo)
Wiki: $(service_url wiki) Wiki: $(service_url wiki)
Lab 3 Terminal: $(service_url wetty)
Kiln app: ${KILN_LAUNCH_PATH:-not installed} Kiln app: ${KILN_LAUNCH_PATH:-not installed}
EOF EOF
} }