launch
tit.launch ¶
Launch the TI-Toolbox container and open its browser UI.
Uses the same compose specification and container labels as Electron. Installed wheels use BUILTIN_SPEC; tests verify it matches docker-compose.yml.
LaunchError ¶
Bases: RuntimeError
A failure the user can act on; the message is the remedy, not a stack trace.
StackSpec
dataclass
¶
StackSpec(image: str, working_dir: str, init: bool, volumes: tuple[str, ...], environment: tuple[tuple[str, str], ...], ports: tuple[str, ...], healthcheck: tuple[str, ...] = ())
The parts of the compose service this launcher realises.
Values still carry ${VAR} / ${VAR:-default} references; they are
interpolated against :func:build_env when the argv is built, exactly as
shared/composeFile.ts does for the app.
LaunchOptions
dataclass
¶
LaunchOptions(project: str, port: int = DEFAULT_PORT, image: str | None = None, timeout: float = 180.0, echo: object = print, repo_dir: str = '', server_reload: bool = False, static_dir: str = '', existing: str | None = None, container: str | None = None, session_project: str = '')
One launch invocation.
The last three are the dev overrides — the three variables the compose file exposes
as ${TIT_REPO_DIR:-}, ${TIT_SERVER_RELOAD:-} and ${TIT_STATIC_DIR:-}, and
the only difference between a user run and a developer run (see
--dev). They default to off, which is the only correct
answer for a user: repo_dir bind-mounts a host directory over /ti-toolbox, which
is where the image installed its own tit from, so a non-empty value in a packaged or
pip-installed run replaces the toolbox with whatever is at that path.
compose_path ¶
compose_path() -> Path | None
Resolve an explicit compose file, the checkout copy, or the built-in fallback.
Standalone loaders pass their adjacent YAML through TIT_COMPOSE_FILE.
An invalid explicit path fails instead of silently starting a different spec.
Source code in tit/launch.py
load_spec ¶
Parse the compose file into a :class:StackSpec, or return :data:BUILTIN_SPEC.
path defaults to :func:compose_path. Passing a path that does not
exist is an error (the caller asked for that file); omitting it and having
no checkout is not.
Source code in tit/launch.py
parse_compose ¶
The services.tit block of a v3 compose file, as a :class:StackSpec.
Source code in tit/launch.py
interpolate ¶
${VAR} / ${VAR:-default}, from env only (never os.environ).
Reading the ambient environment here is exactly the hazard
shared/compose.ts documents for TIT_REPO_DIR: a stray variable in
the user's shell must not bind-mount a host directory over the image's own
/ti-toolbox.
Source code in tit/launch.py
hash8 ¶
The Electron app's 32-bit project hash (shared/compose.ts#hash8), reproduced.
Not a security boundary — it exists so both launchers derive the same
container name and tit.project label for the same directory, which is
what lets tit launch --status see a container the app started and the
app attach to one this launcher started.
It runs over UTF-16 code units, as JavaScript's charCodeAt does: an emoji
is two units, not one code point (tests/fixtures/project_hash.json).
Source code in tit/launch.py
project_name ¶
container_name ¶
default_image ¶
default_image() -> str
Use the compose image default, or an explicit TIT_IMAGE_TAG override.
Runtime package versions can be prereleases with no matching image. The compose spec (and its wheel fallback) owns the image independently of that version.
Source code in tit/launch.py
normalize_image ¶
A bare tag (v3.0.0: no / and no :) names a release of the toolbox image;
anything else is kept, a local tit:dev included. loader.sh's resolved_image
applies the same rule, so every launcher hands Docker and the desktop app the same
reference, never library/v3.0.0.
Source code in tit/launch.py
parse_port ¶
A TCP port written in decimal digits, 1-65535; 08765 is 8765 (loader.sh's rule).
Source code in tit/launch.py
parse_timeout ¶
Positive seconds: text written as N or N.N (loader.sh's rule), or a number.
A number is one already read (argparse, :class:LaunchOptions); it is not turned back
into text, where str(1e-05) would fail the N.N rule loader.sh's text passed.
Source code in tit/launch.py
user_config_path ¶
user_config_path(*, host_platform: str | None = None, env: Mapping[str, str] | None = None, home: str | None = None) -> str
The host directory mounted at /root/.config/ti-toolbox (telemetry consent, prefs).
The documented table (docs/wiki/telemetry.md, PathManager.user_config_dir):
macOS <home>/.config/ti-toolbox always, since a Dock launch never sees a shell's
XDG_CONFIG_HOME; Windows %APPDATA%\ti-toolbox (else <home>\AppData\Roaming);
Linux and the rest $XDG_CONFIG_HOME/ti-toolbox when it is set and non-empty, else
<home>/.config/ti-toolbox. The desktop's userConfigDir and loader.sh's
user_config_dir apply the same rule (tests/fixtures/user_config_dirs.json), so
a telemetry opt-out made through one launcher is the one every launcher mounts.
Source code in tit/launch.py
build_env ¶
build_env(*, host_project_dir: str, port: int, token: str, user_config: str, image_tag: str, repo_dir: str = '', static_dir: str = '', server_reload: bool = False) -> dict[str, str]
The map the compose file's ${VAR} references are interpolated from.
Mirrors shared/compose.ts#buildStackEnv. TIT_REPO_DIR and
TIT_SERVER_RELOAD are always present and empty by default for the reason
that file gives: an omitted key would let the user's own shell supply one.
TIT_NO_TELEMETRY is the opposite case: the user's own opt-out, read from
the host environment so it reaches the container.
Source code in tit/launch.py
build_run_argv ¶
build_run_argv(spec: StackSpec, env: dict[str, str], *, host_project_dir: str, image: str) -> list[str]
The full docker run argv for one stack — pure, so it can be asserted in a test.
Every volume whose source interpolates to empty is dropped rather than
handed to Docker as an empty bind source (composeFile.ts does the same);
that is how the optional dev-repo mount switches itself off.
Source code in tit/launch.py
session_url ¶
The one URL a browser needs: it trades the token for the session cookie.
tit/server/auth.py mints a session id, sets it as an
HttpOnly; SameSite=Strict cookie and redirects to /, so the token
never has to be typed again and never appears in the address bar afterwards.
Source code in tit/launch.py
find_free_port ¶
First free TCP port at or above preferred on the loopback interface.
Source code in tit/launch.py
probe_container_gpu ¶
Exercise CUDA in the selected image without any project mounts.
Source code in tit/launch.py
require_docker ¶
Fail with the remedy, not a traceback, when the daemon is unreachable.
Source code in tit/launch.py
find_container ¶
The container this project owns, by label — the app's own attach rule.
Both labels are checked: tit.project is a 32-bit hash, so matching it
alone could attach one project's UI to another project's container.
Source code in tit/launch.py
is_session ¶
True for a container a TI-Toolbox launcher started, judged by its labels alone.
Service tit (tit.service, else the compose service; empty counts as absent) and
one of tit.stack=ti-toolbox-v3, tit.project or tit.host_project_dir. The
desktop's isToolboxContainer and loader.sh's session_ids apply the same rule
(tests/fixtures/session_labels.json).
Source code in tit/launch.py
find_running_containers ¶
Running toolbox sessions of every project: containers a launcher labelled.
Never matched by image or name: a scratch docker run of the image, a job's
FreeSurfer sibling or a GPU probe would be offered for Recreate and stopped.
Source code in tit/launch.py
choose_existing ¶
Require a deliberate session and action; never infer consent from matching config.
Source code in tit/launch.py
container_credentials ¶
(origin, token) read out of a running container's own environment.
The token is never written to the host filesystem — on attach it comes back from the container that holds it, which is why there is no state file here.
Source code in tit/launch.py
ensure_image ¶
Refresh the mutable release image; retain cached images for offline/dev use.
Source code in tit/launch.py
wait_for_health ¶
Poll /api/health until it answers {"status": "ok"}.
The generous default matches the Electron app's: a cold amd64 start under emulation is slow, and declaring a live stack dead costs the user the whole session.
Source code in tit/launch.py
is_wsl ¶
is_wsl() -> bool
True inside a WSL2 distribution: the Linux side of a Windows host.
WSL puts WSL_DISTRO_NAME (and, with interop on, WSL_INTEROP) in every
process, so those two variables are the whole test. The kernel string is deliberately
not consulted: a Docker Desktop container runs on that same "microsoft" kernel and must
never be mistaken for the host. Being environment-only also lets a plain Linux run stand
in for WSL in tests, and a WSL host stand in for plain Linux (tests/conftest.py
clears the variables). loader.sh's is_wsl applies the identical rule.
Source code in tit/launch.py
translate_project_path ¶
On WSL2, C:\Users\me\project is /mnt/c/Users/me/project; elsewhere unchanged.
Docker Desktop is handed the Linux spelling, and /mnt/c/... already is one, so it
passes straight through. loader.sh applies the identical rule before it validates
the directory, which keeps --print-config byte-identical on WSL too.
Source code in tit/launch.py
resolve_project ¶
The absolute, real path of the project directory, or a message saying what is wrong.
Source code in tit/launch.py
check_project_path ¶
check_project_path(path: str) -> None
Refuse what loader.sh refuses: a colon or newline, or the filesystem root.
A Docker bind SOURCE:TARGET cannot carry a colon in its source (a Windows drive
prefix aside), and / has no name to mount at /mnt/<name>.
Source code in tit/launch.py
start ¶
start(options: LaunchOptions) -> tuple[str, str]
Ask before reusing or replacing any running toolbox container; return (origin, token).
Source code in tit/launch.py
951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 | |
stop ¶
Stop and remove this project's container. Already gone is success, not failure.
Source code in tit/launch.py
status ¶
{name, state, image, origin, health} for this project's container, or None.
Source code in tit/launch.py
logs ¶
Stream the container's logs to this terminal; returns docker's exit code.
Source code in tit/launch.py
windows_openers ¶
Commands that open url in the Windows default browser from inside WSL, in order.
wslview (wslu) is WSL-aware and honours the user's own setup; PowerShell and
cmd.exe ship with Windows and reach WSL through interop. Only URL characters
reach those two, because each one is a shell of sorts; the session URL is made of
nothing else.
Source code in tit/launch.py
open_in_browser ¶
Open the session URL, saying so either way — a headless host has no browser to open.
On WSL the browser lives on the Windows side: webbrowser would hand the URL to
xdg-open and report success whether or not anything showed it, so the Windows
openers are tried instead and judged by their exit status.