Featured image of post Convenient and Colorful ZSH with p10k

Convenient and Colorful ZSH with p10k

My experience with Powerlevel10k for ZSH

Introduction

Previously, I described my experience with ZSH here. Now, things are a bit different; I have fully switched to Powerlevel10k.

It’s a very beautiful and convenient shell prompt that is easy to install and configure on the fly.

Configuring p10k

You can configure p10k very easily, right on the go, even ten times a day.

1
p10k configure

I have embedded the installation of p10k directly in my .zshrc, so you don’t even have to follow the official instructions on the website. Well, almost. There are recommended fonts to ensure everything looks good, which are described here. Below, I recorded and showed what the configuration of p10k looks like. Unfortunately, the asciinema recording shows the beautiful elements as squares since these symbols are not supported in the recording. So, here’s a screenshot of the terminal window.

(Click the image to enlarge)

While the recording didn’t turn out very well, at least I can show you the menus.

Explanation of my .zshrc

Everything written below consists of snippets from my .zshrc, which you can download at the end of the article. For your convenience, I will duplicate the link here.

Installing p10k

This is done with a simple condition and direct cloning from GitHub. If we have Git and p10k is not yet installed, we can clone it.

1
2
3
4
if command -v git >/dev/null && [ ! -d ~/powerlevel10k ]; then
  echo "info: installing p10k"
  git clone --depth 1 --single-branch https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
fi

Plugins

I install plugins simply by cloning them from GitHub. I added comments directly in the code for clarification.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# if we have git installed
if command -v git >/dev/null; then
  # and folder with plugins is absent
  if [ ! -d /usr/share/zsh/plugins ]; then
    # let's create it with sudo
    sudo mkdir -p /usr/share/zsh/plugins
  fi
  # and jump in
  cd /usr/share/zsh/plugins
  # now, let's read every line between <<EOF and EOF lines below
  while IFS= read -r REPO; do
    # and for every line, which will be available in variable named REPO
    # let's cut only last part of the url (ex: zsh-autosuggestions.git) and save to BASENAME var
    BASENAME="$(basename "$REPO")"
    # now, we will remove .git from the end of BASENAME and check either such folder exists
    if [ ! -d "${BASENAME%.git}" ]; then
      # if not, let's print an info message
      echo "${REPO}: Cloning to $(pwd)/${BASENAME%.git}"
      # and finally clone that plugin
      sudo git clone --depth 1 --single-branch "$REPO"
    fi
  done <<EOF
https://github.com/zsh-users/zsh-history-substring-search.git
https://github.com/zsh-users/zsh-syntax-highlighting.git
https://github.com/zsh-users/zsh-autosuggestions.git
EOF
  # after all plugins cloned, we cd back to the location we were
  cd - >/dev/null
fi
# okay, now we assume that all plugins are cloned and we have to include them
# once again, for every hardcoded path to .zsh file
while IFS= read -r SOURCE; do
  # we skip the plugin if it starts with '#' (commented out)
  if echo "$SOURCE" | grep -qE '^#'; then
    continue 
  elif [ -s "$SOURCE" ]; then
    # if it is enabled, we just include it in current shell session with source command
    source "$(realpath "$SOURCE")"
  fi
done <<EOF
/usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
/usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
/usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
EOF

Adding Paths to PATH

Often, we install additional binaries in non-standard folders. For example, I use ~/.local/bin for various tools like kubectl, helm, k9s, and others. Here, we check each folder, if it exists, and add it to the PATH variable.

1
2
3
4
5
6
7
8
9
while IFS= read -r P; do
  if [ -d "$P" ]; then
    export PATH="${P}:${PATH}"
  fi
done <<EOF
${HOME}/.local/share/gem/ruby/3.0.0/bin
${HOME}/projects/stm32/gcc-arm-none-eabi-10.3-2021.10/bin
${HOME}/.local/bin
EOF

Aliases

