Home

Awesome

Go Bullet Train (GBT)

Highly configurable prompt builder for Bash, ZSH and PowerShell written in Go. It's inspired by the Oh My ZSH Bullet Train theme but runs significantly faster.

Demo

GBT comes with an interesting feature called prompt forwarding which allows to forward user-defined prompt to a remote machine and have the same-looking prompt across all machines via SSH but also in Docker, Kubectl, Vagrant, MySQL or in Screen without the need to install anything remotely.

Prompt forwarding demo

All the above works well on Linux (Terminator, Konsole, Gnome Terminal), Mac (Terminal, iTerm), Android (Termux) and Windows (PowerShell, Windows Terminal).

Release Build status Coverage Status Packagecloud

Table of contents

Setup

In order to setup GBT on your machine, you have to install it, activate it and setup a special font in your terminal (optional).

Installation

Arch Linux

yaourt -S gbt

Or install gbt-git if you would like to run the latest greatest from the master branch.

CentOS/RHEL

Packages hosted by Packagecloud:

echo '[gbt]
name=GBT YUM repo
baseurl=https://packagecloud.io/gbt/release/el/7/$basearch
gpgkey=https://packagecloud.io/gbt/release/gpgkey
       https://packagecloud.io/gbt/release/gpgkey/gbt-release-4C6E79EFF45439B6.pub.gpg
gpgcheck=1
repo_gpgcheck=1' | sudo tee /etc/yum.repos.d/gbt.repo >/dev/null
sudo yum install gbt

Use the exact repository definition from above for all RedHat-based distribution regardless its version.

Ubuntu/Debian

Packages hosted by Packagecloud:

curl -L https://packagecloud.io/gbt/release/gpgkey | sudo apt-key add -
echo 'deb https://packagecloud.io/gbt/release/ubuntu/ xenial main' | sudo tee /etc/apt/sources.list.d/gbt.list >/dev/null
sudo apt-get update
sudo apt-get install gbt

Use the exact repository definition from above for all Debian-based distribution regardless its version.

Mac

Using brew:

brew tap jtyr/repo
brew install gbt

Or install gbt-git if you would like to run the latest greatest from the master branch:

brew tap jtyr/repo
brew install --HEAD gbt-git

Windows

Using choco:

choco install gbt

Using scoop:

scoop install gbt

Or manually by copying the gbt.exe file into a directory listed in the PATH environment variable (e.g. C:\Windows\system32).

Android

Install Termux from Google Play Store and then type this in the Termux app:

apt update
apt install gbt

From the source code

Make sure Go is installed and then run the following on Linux and Mac:

mkdir ~/go
export GOPATH=~/go
export PATH="$PATH:$GOPATH/bin"
go get github.com/jtyr/gbt/cmd/gbt

Or the following on Windows using PowerShell:

mkdir ~/go
$Env:GOPATH = '~/go'
$Env:PATH = "~/go/bin;$Env:PATH"
go install github.com/jtyr/gbt/cmd/gbt@latest

Activation

After GBT is installed, it can be activated by calling it from the shell prompt variable:

# For Bash
PS1='$(gbt $?)'

# For ZSH
PROMPT='$(gbt $?)'

If you are using ZSH together with some shell framework (e.g. Oh My ZSH), your shell is processing a fair amount of shell scripts upon ever prompt appearance. You can speed up your shell by removing the framework dependency from your configuration and replacing it with GBT and a simple ZSH configuration. Combining pure ZSH configuration with GBT will provide the best possible performance for your shell.

To activate GBT in PowerShell, run the following in the console or store it to the PowerShell profile file (echo $profile):

