Compare commits
2 Commits
main
..
2080_super
| Author | SHA1 | Date | |
|---|---|---|---|
| 86a5df4681 | |||
| e95ee9c938 |
@@ -4,7 +4,7 @@ This project builds a student-friendly local lab environment for the courseware
|
||||
|
||||
- `./deploy-courseware.sh` installs and configures the environment, then starts every managed service.
|
||||
- `./destroy-courseware.sh` stops the managed services, uninstalls courseware-managed Ollama, and removes the project-owned lab state.
|
||||
- `./labctl` provides day-two controls such as `assets lab2`, `ollama_models`, `update_wiki`, `start`, `stop`, `status`, `urls`, and `logs`.
|
||||
- `./labctl` provides day-two controls such as `assets lab2`, `ollama_models`, `update_wiki`, `start`, `stop`, `status`, `urls`, `logs`, and `open kiln`.
|
||||
|
||||
## What It Installs
|
||||
|
||||
@@ -16,6 +16,7 @@ This project builds a student-friendly local lab environment for the courseware
|
||||
- Embedding Atlas
|
||||
- Promptfoo
|
||||
- Unsloth Studio
|
||||
- Kiln Desktop
|
||||
- Course-specific support assets for lab 1, lab 2, and lab 4
|
||||
|
||||
## Lab 1 Defaults
|
||||
@@ -100,7 +101,7 @@ If CUDA is already mounted or preinstalled outside `PATH`, the installer detects
|
||||
- Run `./labctl assets lab2` when you want to populate repo-local Lab 2 assets in `assets/lab2/` from Hugging Face.
|
||||
- After base setup, run `state/lab2/download_whiterabbitneo-gguf.sh` to fetch only the `Q4_K_M`, `Q8_0`, and `IQ2_M` files from `bartowski/WhiteRabbitNeo_WhiteRabbitNeo-V3-7B-GGUF` and register local Ollama models `WhiteRabbitNeo`, `WhiteRabbitNeo-Q4`, `WhiteRabbitNeo-Q8`, and `WhiteRabbitNeo-IQ2`.
|
||||
- Unsloth homes are redirected into this project's `state/` tree via symlinks.
|
||||
- Managed web services bind for access from both Linux and the Windows side of WSL, while `labctl urls` still reports localhost-friendly URLs.
|
||||
- Managed web services bind on all interfaces for headless LAN/VPN access. `labctl urls` reports the detected LAN IP by default; set `COURSEWARE_URL_HOST=<host-or-ip>` before `./labctl up` to advertise a specific VPN DNS name or address.
|
||||
- The local Ansible bootstrap in `.venv-ansible/` is machine-specific and will be recreated automatically if the folder is copied between hosts.
|
||||
- `llama.cpp` uses a conservative, memory-aware build parallelism setting instead of an unbounded `-j` build, which avoids OOM failures on smaller Linux and WSL hosts.
|
||||
|
||||
@@ -108,25 +109,25 @@ If CUDA is already mounted or preinstalled outside `PATH`, the installer detects
|
||||
|
||||
After `./deploy-courseware.sh`, run `./labctl urls`.
|
||||
|
||||
Default endpoints:
|
||||
Default endpoints use the detected host LAN IP:
|
||||
|
||||
- Ollama API: `http://127.0.0.1:11434`
|
||||
- Open WebUI: `http://127.0.0.1:8080`
|
||||
- Netron: `http://127.0.0.1:8338`
|
||||
- ChunkViz: `http://127.0.0.1:3001`
|
||||
- Embedding Atlas: `http://127.0.0.1:5055`
|
||||
- Unsloth Studio: `http://127.0.0.1:8888`
|
||||
- Promptfoo UI: `http://127.0.0.1:15500`
|
||||
- Wiki: `http://127.0.0.1:80`
|
||||
- Lab 3 Terminal: `http://127.0.0.1:7681/wetty`
|
||||
- Ollama API: `http://<host-lan-ip>:11434`
|
||||
- Open WebUI: `http://<host-lan-ip>:8080`
|
||||
- Netron: `http://<host-lan-ip>:8338`
|
||||
- ChunkViz: `http://<host-lan-ip>:3001`
|
||||
- Embedding Atlas: `http://<host-lan-ip>:5055`
|
||||
- Unsloth Studio: `http://<host-lan-ip>:8888`
|
||||
- Promptfoo UI: `http://<host-lan-ip>:15500`
|
||||
- Wiki: `http://<host-lan-ip>:80`
|
||||
- Lab 3 Terminal: `http://<host-lan-ip>:7681/wetty`
|
||||
|
||||
## Lab 3 Browser Terminal
|
||||
|
||||
The deployment will:
|
||||
|
||||
- leave the host's SSH listen addresses under local control while requiring `127.0.0.1:22` for WeTTY
|
||||
- install WeTTY and expose it at `http://127.0.0.1:7681/wetty`
|
||||
- leave login identity management to the host, so any existing local account with password-based SSH access can sign in through the browser terminal
|
||||
- bind `sshd` to `0.0.0.0:22` so regular SSH clients can connect over the LAN/VPN
|
||||
- install WeTTY and expose it at `http://<host-lan-ip>:7681/wetty`
|
||||
- leave login identity management to the host, so any existing local account with password-based SSH access can sign in through SSH or the browser terminal
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -137,6 +138,7 @@ The deployment will:
|
||||
- `./labctl update_wiki` hard-resets the managed wiki checkout to the remote latest, rebuilds it, and restarts only the managed wiki service on port `80`.
|
||||
- `./labctl start core` starts only `ollama` and `open-webui`.
|
||||
- `./labctl start all` starts every managed web service.
|
||||
- `./labctl open kiln` launches the Kiln desktop app installed into the project state.
|
||||
- The scripted Promptfoo install drops a starter config at `state/lab6/promptfoo.yaml`.
|
||||
- `labctl start all` includes Promptfoo via `promptfoo view` and the cloned wiki app.
|
||||
- Lab 2 includes `state/lab2/download_whiterabbitneo-gguf.sh`, which uses `git` + `git lfs` to pull only the supported WhiteRabbitNeo quants. Add `--download-only` if you want the files without Ollama registration.
|
||||
|
||||
@@ -2,11 +2,14 @@ courseware_state_dir: "{{ courseware_root }}/state"
|
||||
courseware_markers_dir: "{{ courseware_state_dir }}/markers"
|
||||
courseware_logs_dir: "{{ courseware_state_dir }}/logs"
|
||||
courseware_run_dir: "{{ courseware_state_dir }}/run"
|
||||
courseware_cache_dir: "{{ courseware_state_dir }}/cache"
|
||||
courseware_tmp_dir: "{{ courseware_state_dir }}/tmp"
|
||||
courseware_repos_dir: "{{ courseware_state_dir }}/repos"
|
||||
courseware_venvs_dir: "{{ courseware_state_dir }}/venvs"
|
||||
courseware_models_dir: "{{ courseware_state_dir }}/models"
|
||||
courseware_datasets_dir: "{{ courseware_state_dir }}/datasets"
|
||||
courseware_tools_dir: "{{ courseware_state_dir }}/tools"
|
||||
courseware_apps_dir: "{{ courseware_state_dir }}/apps"
|
||||
courseware_downloads_dir: "{{ courseware_state_dir }}/downloads"
|
||||
courseware_lab1_dir: "{{ courseware_state_dir }}/lab1"
|
||||
courseware_lab2_dir: "{{ courseware_state_dir }}/lab2"
|
||||
@@ -16,9 +19,10 @@ courseware_lab1_models_dir: "{{ courseware_models_dir }}/lab1"
|
||||
courseware_ollama_models_dir: "{{ courseware_models_dir }}/ollama"
|
||||
courseware_node_runtime_dir: "{{ courseware_tools_dir }}/node-runtime"
|
||||
courseware_node_runtime_bin_dir: "{{ courseware_node_runtime_dir }}/node_modules/node/bin"
|
||||
courseware_uv_venv_dir: "{{ courseware_tools_dir }}/uv"
|
||||
courseware_uv_python_install_dir: "{{ courseware_tools_dir }}/uv-python"
|
||||
courseware_open_webui_python_version: "3.12"
|
||||
courseware_uv_dir: "{{ courseware_tools_dir }}/uv"
|
||||
courseware_uv_bin: "{{ courseware_uv_dir }}/bin/uv"
|
||||
courseware_uv_cache_dir: "{{ courseware_cache_dir }}/uv"
|
||||
courseware_python_runtime_dir: "{{ courseware_tools_dir }}/python"
|
||||
courseware_netron_venv_dir: "{{ courseware_venvs_dir }}/netron"
|
||||
courseware_wetty_dir: "{{ courseware_tools_dir }}/wetty"
|
||||
courseware_promptfoo_dir: "{{ courseware_lab6_dir }}"
|
||||
@@ -27,7 +31,15 @@ courseware_wiki_runtime_config_path: "{{ courseware_wiki_repo_dir }}/public/cour
|
||||
courseware_llama_cpp_bin_dir: "{{ courseware_repos_dir }}/llama.cpp/build/bin"
|
||||
|
||||
courseware_bind_host: "0.0.0.0"
|
||||
courseware_url_host: "127.0.0.1"
|
||||
courseware_url_host: >-
|
||||
{{
|
||||
(lookup('env', 'COURSEWARE_URL_HOST') | trim)
|
||||
if (lookup('env', 'COURSEWARE_URL_HOST') | trim | length) > 0
|
||||
else (
|
||||
ansible_default_ipv4.address
|
||||
| default(ansible_all_ipv4_addresses | default(['127.0.0.1']) | first)
|
||||
)
|
||||
}}
|
||||
courseware_ports:
|
||||
ollama: 11434
|
||||
open_webui: 8080
|
||||
@@ -44,7 +56,10 @@ courseware_ollama_min_version: "0.12.11"
|
||||
courseware_llama_cpp_commit: "51fa458a92d6a3f305f8fd76fc8f702e3e87ddb5"
|
||||
courseware_chunkviz_commit: "a891eacafda1f28a12373ad3b00102e68f07c57f"
|
||||
courseware_promptfoo_version: "0.119.0"
|
||||
courseware_kiln_release_tag: "v0.18.1"
|
||||
courseware_node_runtime_version: "20.20.2"
|
||||
courseware_python_runtime_version: "3.12"
|
||||
courseware_uv_spec: "uv"
|
||||
courseware_wetty_spec: "wetty@2.5.0"
|
||||
courseware_wetty_base_path: "/wetty"
|
||||
courseware_wiki_repo: "https://git.zuccaro.me/bzuccaro/LLM-Labs.git"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
- { role: preflight, tags: ["preflight"] }
|
||||
- directories
|
||||
- packages
|
||||
- python_runtime
|
||||
- netron
|
||||
- lab1_assets
|
||||
- lab_assets
|
||||
@@ -17,4 +18,5 @@
|
||||
- promptfoo
|
||||
- { role: ollama_models, tags: ["ollama_models"] }
|
||||
- { role: wiki, tags: ["wiki"] }
|
||||
- kiln
|
||||
- unsloth
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
- "{{ courseware_markers_dir }}"
|
||||
- "{{ courseware_logs_dir }}"
|
||||
- "{{ courseware_run_dir }}"
|
||||
- "{{ courseware_cache_dir }}"
|
||||
- "{{ courseware_tmp_dir }}"
|
||||
- "{{ courseware_uv_cache_dir }}"
|
||||
- "{{ courseware_repos_dir }}"
|
||||
- "{{ courseware_venvs_dir }}"
|
||||
- "{{ courseware_models_dir }}"
|
||||
- "{{ courseware_datasets_dir }}"
|
||||
- "{{ courseware_tools_dir }}"
|
||||
- "{{ courseware_apps_dir }}"
|
||||
- "{{ courseware_downloads_dir }}"
|
||||
- "{{ courseware_lab1_dir }}"
|
||||
- "{{ courseware_lab2_dir }}"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
- name: Download Kiln Linux archive
|
||||
get_url:
|
||||
url: "https://github.com/Kiln-AI/Kiln/releases/download/{{ courseware_kiln_release_tag }}/Kiln.Linux.x64.zip"
|
||||
dest: "{{ courseware_downloads_dir }}/Kiln.Linux.x64.zip"
|
||||
mode: "0644"
|
||||
|
||||
- name: Create Kiln Linux directory
|
||||
file:
|
||||
path: "{{ courseware_apps_dir }}/kiln"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Unpack Kiln Linux binary
|
||||
unarchive:
|
||||
src: "{{ courseware_downloads_dir }}/Kiln.Linux.x64.zip"
|
||||
dest: "{{ courseware_apps_dir }}/kiln"
|
||||
remote_src: true
|
||||
creates: "{{ courseware_apps_dir }}/kiln/Kiln"
|
||||
|
||||
- name: Ensure Kiln Linux binary is executable
|
||||
file:
|
||||
path: "{{ courseware_apps_dir }}/kiln/Kiln"
|
||||
mode: "0755"
|
||||
state: file
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
- name: Install Kiln on Linux
|
||||
include_tasks: linux.yml
|
||||
when: ansible_system == "Linux"
|
||||
@@ -4,58 +4,33 @@
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Create uv helper virtual environment
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_python_bin }}"
|
||||
- -m
|
||||
- venv
|
||||
- "{{ courseware_uv_venv_dir }}"
|
||||
args:
|
||||
creates: "{{ courseware_uv_venv_dir }}/bin/python"
|
||||
|
||||
- name: Install uv helper
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_venv_dir }}/bin/python"
|
||||
- -m
|
||||
- pip
|
||||
- install
|
||||
- --upgrade
|
||||
- pip
|
||||
- uv
|
||||
args:
|
||||
creates: "{{ courseware_uv_venv_dir }}/bin/uv"
|
||||
|
||||
- name: Check Open WebUI virtual environment Python version
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_venvs_dir }}/open-webui/bin/python"
|
||||
- -c
|
||||
- "import importlib.util, sys; expected = tuple(map(int, '{{ courseware_open_webui_python_version }}'.split('.')[:2])); ok = sys.version_info[:len(expected)] == expected and importlib.util.find_spec('pip') is not None; raise SystemExit(0 if ok else 1)"
|
||||
register: courseware_open_webui_python_check
|
||||
- "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||
register: courseware_open_webui_venv_python_version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Remove incompatible Open WebUI virtual environment
|
||||
- name: Remove Open WebUI virtual environment with incompatible Python
|
||||
file:
|
||||
path: "{{ courseware_venvs_dir }}/open-webui"
|
||||
state: absent
|
||||
when: courseware_open_webui_python_check.rc != 0
|
||||
when:
|
||||
- courseware_open_webui_venv_python_version.rc == 0
|
||||
- courseware_open_webui_venv_python_version.stdout != courseware_python_runtime_version
|
||||
|
||||
- name: Create Open WebUI virtual environment
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_venv_dir }}/bin/uv"
|
||||
- "{{ courseware_python_bin }}"
|
||||
- -m
|
||||
- venv
|
||||
- --seed
|
||||
- --python
|
||||
- "{{ courseware_open_webui_python_version }}"
|
||||
- "{{ courseware_venvs_dir }}/open-webui"
|
||||
args:
|
||||
creates: "{{ courseware_venvs_dir }}/open-webui/bin/python"
|
||||
environment:
|
||||
UV_PYTHON_INSTALL_DIR: "{{ courseware_uv_python_install_dir }}"
|
||||
|
||||
- name: Upgrade Open WebUI venv tooling
|
||||
command:
|
||||
@@ -79,6 +54,24 @@
|
||||
- "{{ courseware_open_webui_spec }}"
|
||||
- "numpy<2"
|
||||
|
||||
- name: Check Embedding Atlas virtual environment Python version
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_venvs_dir }}/embedding-atlas/bin/python"
|
||||
- -c
|
||||
- "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||
register: courseware_embedding_atlas_venv_python_version
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Remove Embedding Atlas virtual environment with incompatible Python
|
||||
file:
|
||||
path: "{{ courseware_venvs_dir }}/embedding-atlas"
|
||||
state: absent
|
||||
when:
|
||||
- courseware_embedding_atlas_venv_python_version.rc == 0
|
||||
- courseware_embedding_atlas_venv_python_version.stdout != courseware_python_runtime_version
|
||||
|
||||
- name: Create Embedding Atlas virtual environment
|
||||
command:
|
||||
argv:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
- name: Create contained Python runtime manager virtual environment
|
||||
command:
|
||||
argv:
|
||||
- /usr/bin/python3
|
||||
- -m
|
||||
- venv
|
||||
- "{{ courseware_uv_dir }}"
|
||||
args:
|
||||
creates: "{{ courseware_uv_dir }}/bin/python"
|
||||
|
||||
- name: Upgrade contained Python runtime manager tooling
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_dir }}/bin/python"
|
||||
- -m
|
||||
- pip
|
||||
- install
|
||||
- --upgrade
|
||||
- pip
|
||||
- setuptools
|
||||
- wheel
|
||||
|
||||
- name: Install contained Python runtime manager
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_dir }}/bin/python"
|
||||
- -m
|
||||
- pip
|
||||
- install
|
||||
- "{{ courseware_uv_spec }}"
|
||||
|
||||
- name: Install managed CPython runtime
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_bin }}"
|
||||
- python
|
||||
- install
|
||||
- "{{ courseware_python_runtime_version }}"
|
||||
- --install-dir
|
||||
- "{{ courseware_python_runtime_dir }}"
|
||||
environment:
|
||||
UV_PYTHON_INSTALL_DIR: "{{ courseware_python_runtime_dir }}"
|
||||
UV_CACHE_DIR: "{{ courseware_uv_cache_dir }}"
|
||||
XDG_CACHE_HOME: "{{ courseware_cache_dir }}"
|
||||
TMPDIR: "{{ courseware_tmp_dir }}"
|
||||
register: courseware_python_runtime_install
|
||||
changed_when: "'Installed Python' in courseware_python_runtime_install.stdout"
|
||||
|
||||
- name: Resolve managed CPython runtime
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_uv_bin }}"
|
||||
- python
|
||||
- find
|
||||
- "{{ courseware_python_runtime_version }}"
|
||||
environment:
|
||||
UV_PYTHON_INSTALL_DIR: "{{ courseware_python_runtime_dir }}"
|
||||
UV_CACHE_DIR: "{{ courseware_uv_cache_dir }}"
|
||||
XDG_CACHE_HOME: "{{ courseware_cache_dir }}"
|
||||
TMPDIR: "{{ courseware_tmp_dir }}"
|
||||
register: courseware_python_runtime_find
|
||||
changed_when: false
|
||||
|
||||
- name: Set managed Python runtime for courseware venvs
|
||||
set_fact:
|
||||
courseware_python_bin: "{{ courseware_python_runtime_find.stdout | trim }}"
|
||||
|
||||
- name: Verify managed Python runtime version
|
||||
command:
|
||||
argv:
|
||||
- "{{ courseware_python_bin }}"
|
||||
- -c
|
||||
- "import sys; expected=tuple(map(int, '{{ courseware_python_runtime_version }}'.split('.'))); raise SystemExit(0 if sys.version_info[:len(expected)] == expected else 1)"
|
||||
changed_when: false
|
||||
@@ -13,7 +13,7 @@
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure courseware sshd policy
|
||||
- name: Configure courseware loopback-only sshd policy
|
||||
become: true
|
||||
template:
|
||||
src: sshd-courseware-terminal.conf.j2
|
||||
@@ -46,14 +46,24 @@
|
||||
enabled: true
|
||||
when: ansible_service_mgr == "systemd"
|
||||
|
||||
- name: Reload sshd when config changed with systemd
|
||||
- name: Check systemd sshd listener policy
|
||||
become: true
|
||||
command: ss -ltn
|
||||
register: courseware_terminal_systemd_ss_listeners
|
||||
changed_when: false
|
||||
when: ansible_service_mgr == "systemd"
|
||||
|
||||
- name: Restart sshd with systemd when listener policy is not active
|
||||
become: true
|
||||
systemd:
|
||||
name: ssh
|
||||
state: reloaded
|
||||
state: restarted
|
||||
enabled: true
|
||||
when:
|
||||
- ansible_service_mgr == "systemd"
|
||||
- courseware_terminal_sshd_config.changed
|
||||
- >-
|
||||
'0.0.0.0:22' not in courseware_terminal_systemd_ss_listeners.stdout
|
||||
or '[::]:22' in courseware_terminal_systemd_ss_listeners.stdout
|
||||
|
||||
- name: Check for running sshd when systemd is unavailable
|
||||
become: true
|
||||
@@ -98,13 +108,18 @@
|
||||
environment:
|
||||
PATH: "{{ courseware_node_runtime_bin_dir }}:{{ ansible_env.PATH }}"
|
||||
|
||||
- name: Wait for sshd to accept local WeTTY connections
|
||||
wait_for:
|
||||
host: 127.0.0.1
|
||||
port: 22
|
||||
state: started
|
||||
timeout: 10
|
||||
msg: "sshd must accept connections on 127.0.0.1:22 for the browser terminal deployment."
|
||||
- name: Check sshd listener
|
||||
become: true
|
||||
command: ss -ltn
|
||||
register: courseware_terminal_ss_listeners
|
||||
changed_when: false
|
||||
|
||||
- name: Assert sshd accepts LAN and loopback clients
|
||||
assert:
|
||||
that:
|
||||
- "'0.0.0.0:22' in courseware_terminal_ss_listeners.stdout"
|
||||
- "'[::]:22' not in courseware_terminal_ss_listeners.stdout"
|
||||
fail_msg: "sshd must listen on 0.0.0.0:22 so VPN/LAN SSH clients and local WeTTY can connect."
|
||||
|
||||
- name: Assert WeTTY binary exists
|
||||
stat:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Managed by Local Courseware Deployment.
|
||||
ListenAddress 0.0.0.0
|
||||
AddressFamily inet
|
||||
PermitRootLogin no
|
||||
PasswordAuthentication yes
|
||||
KbdInteractiveAuthentication no
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
args:
|
||||
executable: /bin/bash
|
||||
creates: "{{ courseware_unsloth_home }}/.install_complete"
|
||||
environment:
|
||||
UV_CACHE_DIR: "{{ courseware_uv_cache_dir }}"
|
||||
XDG_CACHE_HOME: "{{ courseware_cache_dir }}"
|
||||
TMPDIR: "{{ courseware_tmp_dir }}"
|
||||
rescue:
|
||||
- name: Capture Unsloth installer log tail
|
||||
shell: |
|
||||
@@ -41,3 +45,18 @@
|
||||
|
||||
Last log lines:
|
||||
{{ courseware_unsloth_install_log_tail.stdout | default('(no log output captured)') }}
|
||||
|
||||
- name: Install x86_64-compatible NumPy for Unsloth Studio
|
||||
command:
|
||||
argv:
|
||||
- "{{ ansible_env.HOME }}/.unsloth/studio/unsloth_studio/bin/python"
|
||||
- -m
|
||||
- pip
|
||||
- install
|
||||
- "numpy<2"
|
||||
environment:
|
||||
UV_CACHE_DIR: "{{ courseware_uv_cache_dir }}"
|
||||
XDG_CACHE_HOME: "{{ courseware_cache_dir }}"
|
||||
TMPDIR: "{{ courseware_tmp_dir }}"
|
||||
register: courseware_unsloth_numpy_install
|
||||
changed_when: "'Successfully installed' in courseware_unsloth_numpy_install.stdout"
|
||||
|
||||
@@ -34,3 +34,5 @@ PROMPTFOO_BIN="{{ courseware_tools_dir }}/promptfoo/node_modules/.bin/promptfoo"
|
||||
WIKI_DIR="{{ courseware_wiki_repo_dir }}"
|
||||
WIKI_RUNTIME_CONFIG_PATH="{{ courseware_wiki_runtime_config_path }}"
|
||||
LLAMA_CPP_BIN_DIR="{{ courseware_llama_cpp_bin_dir }}"
|
||||
KILN_LINUX_BIN="{{ courseware_apps_dir }}/kiln/Kiln"
|
||||
KILN_LAUNCH_PATH="{{ courseware_apps_dir }}/kiln/Kiln"
|
||||
|
||||
@@ -27,6 +27,7 @@ Usage:
|
||||
./labctl stop [all|service...]
|
||||
./labctl status [all|service...]
|
||||
./labctl urls
|
||||
./labctl open kiln
|
||||
./labctl logs <service>
|
||||
EOF
|
||||
}
|
||||
@@ -81,6 +82,7 @@ WARNING: THIS SCRIPT WILL CONFIGURE YOUR ENVIRONMENT WILL THE FOLLOWING SOFTWARE
|
||||
- Embedding Atlas
|
||||
- Promptfoo
|
||||
- Unsloth Studio
|
||||
- Kiln Desktop
|
||||
- Course-specific support assets for lab 1, lab 2, and lab 4
|
||||
- Pre-pulled Gemma 4 E2B Ollama models for Lab 1 and Lab 2
|
||||
- Lab 1 confidence support through Gemma 4 E2B Q4 (requires Ollama ${min_ollama}+)
|
||||
@@ -105,6 +107,7 @@ WARNING: THIS SCRIPT WILL CONFIGURE YOUR ENVIRONMENT WILL THE FOLLOWING SOFTWARE
|
||||
- Embedding Atlas
|
||||
- Promptfoo
|
||||
- Unsloth Studio
|
||||
- Kiln Desktop
|
||||
- Course-specific support assets for lab 1, lab 2, and lab 4
|
||||
- Pre-pulled Gemma 4 E2B Ollama models for Lab 1 and Lab 2
|
||||
- Lab 1 confidence support through Gemma 4 E2B Q4 (requires Ollama ${min_ollama}+)
|
||||
@@ -573,7 +576,7 @@ main() {
|
||||
require_arg "$@"
|
||||
handle_assets_command "$@"
|
||||
;;
|
||||
start|stop|status|urls|logs)
|
||||
start|stop|status|urls|open|logs)
|
||||
exec bash "$ROOT_DIR/scripts/service_manager.sh" "$cmd" "$@"
|
||||
;;
|
||||
""|-h|--help|help)
|
||||
|
||||
@@ -14,7 +14,10 @@ load_runtime_env() {
|
||||
|
||||
: "${COURSEWARE_STATE_DIR:=$STATE_DIR}"
|
||||
: "${COURSEWARE_BIND_HOST:=127.0.0.1}"
|
||||
if [ -z "${COURSEWARE_URL_HOST:-}" ]; then
|
||||
COURSEWARE_URL_HOST=$(ip route get 1.1.1.1 2>/dev/null | sed -nE 's/.* src ([0-9.]+).*/\1/p' | head -n 1)
|
||||
: "${COURSEWARE_URL_HOST:=127.0.0.1}"
|
||||
fi
|
||||
: "${COURSEWARE_NETRON_PORT:=8338}"
|
||||
: "${COURSEWARE_PROMPTFOO_PORT:=15500}"
|
||||
: "${COURSEWARE_WIKI_PORT:=80}"
|
||||
|
||||
+89
-50
@@ -115,8 +115,9 @@ is_running() {
|
||||
service_startup_attempts() {
|
||||
case "$1" in
|
||||
embedding-atlas)
|
||||
# The first launch can be noticeably slower on cold environments.
|
||||
printf '%s\n' 180
|
||||
# First launch embeds the bundled dataset. On older GPU drivers this falls
|
||||
# back to CPU and can take close to an hour.
|
||||
printf '%s\n' 3600
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' 60
|
||||
@@ -198,56 +199,13 @@ terminate_service_processes() {
|
||||
done < <(service_listener_pids "$service")
|
||||
}
|
||||
|
||||
start_one() {
|
||||
wait_for_service_ready() {
|
||||
local service=$1
|
||||
local cmd
|
||||
local log_file
|
||||
local pid_file
|
||||
local log_file=$2
|
||||
local pid_file=$3
|
||||
local startup_attempts=$4
|
||||
local pid_grace_attempts=$5
|
||||
local attempt
|
||||
local pid_grace_attempts=5
|
||||
local startup_attempts
|
||||
|
||||
if [ "$service" = "ollama" ] || [ "$service" = "wiki" ]; then
|
||||
assert_ollama_logprobs_support
|
||||
fi
|
||||
|
||||
if has_live_pid "$service"; then
|
||||
echo "$service already running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if service_ready "$service"; then
|
||||
echo "$service already available"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$service" in
|
||||
open-webui)
|
||||
start_one ollama
|
||||
;;
|
||||
wetty)
|
||||
check_wetty_prereqs
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
cmd=$(service_command "$service")
|
||||
startup_attempts=$(service_startup_attempts "$service")
|
||||
log_file=$(service_log_file "$service")
|
||||
pid_file=$(service_pid_file "$service")
|
||||
|
||||
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 &
|
||||
fi
|
||||
echo $! >"$pid_file"
|
||||
|
||||
for attempt in $(seq 1 "$startup_attempts"); do
|
||||
if service_ready "$service"; then
|
||||
@@ -270,6 +228,68 @@ start_one() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
start_one() {
|
||||
local service=$1
|
||||
local cmd
|
||||
local log_file
|
||||
local pid_file
|
||||
local pid_grace_attempts=5
|
||||
local startup_attempts
|
||||
|
||||
if [ "$service" = "ollama" ] || [ "$service" = "wiki" ]; then
|
||||
assert_ollama_logprobs_support
|
||||
fi
|
||||
|
||||
startup_attempts=$(service_startup_attempts "$service")
|
||||
log_file=$(service_log_file "$service")
|
||||
pid_file=$(service_pid_file "$service")
|
||||
|
||||
if service_ready "$service"; then
|
||||
echo "$service already available"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if has_live_pid "$service"; then
|
||||
echo "$service already starting"
|
||||
wait_for_service_ready "$service" "$log_file" "$pid_file" "$startup_attempts" "$pid_grace_attempts"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$service" in
|
||||
open-webui)
|
||||
start_one ollama
|
||||
;;
|
||||
wetty)
|
||||
check_wetty_prereqs
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
cmd=$(service_command "$service")
|
||||
|
||||
if [ "$service" = "ollama" ]; then
|
||||
if command -v setsid >/dev/null 2>&1; then
|
||||
nohup setsid env \
|
||||
OLLAMA_HOST="${COURSEWARE_BIND_HOST}:${COURSEWARE_OLLAMA_PORT}" \
|
||||
OLLAMA_MODELS="$OLLAMA_MODELS_DIR" \
|
||||
"$OLLAMA_BIN" serve </dev/null >>"$log_file" 2>&1 &
|
||||
else
|
||||
nohup env \
|
||||
OLLAMA_HOST="${COURSEWARE_BIND_HOST}:${COURSEWARE_OLLAMA_PORT}" \
|
||||
OLLAMA_MODELS="$OLLAMA_MODELS_DIR" \
|
||||
"$OLLAMA_BIN" serve </dev/null >>"$log_file" 2>&1 &
|
||||
fi
|
||||
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 &
|
||||
fi
|
||||
echo $! >"$pid_file"
|
||||
|
||||
wait_for_service_ready "$service" "$log_file" "$pid_file" "$startup_attempts" "$pid_grace_attempts"
|
||||
}
|
||||
|
||||
stop_one() {
|
||||
local service=$1
|
||||
local pid_file
|
||||
@@ -362,9 +382,21 @@ Promptfoo CLI: $PROMPTFOO_BIN
|
||||
Promptfoo UI: $(service_url promptfoo)
|
||||
Wiki: $(service_url wiki)
|
||||
Lab 3 Terminal: $(service_url wetty)
|
||||
Kiln app: ${KILN_LAUNCH_PATH:-not installed}
|
||||
EOF
|
||||
}
|
||||
|
||||
open_kiln() {
|
||||
if [ -x "$KILN_LINUX_BIN" ]; then
|
||||
nohup "$KILN_LINUX_BIN" >/dev/null 2>&1 &
|
||||
echo "started Kiln from $KILN_LINUX_BIN"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Kiln is not installed." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
show_logs() {
|
||||
local service=$1
|
||||
local log_file
|
||||
@@ -406,6 +438,13 @@ main() {
|
||||
urls)
|
||||
urls
|
||||
;;
|
||||
open)
|
||||
if [ "${1:-}" != "kiln" ]; then
|
||||
echo "Only 'open kiln' is supported." >&2
|
||||
exit 1
|
||||
fi
|
||||
open_kiln
|
||||
;;
|
||||
logs)
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: ./labctl logs <service>" >&2
|
||||
|
||||
Reference in New Issue
Block a user