Here everything is straightforward, with lots of different aliases.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
alias ..="cd .. && ls -lhA --color=auto"
alias d='sudo -E docker'
alias dc="docker-compose" 
alias e="exit"
alias j="sudo journalctl"
alias l="ls -lh --color=always"
alias la="ls -lhA --color=always"
alias p="ping 8.8.8.8"
alias please='sudo'
alias reborn="sudo shutdown -r now"
alias s='sudo'
alias sctl="sudo systemctl"
alias svi="sudo vim"
alias svim="sudo vim"
alias t='terraform'
alias k="kubectl"
alias kd="kubectl describe"
alias kg="kubectl get"
alias ky="kubectl get -o yaml"
alias ke='kubectl exec -it'
alias vi="vim"
alias g="git"
alias b="sudo btrfs"
alias c="code ."
alias ansible-time="time ansible-playbook"

Git Config

By the way, I also have a Git config. The link is here or the same at the end of the page. Two interesting commands here:

  1. git ps - pushes the branch, and if it doesn’t exist yet, it catches the message from the Git error log and pushes with –set-upstream.
  2. git dpush (dummy push) - adds all files and folders to the commit, writes a standard commit message, and pushes.
  3. git cc - simply makes a commit with a standard message.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[alias]
co = checkout
a = add .
ci = commit
cc = commit -m 'Nothing special. Just regular commit.'
ps = ! sh -c 'git push >/tmp/git-ps 2>&1 || grep -- "git push --set-upstream" /tmp/git-ps | sh'
pt = push --tags
pts = ! sh -c 'git push && git push --tags'
pl = pull
st = status
br = branch
hist = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
type = cat-file -t
dump = cat-file -p
dpush = ! sh -c 'git add . && git commit -m "Nothing special. Just regular commit."' && git push

KUBECONFIG

Here, we look for the standard kubeconfig for k3s or rke2 and set it in KUBECONFIG.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# if there is no file at the path that is already being set in KUBECONFIG var
if [ -z "$KUBECONFIG" ]; then
  # for every filepath from the list below
  while IFS= read -r K; do
    # check if file exists and it is not empty
    if [ -s "$K" ]; then
      # change owner and group to current user
      if ! stat -c '%u:%g' "$K" | grep -qF "$(id -u):$(id -g)"; then
        sudo chown "${USER}:${USER}" "$K"
        echo "info

: chown $K"
      fi
      # assign it to the KUBECONFIG variable
      export KUBECONFIG="${K}:${KUBECONFIG}"
    fi
  done <<EOF
/etc/rancher/rke2/rke2.yaml
~/.kube/config
~/.kube/k3s.yaml
EOF
fi

Here’s the translated content of your Hugo page section into English, formatted in valid Markdown:

Automatic Utility Downloads

Here’s what’s happening: .zshrc automatically checks to ensure that we have the binaries of the utilities we use installed. If the utility is missing, it downloads it; if not, it does nothing. However, I can manually update k9s by executing _update_k9s. This section installs the following utilities:

  1. k9s - more details in my article
  2. kubepug - a tool for checking deprecated APIs in the cluster before upgrading the Kubernetes version
  3. kubecolor - a wrapper around kubectl for colorful output
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
_update_kubepug() {
  mkdir -p ~/.local/bin
  LOCATION="$(curl --connect-timeout 1 -sS https://github.com/rikatz/kubepug/releases/latest -D- | awk '/^location:/{print $2}')"
  LATEST_VERSION="$(echo "$LOCATION" | awk -F/ '{print $NF}' | grep -oE '[0-9a-zA-Z_.-]+')"
  if ! k9s version --short | grep -qF "$LATEST_VERSION"; then
    printf "Updating kubepug to %s\n" "$LATEST_VERSION"
    printf 'https://github.com/rikatz/kubepug/releases/download/%s/kubepug_linux_%s.tar.gz' \
      "$LATEST_VERSION" "$(uname -p | sed 's/unknown/amd64/g' | tr -d '[[:space:]]')" | \
    xargs curl -sSL | \
    tar -xpzf - kubepug
    install kubepug ~/.local/bin/kubepug -m 0755
    rm -f kubepug
  fi
}
if ! command -v kubepug &>/dev/null; then
  _update_kubepug
fi