function prompt {
    $rc = [int]$(-Not $?)
    $Env:GBT_SHELL = 'plain'
    $Env:PWD = get-location
    $Env:GBT_CAR_CUSTOM_EXECUTOR='powershell.exe'
    $Env:GBT_CAR_CUSTOM_EXECUTOR_PARAM='-Command'
    $gbt_output = & @({gbt $rc},{gbt.exe $rc})[$PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows] | Out-String
    $gbt_output = $gbt_output -replace ([Environment]::NewLine + '$'), ''
    Write-Host -NoNewline $gbt_output
    return [char]0
}
# Needed only on Windows
[console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding

Fonts and colors

Although GBT can be configured to use only ASCII characters (see basic theme), the default configuration uses some UTF-8 characters which require special font. In order to display all characters of the default prompt correctly, the shell should support UTF-8 and Nerd fonts (or at least the DejaVuSansMono Nerd font) should be installed. On Linux, you can install it like this:

mkdir ~/.fonts
curl -L -o ~/.fonts/DejaVuSansMonoNerdFontCompleteMono.ttf https://github.com/ryanoasis/nerd-fonts/raw/master/patched-fonts/DejaVuSansMono/Regular/complete/DejaVu%20Sans%20Mono%20Nerd%20Font%20Complete%20Mono.ttf
fc-cache

On Mac, it can be installed via brew:

brew tap homebrew/cask-fonts
brew install --cask font-dejavu-sans-mono-nerd-font

On Windows, it can be installed via choco:

choco install font-nerd-DejaVuSansMono

Or via scoop:

scoop bucket add nerd-fonts
scoop install DejaVuSansMono-NF

Or just download the font, open it and then install it.

Once the font is installed, it has to be set in the terminal application to render all prompt characters correctly. Search for the font name DejaVuSansMono Nerd Font Mono on Linux and Mac and DejaVuSansMono NF on Windows.

In order to have the Nerd font in Termux on Android, you have to install Termux:Styling application. Then longpress the terminal screen and select MORE...StyleCHOOSE FONT and there choose the DejaVu font.

Some Unix terminals might not use 256 color palette by default. In such case try to set the following:

export TERM='xterm-256color'

Configuration

The prompt (train) is assembled from several elements (cars). The look and behavior of whole train as well as each car can be influenced by a set of environment variables. To set the environment variable, use export in the Linux and Mac shell and $Env: on Windows.

Colors

The value of all _BG and _FG variables defines the background and foreground color of the particular element. The value of the color can be specified in 3 ways:

Color name

Only a limited number of named colors is supported:

Examples:

# Set the background color of the `Dir` car to red
export GBT_CAR_DIR_BG='red'
# Set the foreground color of the `Dir` car to white
export GBT_CAR_DIR_FG='white'

Color number

Color can also by expressed by a single number in the range from 0 to 255. The color of each number in that range is visible in the 256-color lookup table on Wikipedia. The named colors described above are the first 16 numbers from the lookup table.

Examples:

# Set the background color of the `Dir` car to red
export GBT_CAR_DIR_BG='1'
# Set the foreground color of the `Dir` car to white
export GBT_CAR_DIR_FG='15'

RGB color

Arbitrary color can be expressed in the form of RGB triplet.

Examples:

# Set the background color of the `Dir` car to red
export GBT_CAR_DIR_BG='170;0;0'
# Set the foreground color of the `Dir` car to white
export GBT_CAR_DIR_FG='255;255;255'

Color scheme resistance

GBT is using 8-bit color palette to color individual cars of the train. First 16 colors (Standart and High-intensity colors) of the palette are prone to a change if the terminal is using some color scheme (e.g. Solarized). That means that if one GBT train uses mixture of the first 16 and the remaining 240 colors, the look might be inconsistent because some of the colors might change (depending on the color scheme) and some not. Luckily the first 16 colors can be found in the remaining 240 colors and therefore GBT can automatically convert the first 16 colors into higher colors which provides consistent look regardless the color scheme. This works automatically for color names as well as for color numbers. If needed, the automatic conversion can be disabled with the following variable:

export GBT_FORCE_HIGHER_COLORS='0'

Formatting

Formatting is done via _FM variables. The possible values are:

Train variables

Cars variables

Aws car

Car that displays information about the local AWS configuration.

Azure car

Car that displays information about the local Azure configuration.

Custom car

The main purpose of this car is to provide the possibility to create car with custom text.

Multiple Custom cars can be used in the GBT_CARS variable. Just add some identifier behind the car name. To set properties of the new car, just add the same identifier into the environment variable:

# Adding Custom and Custom1 car
export GBT_CARS='Status, Os, Custom, Custom1, Hostname, Dir, Git, Sign'
# The text of the default Custom car
export GBT_CAR_CUSTOM_TEXT_TEXT='default'
# The text of the Custom1 car
export GBT_CAR_CUSTOM1_TEXT_TEXT='1'
# Set different background color for the Custom1 car
export GBT_CAR_CUSTOM1_BG='magenta'

Dir car

Car that displays current directory name.

ExecTime car

Car that displays how long each shell command run.

In order to allow this car to calculate the execution time, the following must be loaded in the shell:

# For Bash
source /usr/share/gbt/sources/exectime/bash.sh
# For ZSH
source /usr/share/gbt/sources/exectime/zsh.sh

On macOS the date command does not support %N format for milliseconds and you need to override the environment variable GBT__SOURCE_DATE_ARG='+%s.

Gcp car

Car that displays information about the local GCP configuration.

Git car

Car that displays information about a local Git repository. This car is displayed only if the current directory is a Git repository.

Hostname car

Car that displays username of the currently logged user and the hostname of the local machine.

Kubectl car

Car that displays kubectl information.

Os car

Car that displays icon of the operating system.

PyVirtEnv car

Car that displays Python Virtual Environment name. This car is displayed only if the Python Virtual Environment is activated. The activation script usually prepends the shell prompt by the Virtual Environment name by default. In order to disable it, the following environment variable must be set:

export VIRTUAL_ENV_DISABLE_PROMPT='1'

Variables used by the car:

Sign car

Car that displays prompt character for the admin and user at the end of the train.

Status car

Car that visualizes return code of every command. By default, this car is displayed only when the return code is non-zero. If you want to display it even if the return code is zero, set the following variable:

export GBT_CAR_STATUS_DISPLAY='1'

Variables used by the car:

Time car

Car that displays current date and time.

Benchmark

Benchmark of GBT can be done by faking the output of GBT by a testing script which executes as minimum of commands as possible. For simplicity, the test will produce output of the Git car only and will be done from within a directory with a Git repository.

The testing script is using exactly the same commands like GBT to determine the Git branch, whether the Git repository contains any change and whether it's ahead/behind of the remote branch. The script has the following content and is stored in /tmp/test.sh:

BRANCH="$(git symbolic-ref HEAD)"
[ -z "$(git status --porcelain)" ] && DIRTY_ICON='%{\e[38;5;2m%}✔' || DIRTY_ICON='%{\e[38;5;1m%}✘'
[[ "$(git rev-list --count HEAD..@{upstream})" == '0' ]] && AHEAD_ICON='' || AHEAD_ICON=' ⬆'
[[ "$(git rev-list --count @{upstream}..HEAD)" == '0' ]] && BEHIND_ICON='' || BEHIND_ICON=' ⬇'

echo -en "%{\e[0m%}%{\e[48;5;7m%}%{\e[38;5;0m%} %{\e[48;5;7m%}%{\e[38;5;0m%}%{\e[48;5;7m%}%{\e[38;5;0m%} %{\e[48;5;7m%}%{\e[38;5;0m%}${BRANCH##*/}%{\e[48;5;7m%}%{\e[38;5;0m%} %{\e[48;5;7m%}%{\e[38;5;0m%}%{\e[48;5;7m%}$DIRTY_ICON%{\e[48;5;7m%}%{\e[38;5;0m%}%{\e[48;5;7m%}%{\e[38;5;0m%}%{\e[48;5;7m%}%{\e[38;5;0m%}$AHEAD_ICON%{\e[48;5;7m%}%{\e[38;5;0m%}%{\e[48;5;7m%}%{\e[38;5;0m%}$BEHIND_ICON%{\e[48;5;7m%}%{\e[38;5;0m%} %{\e[0m%}"

The testing script produces the same output like GBT when run by Bash or ZSH:

bash /tmp/test.sh > /tmp/a
zsh /tmp/test.sh > /tmp/b
GBT_SHELL='zsh' GBT_CARS='Git' gbt > /tmp/c
diff /tmp/{a,b}
diff /tmp/{b,c}

We will use ZSH to run 10 measurements of 100 executions of the testing script by Bash and ZSH as well as of GBT itself.

# Execution of the testing script by Bash
for N in $(seq 10); do time (for M in $(seq 100); do bash /tmp/test.sh 1>/dev/null 2>&1; done;) done 2>&1 | sed 's/.*  //'
0.95s user 1.05s system 102% cpu 1.944 total
0.94s user 1.06s system 102% cpu 1.944 total
0.93s user 1.05s system 102% cpu 1.930 total
0.91s user 1.10s system 102% cpu 1.954 total
0.92s user 1.07s system 102% cpu 1.933 total
0.97s user 1.03s system 102% cpu 1.943 total
0.92s user 1.07s system 102% cpu 1.931 total
0.92s user 1.08s system 102% cpu 1.949 total
0.89s user 1.11s system 102% cpu 1.938 total
0.93s user 1.07s system 102% cpu 1.944 total
# Execution of the testing script by ZSH
for N in $(seq 10); do time (for M in $(seq 100); do zsh /tmp/test.sh 1>/dev/null 2>&1; done;) done 2>&1 | sed 's/.*  //'
0.89s user 1.08s system 103% cpu 1.909 total
0.82s user 1.15s system 103% cpu 1.906 total
0.82s user 1.15s system 103% cpu 1.903 total
0.84s user 1.13s system 103% cpu 1.907 total
0.88s user 1.10s system 103% cpu 1.915 total
0.88s user 1.09s system 103% cpu 1.907 total
0.84s user 1.14s system 103% cpu 1.919 total
0.85s user 1.11s system 103% cpu 1.901 total
0.89s user 1.08s system 103% cpu 1.914 total
0.96s user 1.01s system 103% cpu 1.908 total
# Execution of GBT
for N in $(seq 10); do time (for M in $(seq 100); do GBT_SHELL='zsh' GBT_CARS='Git' gbt 1>/dev/null 2>&1; done;) done 2>&1 | sed 's/.*  //'
1.03s user 1.19s system 115% cpu 1.922 total
0.98s user 1.18s system 115% cpu 1.874 total
1.06s user 1.11s system 115% cpu 1.880 total
1.02s user 1.14s system 115% cpu 1.867 total
1.04s user 1.17s system 115% cpu 1.918 total
1.05s user 1.10s system 115% cpu 1.853 total
1.07s user 1.11s system 115% cpu 1.895 total
1.01s user 1.18s system 115% cpu 1.903 total
1.08s user 1.03s system 115% cpu 1.825 total
1.05s user 1.09s system 115% cpu 1.844 total

From the above is visible that GBT performs faster than Bash and ZSH even if the testing script was as simple as possible. You can also notice that GBT was using more CPU than Bash or ZSH. That's probably because of the built-in concurrency support in Go.

Prompt forwarding

In order to enjoy GBT prompt via SSH but also in Docker, Kubectl, Vagrant, MySQL or in Screen without the need to install GBT everywhere, you can use GBTS (GBT written in Shell). GBTS is a set of scripts which get forwarded to applications and remote connections and then executed to generate the nice looking prompt.

You can start using it by doing the following:

export GBT__HOME='/usr/share/gbt'
source $GBT__HOME/sources/gbts/cmd/local.sh

This will automatically create command line aliases for all enabled plugins (by default docker, gssh, kubectl, mysql, screen, ssh, su, sudo and vagrant). Then just SSH to some remote server or enter some Docker container (even via kubectl) or Vagrant box and you should get GBT prompt there.

If you want to have some of the default aliase available only on the remote site, just un-alias them locally:

unalias sudo su

You can also forward your own aliases which will be then available on any remote site. For example to have alias ll='ls -l' on any remote site, just create the following alias and it will be automatically forwarded:

alias gbt___ll='ls -l'

The idea behind prompt forwarding is coming from Vladimir Babichev (@mrdrup) who was using it for several years before GBT even existed. After seeing the potential of GBT, he sparked the implementation of prompt forwarding into GBT which later turned into GBTS.

Principle

Principle of GBTS is to pass the GBTS scripts to the application and then execute them. This is done by concatting all the GBTS scripts into one file and encoding it by Base64 algorithm. Such string, together with few more commands, is then used as an argument of the application which makes it to store it on the remote site in the /tmp/.gbt.<NUM> file. The same we create the /tmp/.gbt.<NUM>.bash script which is then used as a replacement of the real shell on the remote site. For SSH it would look like this:

ssh -t myserver "export GBT__CONF='$GBT__CONF' && echo '<BASE64_ENCODED_STRING>' | base64 -d > \$GBT__CONF && bash --rcfile \$GBT__CONF"

In order to make all this invisible, we wrap that command into a function (e.g. gbt_ssh) and assign it to an alias of the same name like the original application (e.g. ssh):

alias ssh='gbt_ssh'

The same or very similar principle applies to other supported commands like docker, gssh (GCP SSH), kubectl, mysql, screen, su, sudo and vagrant.

Additional settings

GBTS has few settings which can be used to influence its behaviour. See the details here.

MacOS users

To make GBTS working correctly between Linux and MacOS and vice versa requires a little bit of fiddling. The reason is that the basic command line tools like date and base64 are very old on MacOS and mostly incompatible with the Linux world. Some tools are even called differently (e.g. md5sum is called md5).

Therefore if you want to make the remote script verification working (make sure nobody changed the remote script while using it), the following variables must be set:

# Use 'md5' command instead of 'md5sum'
export GBT__SOURCE_MD5_LOCAL='md5'
# Cut the 4th field from the output of 'md5'
export GBT__SOURCE_MD5_CUT_LOCAL='4'

If you don't want to use this feature, you can disable it in which case the above variables won't be required:

export GBT__SOURCE_SEC_DISABLE=1

When using the ExecTime car, the following variable must be set:

# Don't use nanoseconds in the 'ExecTime' car
export GBT__SOURCE_DATE_ARG='+%s'

For maximum compatibility with GBT, it's recommended to install GNU coreutils (brew install coreutils) and instead of the variable above use these:

# Use 'gdate' instead of 'date'
export GBT__SOURCE_DATE='gdate'
# Use 'gdate' instead of 'date' (only if you run GBT on a Mac)
export GBT__SOURCE_BASE64_LOCAL='gbase64'
# Use 'gdate' instead of 'date' (only if you are connection to Mac via SSH)
export GBT__SOURCE_BASE64='gbase64'

When connecting to MacOS from Linux using gbt_ssh and not using gbase64 on MacOS, the following variable must be set on Linux to make the Base64 decoding working on MacOS:

# Use 'base64 -D' to decode Base64 encoded text
export GBT__SOURCE_BASE64_DEC='-D'

Limitations

TODO

Contribution to the following is more than welcome:

Author

Jiri Tyr

License

MIT