---
title: "Install"
description: "The install script and its options, what each release download contains, what a server install needs, and what happens the first time ThinkTerm opens."
url: "https://docs.closex.org/thinkterm/install.html"
markdown_url: "https://docs.closex.org/thinkterm/install.md"
site: "CloseX Docs"
product: "ThinkTerm"
language: "en"
last_updated: "2026-11-08T12:04:16.000Z"
alternates:
  en: "https://docs.closex.org/thinkterm/install.md"
  zh-Hans: "https://docs.closex.org/zh/thinkterm/install.md"
  ja: "https://docs.closex.org/ja/thinkterm/install.md"
---

# Install

> **Documentation in progress**
> Some sections are still incomplete.

[ThinkTerm's first launch: hello. is written out, then the Welcome to ThinkTerm page appears](https://docs.closex.org/img/thinkterm/thinkterm_first_launch.mp4)

ThinkTerm is released for macOS, Linux and Windows on the [releases page](https://github.com/RoversX/thinkterm/releases). The iOS and Android apps are in development and have not been released.

## Which way to install

| On | Architectures | Do this |
|---|---|---|
| macOS | Apple silicon, Intel | Run the [install script](#the-install-script), or install the [Homebrew cask](#homebrew-on-macos) |
| Linux | x86_64, arm64 | Run the [install script](#the-install-script), or install a [`.deb`, `.rpm` or AppImage](#linux) from the releases page |
| Windows | x64, arm64 | Download the [installer or the zip](#windows) from the releases page; there is no install script |
| A server you reach over SSH | As above | Run the install script with `--server`; see [Installing on a server](#installing-on-a-server) |

On macOS and Linux:

```bash
curl -fsSL https://raw.githubusercontent.com/RoversX/thinkterm/main/install.sh | sh
```

The script asks whether to install the desktop app or only the server programs, and can update itself later. The rest of this page is the detail: what each way installs, the script's options, and what the first launch shows.

## System requirements

| System | Needs |
|---|---|
| macOS | macOS 14 or later, on Apple silicon or Intel |
| Linux | x86_64 or arm64. The desktop variant needs glibc 2.35 or newer — Ubuntu 22.04, Debian 12, RHEL 10 or later. The server variant needs glibc 2.31 or newer, which adds Ubuntu 20.04, Debian 11 and RHEL 9 |
| Windows | Windows 10 version 1809 or later, on x64 or arm64 |

Linux builds link against glibc, so a musl system such as Alpine cannot run them at all. An older glibc fails in a less obvious way — at load time, with a message about a symbol version rather than the real cause — so the install script checks the version first, against the variant it is installing, and says so in words. On either, the answer is to build from source. When a desktop install is refused on a glibc from 2.31 to 2.34, the message adds that `--server` runs there. On a host below glibc 2.34 the server variant is a different build from the one other hosts get, with OpenSSL built in, because those hosts have only OpenSSL 1.1; the script chooses it by itself and says so.

## The install script

```bash
curl -fsSL https://raw.githubusercontent.com/RoversX/thinkterm/main/install.sh | sh
```

The script picks the release for this machine, downloads it, and checks it against the SHA-256 digest GitHub recorded when the file was uploaded. A download that does not match is refused and nothing is installed. If GitHub has no digest for the file, or the machine has neither `sha256sum` nor `shasum`, the script says the download was not verified rather than staying quiet about it.

The script downloads from GitHub and turns to a mirror only when GitHub is slow. It first tests GitHub for a few seconds and stays with it when it delivers about 300 KB/s or more. Otherwise it tests the mirrors all at once and ranks GitHub and the mirrors by those results, fastest first; any that did not answer the test come last. A source that stalls, or stays below that rate for a few seconds, is passed over for the next one, which goes on from the bytes already fetched instead of starting again. An interrupted transfer resumes where it stopped when the source allows it. The whole file is checked at the end, and if the bytes carried over turn out to be wrong, the new source downloads the whole file once before it is passed over. If every source fails, the ones that were making progress get a second pass in the same order, allowed to be slow so long as they do not stall. The partial download is deleted when the script ends, so nothing resumes between two runs.

A mirror is only used when there is a digest to check it against — GitHub's, or one from the release's signed file list, below — and what it sends is installed only if it matches. `GITHUB_TOKEN` is never sent to a mirror. `--mirror direct` keeps the download on GitHub, and `--mirror` with a mirror's host name tries that one first and then GitHub.

Each release also carries a signed list of its files, `thinkterm-release.txt`, with the signature beside it as `thinkterm-release.txt.sig`. When GitHub's release lookup does not answer and you named the release with `--version`, the script takes the digest from that list instead, after checking the signature against a key built into the script; that needs an `ssh-keygen` that can verify signatures. If no list can be verified, the script says it is trying GitHub's archive directly, without mirrors, and since it then has no digest to compare against, the download is not checked; a list whose signature fails stops the install. Finding out which release is the latest still needs GitHub's lookup.

It installs for the current user and needs no root and no package manager:

- **Linux** — the programs go in `~/.local/bin`, and the desktop variant adds a launcher entry and an icon under `~/.local/share`.
- **macOS** — the signed `ThinkTerm.app` goes in `/Applications`, or `~/Applications` when `/Applications` is not writable for you, and the command-line tools are linked into `~/.local/bin`. The links point into the app rather than copying out of it, so they stay covered by its signature.

Completions for bash, zsh and fish are installed under `~/.local/share` as well. A small record of what was installed is kept beside them, which is how `thinkterm update` and `Settings › Software Update` later know they may replace these files.

Your shell startup files are not touched. When `~/.local/bin` is not on your `PATH` the script prints the line to add — on Debian and Ubuntu it only joins `PATH` at the next login, and only if it existed then — and it prints the optional line that turns on shell integration, which gives prompt marks and lets ThinkTerm follow the shell's working directory.

Both variants put a `wezterm` command beside `thinkterm`, for tools that look for one when `TERM_PROGRAM` says WezTerm. If WezTerm itself is also installed, whichever directory comes first on your `PATH` decides which `wezterm` runs.

### Desktop or server

| Variant | Contains |
|---|---|
| `--desktop` | The app, the command-line tools, the TUI and the session server |
| `--server` | Everything but the app. On Linux it needs no graphics libraries either |

The script never guesses from the machine: a headless box may still want the TUI, and a desktop may be somebody's SSH target. Run in a terminal without either flag, it asks. Run with no terminal at all — from a provisioning script — the flag is required.

The two are exclusive. Installing one removes the other's files, so moving a machine from server to desktop is the same command with `--desktop`, which adds the app and keeps the rest, and moving back removes the app and its launcher.

### Options

| Option | Effect |
|---|---|
| `--desktop`, `--server` | Choose the variant |
| `--version X` | Install release `X` instead of the latest |
| `--prefix DIR` | Install under `DIR` instead of `~/.local`; the programs go in `DIR/bin` |
| `--app-dir DIR` | macOS only: put `ThinkTerm.app` in `DIR` |
| `--from FILE` | Install an archive already downloaded; nothing is fetched, and it is checked only if `--sha256` is given |
| `--dry-run` | Say what would be done and stop before touching the disk |
| `--sha256 HASH` | With `--from`: check the archive against this SHA-256, and install nothing if it differs |
| `--mirror M` | Where the archive comes from. `auto`, the default, starts with GitHub and ranks the mirrors when GitHub is slow; `direct` uses GitHub only; a mirror's host name tries that mirror, then GitHub |

Options reach the script through `sh -s --`:

```bash
curl -fsSL https://raw.githubusercontent.com/RoversX/thinkterm/main/install.sh | sh -s -- --server
```

With `--from` there is no release lookup, and no SHA-256 check unless you pass `--sha256` with the digest you expect, in which case a mismatch installs nothing: the script installs the file it is given. The version it records comes from `--version`, or else from the archive itself: its name, or the folder inside it. An archive that gives neither is recorded as `unknown`, with a warning; pass `--version` to name it.

`GITHUB_TOKEN`, if it is set, is sent with the script's requests, which lifts GitHub's anonymous rate limit. It is never sent to a mirror. ThinkTerm's own background update check never reads it; see [Updates](https://docs.closex.org/thinkterm/updates.md).

### Upgrading and removing

Running the script again is the upgrade. It does not compare versions — it installs the release it fetched over whatever is there — so it can also take a machine back to an older release with `--version`. `thinkterm update` and `Install Update` in Settings run this same script for you; [Updates](https://docs.closex.org/thinkterm/updates.md) has the details.

Files are swapped in place, so anything already running keeps the version it started with. What is still running on the old version is listed when the install ends.

A server is the exception. When the server variant is installed at a terminal while a mux server is running, the script starts the new `thinkterm-mux-server` with `--daemonize --takeover`, so the running server hands its sessions to the new build and nothing running in them ends. A failed handover is tried again, up to three times. A server that cannot hand over, such as one from before the feature, keeps running as it was, and the script says so and prints how to switch by hand, which ends its sessions. The script does this only at a terminal. A remote server updated from your desktop is handed over by the client itself, see [Updating a remote server](https://docs.closex.org/thinkterm/updates.md#updating-a-remote-server), and the desktop app moves its own session server over the next time it starts, because swapping it while the app is open would cut it off.

The script reports as it goes. On a terminal that can redraw a line, it types out a wordmark, then shows each step — detecting the machine, looking up the release, downloading, verifying, unpacking, installing — with a spinner while it works, and a progress bar for the download, and ends each step with a tick. Piped or logged output, or `TERM=dumb`, gets plain lines instead. Colour is used only when the output is a terminal and `NO_COLOR` is not set, and warnings and failures go to standard error. It also uses ASCII in place of the ticks, arrows and bar when the locale is not UTF-8.

When the install is done the script prints what is left for you under `Next`, the most pressing first. An item marked `!` stops ThinkTerm, or part of it, working until it is dealt with: `~/.local/bin` is not on your `PATH`, another `thinkterm` comes first on it, on Linux the desktop app is missing libraries, a server install in `~/.local` sits beside a desktop app it does not update (see [Installing on a server](#installing-on-a-server)), or a mux server that could not hand its sessions over still runs the old version. An item marked `·` is worth knowing: that a mux server from before the install is still the old version, which a newer app or command-line tool may refuse to talk to until it is restarted, that ThinkTerm is open and starts the new version next time, how to start the session server (for the server variant) and, on Linux, that it usually keeps running after you log out, with `loginctl enable-linger $USER` as the fallback if it does not, which some systems let only an administrator run, and the optional line for shell integration. Each command to paste is printed under the item that asks for it.

The last line says `Ready.` and what to run — `thinkterm`, or `thinkterm tui` for the server variant — or `Installed.` with a note that the desktop app needs the libraries listed. When you are at a terminal on a machine with a screen, and ThinkTerm is not already open, the script asks `Open ThinkTerm now? [Y/n]` instead of leaving you to type it; over SSH it does not ask.

There is no uninstaller. The script prints every file it installs, and removing ThinkTerm is deleting them.

### Missing libraries on Linux

A package manager would pull in the libraries the app needs; a tarball cannot. For the desktop variant the script asks the system which ones the app is missing and names the package that provides each, for `apt`, `dnf` and `zypper`. It warns rather than stops: the command-line tools and the session server work without them, and the app starts once they are installed.

## Homebrew on macOS

```bash
brew install --cask roversx/tap/thinkterm
```

The cask installs the macOS release from the [releases page](https://github.com/RoversX/thinkterm/releases).

## What a release contains

Every release also carries `thinkterm-release.txt`, a list of its files with their SHA-256 digests and sizes, signed with ThinkTerm's release key, and the signature as `thinkterm-release.txt.sig`. [The install script](#the-install-script) and a client updating a server over SSH fall back on it when GitHub's own lookup fails.

### macOS

One zip per architecture, `arm64` and `x86_64`, each holding a signed and notarised `ThinkTerm.app`. The install script uses the same zip for both variants.

You can also unzip it and move the app into Applications yourself. A copy placed by hand is one ThinkTerm can still update: `Install Update` replaces it where it sits and adds the command-line tools under `~/.local/bin`, after which it is managed like a script install.

### Linux

| Download | What it is |
|---|---|
| `thinkterm-<version>-linux-<arch>.tar.gz` | The desktop variant, as the install script installs it |
| `thinkterm-server-<version>-linux-<arch>.tar.gz` | The server variant |
| `thinkterm-server-<version>-linux-<arch>.tar.xz` | The server variant as a smaller download. The install script takes it when the release has it and the machine has `xz` |
| `thinkterm-server-<version>-linux-<arch>-glibc2.31.tar.gz`, and the same as `.tar.xz` | The server variant for hosts with glibc below 2.34, built on Ubuntu 20.04 with OpenSSL built in. The install script, `thinkterm update` and remote installs pick it on glibc 2.31 to 2.33 |
| `.deb` | Packages `thinkterm` and `thinkterm-server`, built on Ubuntu 22.04 |
| `.rpm` | Packages `thinkterm` and `thinkterm-server`, built on Fedora |
| `.AppImage` | The desktop app in one file, built on Ubuntu 22.04 |

Each comes for x86_64 and arm64.

The `.deb` and `.rpm` packages cannot be installed alongside a WezTerm package, because both provide a `wezterm` command, and the desktop and server packages exclude each other. A package is updated through the package manager, and an AppImage through AppImageUpdate; ThinkTerm points you there rather than replacing their files.

### Windows

Windows has no install script. The release carries an installer and a zip for x64, and the same two for arm64, which run natively there. The arm64 names carry `-arm64`, and the x64 names are unchanged. The x64 installer also installs on Windows on arm64, where ThinkTerm then runs under emulation; the arm64 installer is the native one.

**The installer**, `ThinkTerm-<version>-setup.exe` (`ThinkTerm-<version>-arm64-setup.exe` for arm64), asks for administrator rights and installs into Program Files. It adds ThinkTerm's folder to the system `PATH`, adds `Open ThinkTerm here` to the Explorer menu for folders, folder backgrounds and drives, and offers a desktop shortcut. Updating from inside ThinkTerm downloads the next installer for the architecture ThinkTerm is running as, checks it against the SHA-256 digest GitHub recorded when there is one — without one it says the download was not verified and goes on — and runs it after Windows' permission prompt, then waits for it to finish. The installer does not close ThinkTerm: it moves each program and library of the old build aside under the same names, puts the new ones in their place, and moves the old ones back if the install fails. ThinkTerm keeps running the old build until it restarts, and its session server hands its terminals to the new build then.

**The zip**, `ThinkTerm-windows-<version>.zip` (`ThinkTerm-windows-arm64-<version>.zip` for arm64), unpacks anywhere and runs from there, with nothing added to `PATH` or to Explorer. A `thinkterm.lua` beside the executable takes precedence over the config in your user profile, so a copy can carry its own settings — see [Configuration](https://docs.closex.org/thinkterm/configuration.md). Only the config travels with it, though: Spaces, hosts and the rest of ThinkTerm's data are still kept in your user profile. To update it, unpack the new zip in its place.

## Installing on a server

A machine you reach over SSH needs ThinkTerm only for ThinkTerm Connect, the persistent connection that keeps sessions running on the host. The server variant is enough:

```bash
curl -fsSL https://raw.githubusercontent.com/RoversX/thinkterm/main/install.sh | sh -s -- --server
```

On macOS the server variant keeps the app bundle under `~/.local/libexec`, out of Applications and Launchpad, since what is wanted from it is the programs inside rather than a Dock icon. On Linux the `thinkterm-server` package does the same job for a machine you would rather manage with `apt` or `dnf`.

If a desktop app is already on the machine from somewhere else — a distro package, or an install under another prefix — the server variant leaves it alone, and it stays at its own version. Because `~/.local/bin` usually comes first on `PATH`, the app's launcher can then find the server install's `thinkterm`, which has no GUI, and stop working. The script mentions the app when it asks which variant you want and ends with a `!` item that says so; on a machine whose glibc can run the desktop variant, that item prints the command that installs the desktop variant under `~/.local` instead, which keeps the two together. A `thinkterm` with no GUI beside it answers a request for a window with `this ThinkTerm has no GUI to start` and points at `thinkterm tui`. The packages ThinkTerm builds start the app from `/usr/bin`, so a server install in `~/.local` does not take their place.

The session server needs no root and no service file. [Remote](https://docs.closex.org/thinkterm/remote.md) covers starting it, keeping it running after you log out, and what a host needs for each kind of connection.

## The first launch

The first time the app opens, setup appears as a tab in the main window. It opens with a short animation, the word *hello.* written out by hand, and the page is ready after about five seconds; until then it does not respond, and the animation cannot be skipped.

The page, `Welcome to ThinkTerm`, has two choices: `Language` and `Appearance` (`Follow System`, `Light` or `Dark`). Each applies as you pick it, so you see the result before deciding. `Get Started` keeps them; `Skip`, or the Escape key, puts back what ThinkTerm was using when the page opened. Either way the page does not come back. It also says, in so many words, *Nothing you open leaves this machine.*

When the computer has something to bring over, a `Found on this computer` card sits between the choices and the buttons, with one row for each of:

| Row | Button | Opens |
|---|---|---|
| Herdr sessions, and how many are running when some are — macOS and Linux only | `Import` | `Settings › Import` on Herdr |
| A WezTerm configuration file | `Import settings` | `Settings › Import` on WezTerm; see [Configuration](https://docs.closex.org/thinkterm/configuration.md#importing-from-wezterm) |
| Folders you opened in code editors, not yet in a local Space | `Choose projects` | `Settings › Import` on those folders |

A row is there only if its source is. The button keeps the language and appearance you picked, as `Get Started` does, ends the setup and opens that page with the source already chosen. Finding them happens on this computer; [Privacy and Security](https://docs.closex.org/thinkterm/privacy.md) says what is read.

Both can be changed later, under `Settings › General › Language` and `Settings › Appearance`.

A new installation also keeps local terminals in a background session server from the start, so quitting ThinkTerm does not end them. [Remote](https://docs.closex.org/thinkterm/remote.md) explains that setting and how to turn it off.

## Related

- [Updates](https://docs.closex.org/thinkterm/updates.md) — how an installed copy is kept up to date
- [Remote](https://docs.closex.org/thinkterm/remote.md) — preparing a host for SSH, Mosh and ThinkTerm Connect
- [Configuration](https://docs.closex.org/thinkterm/configuration.md) — where the config and data live on each system
- [Privacy and Security](https://docs.closex.org/thinkterm/privacy.md) — what ThinkTerm sends, and what it keeps