_update_k9s() {
  mkdir -p ~/.local/bin
  LOCATION="$(curl --connect-timeout 1 -sS https://github.com/derailed/k9s/releases/latest -D- | awk '/^location:/{print $2}')"
  LATEST_VERSION="$(echo "$LOCATION" | awk -F/ '{print $NF}' | grep -oE '[0-9a-zA-Z_.-]+')"
  if ! k9s version --short | grep -qF "$LATEST_VERSION"; then
    printf "Updating k9s to %s\n" "$LATEST_VERSION"
    printf 'https://github.com/derailed/k9s/releases/download/%s/k9s_Linux_%s.tar.gz' \
      "$LATEST_VERSION" "$(uname -p | sed 's/unknown/x86_64/g' | tr -d '[[:space:]]')" | \
    xargs curl -sSL | \
    tar -xpzf - k9s
    install k9s ~/.local/bin/k9s -m 0755
    rm -f k9s
  fi
}
if ! command -v k9s &>/dev/null; then
  _update_k9s
fi

_update_kubecolor() {
  mkdir -p ~/.local/bin
  LOCATION="$(curl --connect-timeout 1 -sS https://github.com/hidetatz/kubecolor/releases/latest -D- | awk '/^location:/{print $2}')"
  LATEST_VERSION="$(echo "$LOCATION" | awk -F/ '{print $NF}' | grep -oE '[0-9a-zA-Z_.-]+' | sed 's/^v//g')"
  if ! kubecolor --kubecolor-version | grep -qF "$LATEST_VERSION"; then
    printf "Updating kubecolor to %s\n" "$LATEST_VERSION"
    printf 'https://github.com/hidetatz/kubecolor/releases/download/%s/kubecolor_%s_Linux_%s.tar.gz' \
      "v$LATEST_VERSION" "$LATEST_VERSION" "$(uname -p | sed 's/unknown/x86_64/g' | tr -d '[[:space:]]')" | \
    xargs curl -sSL | \
    tar -xpzf - kubecolor
    install kubecolor ~/.local/bin/kubecolor -m 0755
    rm -f kubecolor
  fi
}
if ! command -v kubecolor &>/dev/null; then
  _update_kubecolor
  alias kubectl="kubecolor"
  typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND="${POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND}|kubecolor"
fi

Additionally, for several commands, if they exist, automatic loading of completion scripts is performed so that zsh knows what to suggest.

1
2
3
4
5
6
## Completions for 3rd-party binaries
for COMMAND in kubectl helm k3d flux kubepug; do
  if command -v "$COMMAND" >/dev/null; then
    source <("$COMMAND" completion zsh)
  fi
done

Separate Environments

This is the smartest and at the same time simplest trick in my arsenal. The most valuable thing in work is that when you work on different projects/environments, the scariest thing you can do is to break something. The easiest way to do that is forgetting to switch to the desired environment and apply something where you didn’t intend.

This snippet of code prevents exactly that.

1
2
3
if [ -f env.sh ]; then
  source ./env.sh
fi

It’s that simple. What actually happens? When zsh starts, it checks the directory it was launched from, and if there’s a file named exactly env.sh, it includes it in the context. Let me explain.

I use Ranger for navigation through folders from the terminal. Let’s say I’m working on a project and also have a blog. I created the following directories for my projects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.
├── MyBlog
│   ├── env.sh
│   └── kubeconfig.yaml
└── SuperWorkProject
    ├── development
    │   ├── env.sh
    │   └── kubeconfig.yaml
    └── production
        ├── env.sh
        └── kubeconfig.yaml

4 directories, 6 files

In my production project, I have two environments, all running Kubernetes, each with its separate kubeconfig.yaml. Let’s say I need to work on production. In its env.sh, I will write the following.

1
2
3
4
5
6
7
8
#!/usr/bin/env sh

export KUBECONFIG='~/projects/SuperWorkProject/production/kubeconfig.yaml'
export AWS_PROFILE='production'

pf() {
  nohup sh -c 'kubectl get po -n monitoring -l app.kubernetes.io/name=grafana -o name | xargs -I{} kubectl port-forward {} 3000' 1>/dev/null &
}

Now, I just open zsh in this folder or run source ./env.sh. That’s it; now I’m sure I’m working with production. Additionally, I declared a function for port-forwarding.

Licensed under Apache License, Version 2.0
Last updated on Oct 17, 2024 12:47 +0300
All rights reserved
Built with Hugo
Theme Stack designed by Jimmy