Автоматизация установки Zsh с Oh-My-Zsh и темой Jovial через Ansible
СводимПолное
существующиерешениехост-группыдляAnsibleмассовойв одну общую через:children.Разворачиваемzsh,Oh-My-Zsh, темуJovialустановки иплагинынастройки(zsh-autosuggestions,zsh-syntax-highlighting,zsh-history-enquirer, а также встроенныйbgnotify) на всех целевых хостах.Кладём единый.zshrc, в котором:первая строка-сторож: исполнять только подzsh,аккуратные алиасы подezaиbat/batcatс безопасными fallback’ами,
Для VS Code (Remote-SSH) выбираемzshкак дефолтный терминал и не позволяемbashисполнять.zshrc.
1) Задача и подводные камни
Задача: получитьZsh на нескольких серверах единообразныйс интерактивныйучётом опытвсех в терминале: zsh + Oh-My-Zsh + тема Jovial + нужные плагинынюансов и алиасы.подводных Сделать это повторяемо и прозрачно через Ansible.камней.
Подводные
Структура камни,проекта
ansible-zsh/
встретились├── наinventories/
практике:│ От└──имениinventory.iniкакого├──пользователяgroup_vars/выполняется│конфигурация:└──из-заzsh_targets.ymlbecome:├──yesplaybooks/ │ └── zsh_deploy.yml └── README.mdможно ненароком раскатывать всё в/rootвместо/home/user.VS Code Remote-SSH может поднимать процессыbash; если.zshrcвдруг исполняется bash’ем — посыплются ошибки.В Ansible легко поймать «мелкие» ошибки: несовместимые параметры модулей, неверные отступы YAML.Порядок подключения плагинов важен:zsh-syntax-highlightingдолжен бытьпоследним.
Дальше — минималистичное и явное решение без переменных-шаблонов.
2)1. Inventory: одна общая группа из несколькихinventories/inventory.ini
Объединяем ваши темы хостов (docker, vpn, zud) в общую группу zsh_targets:
# === INVENTORY ===
# Замените примеры на реальные данные ваших серверов
[docker]
n-1-dsw ansible_host=203.0.113.11 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_dsw1
n-2-dsw ansible_host=203.0.113.12 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_dsw2
n-3-dsw ansible_host=203.0.113.13 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_dsw3
[vpn]
srv-1 ansible_host=198.51.100.21 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_vpn
[zud]
zud-1 ansible_host=192.0.2.41 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_zud1
zud-2 ansible_host=192.0.2.42 ansible_user=myuser ansible_ssh_private_key_file=~/.ssh/id_rsa_zud2
# Объединяем все группы в одну для удобства
[zsh_targets:children]
docker
vpn
zud
[all:vars]
ansible_python_interpreter=/usr/bin/python3
2. Переменные: group_vars/zsh_targets.yml
---
# === ПЕРЕМЕННЫЕ (CHANGEИЗМЕНИТЕ MEПОД гдеСЕБЯ) отмечено)===
# Пользователь для которого настраивается zsh
zsh_user: myuser
# Домашняя директория (будет определена автоматически, но можно задать явно)
# zsh_home: /home/myuser
# Путь к zsh (определяется автоматически)
# zsh_shell_path: /usr/bin/zsh
# Создавать ли бэкап существующего .zshrc
zsh_backup_config: true
# Версии плагинов (ветки в git)
zsh_omz_version: master
zsh_plugins_version: master
# Редактор по умолчанию
zsh_editor: nano
# Для VS Code раскомментируйте:
# zsh_editor: "code --wait --reuse-window"
3. Playbook: playbooks/zsh_deploy.yml
---
- name: Install and configure Zsh with Oh-My-Zsh and Jovial theme
hosts: zsh_targets
gather_facts: yes
become: yes
vars:
# Домашняя директория пользователя (определяется автоматически)
zsh_home: "{{ ansible_facts['user_dir'] | default('/home/' + zsh_user) }}"
tasks:
# ======================================
# Везде, где указано user и /home/user — замени на своего реального пользователя
# и его домашний каталог. Примеры ниже — ЭТО ШАБЛОН.
[docker]
n-1-dsw ansible_host=203.0.113.11 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_dsw1 # CHANGE ME: user → твой логин; /home/user → твой HOME; путь к ключу → твой ключ
n-2-dsw ansible_host=203.0.113.12 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_dsw2 # CHANGE ME
n-3-dsw ansible_host=203.0.113.13 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_dsw3 # CHANGE ME
[vpn]
srv-1 ansible_host=198.51.100.21 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_vpn # CHANGE ME
[zud]
zud-1 ansible_host=192.0.2.41 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_zud1 # CHANGE ME
zud-2 ansible_host=192.0.2.42 ansible_user=user ansible_ssh_private_key_file=/home/user/.ssh/id_rsa_zud2 # CHANGE ME
# Общая группа-«зонтик» для раскатки zsh
[zsh_targets:children]
docker
vpn
zud
[all:vars]
ansible_python_interpreter=/usr/bin/python3Комментарий: всё явно — логин user, приватные ключи в конкретных путях.
3) .zshrc пример:
Ключевые особенности:
Строчка-сторожв самом верху: исполняем файл только вzsh(это предотвращает ошибки, когдаbashслучайно «подхватывает».zshrc, например в VS Code).Плагинzsh-syntax-highlighting—строго последним.
# === ~/.zshrc (CHANGE ME где отмечено) =======================================
# СразуЭТАП после1: вставкиПРОВЕРКИ поменяйИ ПОДГОТОВКА
# ============================================================================
- name: Ensure user exists
ansible.builtin.user:
name: "{{ zsh_user }}"
state: present
create_home: yes
register: user_info
- name: Get user home directory
ansible.builtin.getent:
database: passwd
key: "{{ zsh_user }}"
register: user_getent
- name: Set zsh_home fact
ansible.builtin.set_fact:
zsh_home: "{{ user_getent.ansible_facts.getent_passwd[zsh_user][4] }}"
- name: Display configuration info
ansible.builtin.debug:
msg:
- "Target user: {{ zsh_user }}"
- "Home directory: {{ zsh_home }}"
- "OS family: {{ ansible_os_family }}"
# ============================================================================
# ЭТАП 2: УСТАНОВКА ПАКЕТОВ
# ============================================================================
- name: Install base packages (Debian/Ubuntu)
ansible.builtin.apt:
name:
- zsh
- git
- curl
- ca-certificates
- autojump
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Install base packages (RedHat/CentOS/Fedora)
ansible.builtin.dnf:
name:
- zsh
- git
- curl
- ca-certificates
- autojump-zsh
state: present
when: ansible_os_family == "RedHat"
- name: Install base packages (Arch Linux)
community.general.pacman:
name:
- zsh
- git
- curl
- ca-certificates
- autojump
state: present
when: ansible_os_family == "Archlinux"
# eza installation
- name: Check if eza is available in repositories (Debian/Ubuntu)
ansible.builtin.shell: apt-cache show eza
register: eza_available
failed_when: false
changed_when: false
when: ansible_os_family == "Debian"
- name: Install eza from package manager (Debian/Ubuntu)
ansible.builtin.apt:
name: eza
state: present
when:
- ansible_os_family == "Debian"
- eza_available.rc == 0
ignore_errors: yes
- name: Install eza (RedHat/Fedora)
ansible.builtin.dnf:
name: eza
state: present
when: ansible_os_family == "RedHat"
ignore_errors: yes
- name: Install eza (Arch Linux)
community.general.pacman:
name: eza
state: present
when: ansible_os_family == "Archlinux"
ignore_errors: yes
# bat installation
- name: Install bat (Debian/Ubuntu)
ansible.builtin.apt:
name: bat
state: present
when: ansible_os_family == "Debian"
ignore_errors: yes
- name: Create symlink for batcat (Debian/Ubuntu)
ansible.builtin.file:
src: /home/usr/bin/batcat
dest: /usr/local/bin/bat
state: link
when:
- ansible_os_family == "Debian"
ignore_errors: yes
- name: Install bat (RedHat/Fedora)
ansible.builtin.dnf:
name: bat
state: present
when: ansible_os_family == "RedHat"
ignore_errors: yes
- name: Install bat (Arch Linux)
community.general.pacman:
name: bat
state: present
when: ansible_os_family == "Archlinux"
ignore_errors: yes
# ============================================================================
# ЭТАП 3: ОПРЕДЕЛЕНИЕ ПУТИ К ZSH И УСТАНОВКА КАК SHELL ПО УМОЛЧАНИЮ
# ============================================================================
- name: Find zsh binary path
ansible.builtin.command: command -v zsh
register: zsh_path_result
changed_when: false
- name: Set zsh_shell_path fact
ansible.builtin.set_fact:
zsh_shell_path: "{{ zsh_path_result.stdout }}"
- name: Display zsh path
ansible.builtin.debug:
msg: "Zsh binary found at: {{ zsh_shell_path }}"
- name: Set zsh as default shell for user
наansible.builtin.user:
твойname: реальный"{{ home-каталог!zsh_user }}"
shell: "{{ zsh_shell_path }}"
# ============================================================================
# ЭТАП 4: УСТАНОВКА OH-MY-ZSH
# ============================================================================
- name: Backup existing .zshrc if exists
ansible.builtin.copy:
src: "{{ zsh_home }}/.zshrc"
dest: "{{ zsh_home }}/.zshrc.backup.{{ ansible_date_time.epoch }}"
remote_src: yes
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0644"
when: zsh_backup_config | bool
failed_when: false
- name: Check if Oh-My-Zsh is already installed
ansible.builtin.stat:
path: "{{ zsh_home }}/.oh-my-zsh"
register: omz_dir
- name: Create Oh-My-Zsh directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh"
state: directory
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0755"
when: not omz_dir.stat.exists
- name: Clone Oh-My-Zsh repository
ansible.builtin.git:
repo: https://github.com/ohmyzsh/ohmyzsh.git
dest: "{{ zsh_home }}/.oh-my-zsh"
version: "{{ zsh_omz_version }}"
update: yes
force: no
become_user: "{{ zsh_user }}"
register: omz_clone
# ============================================================================
# ЭТАП 5: СОЗДАНИЕ ДИРЕКТОРИЙ ДЛЯ КАСТОМНЫХ ПЛАГИНОВ И ТЕМ
# ============================================================================
- name: Create custom plugins directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh/custom/plugins"
state: directory
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0755"
- name: Create custom themes directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh/custom/themes"
state: directory
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0755"
# ============================================================================
# ЭТАП 6: УСТАНОВКА ПЛАГИНОВ
# ============================================================================
- name: Clone zsh-autosuggestions plugin
ansible.builtin.git:
repo: https://github.com/zsh-users/zsh-autosuggestions.git
dest: "{{ zsh_home }}/.oh-my-zsh/custom/plugins/zsh-autosuggestions"
version: "{{ zsh_plugins_version }}"
update: yes
force: no
become_user: "{{ zsh_user }}"
- name: Clone zsh-syntax-highlighting plugin
ansible.builtin.git:
repo: https://github.com/zsh-users/zsh-syntax-highlighting.git
dest: "{{ zsh_home }}/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting"
version: "{{ zsh_plugins_version }}"
update: yes
force: no
become_user: "{{ zsh_user }}"
- name: Clone zsh-history-enquirer plugin
ansible.builtin.git:
repo: https://github.com/zthxxx/zsh-history-enquirer.git
dest: "{{ zsh_home }}/.oh-my-zsh/custom/plugins/zsh-history-enquirer"
version: "{{ zsh_plugins_version }}"
update: yes
force: no
become_user: "{{ zsh_user }}"
- name: Clone jovial plugin/theme
ansible.builtin.git:
repo: https://github.com/zthxxx/jovial.git
dest: "{{ zsh_home }}/.oh-my-zsh/custom/plugins/jovial"
version: "{{ zsh_plugins_version }}"
update: yes
force: no
become_user: "{{ zsh_user }}"
# ============================================================================
# ЭТАП 7: УСТАНОВКА ТЕМЫ JOVIAL
# ============================================================================
- name: Create symlink for jovial theme
ansible.builtin.file:
src: "{{ zsh_home }}/.oh-my-zsh/custom/plugins/jovial/jovial.zsh-theme"
dest: "{{ zsh_home }}/.oh-my-zsh/custom/themes/jovial.zsh-theme"
state: link
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
force: yes
# ============================================================================
# ЭТАП 8: СОЗДАНИЕ .ZSHRC
# ============================================================================
- name: Install .zshrc configuration
ansible.builtin.copy:
dest: "{{ zsh_home }}/.zshrc"
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0644"
content: |
# === GUARD: выполнять только в zsh ===
[ -n "$ZSH_VERSION" ] || return
# не=== исполнятьOh-My-Zsh .zshrc в bash/VS Code===
export ZSH="{{ zsh_home }}/home/user/.oh-my-zsh"
# CHANGE ME: /home/user → твой HOME
ZSH_THEME="jovial"
# === Плагины ===
# ВАЖНО: zsh-syntax-highlighting должен быть последним!
plugins=(
git
autojump
bgnotify
zsh-history-enquirer
zsh-autosuggestions
jovial
zsh-syntax-highlighting
# должен быть ПОСЛЕДНИМ
)
source "/home/user/.oh-my-zsh/$ZSH/oh-my-zsh.sh"
# CHANGE=== ME:Настройки /home/userJovial → твой HOME===
typeset -g JOVIAL_EXEC_THRESHOLD_SECONDS=1
# === Терминал ===
export TERM="xterm-256color"
export COLORTERM="truecolor"
# === Локаль (для корректного отображения emoji) ===
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
# === История команд ===
HISTFILE=~/.zsh_history
HISTSIZE=10000
SAVEHIST=10000
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
setopt HIST_FIND_NO_DUPS
# === Автодополнения (case-insensitive) ===
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
# === Алиасы для eza (еслис нет — нижебезопасным fallback на ls) ===
if command -v eza >/dev/null 2>&1; then
alias ls='eza --icons=auto --group-directories-first --git'
alias ll='eza -l --icons=auto --group-directories-first --git'
alias la='eza -la --icons=auto --group-directories-first --git'
alias lt='eza --tree --level=2 --icons=auto'
alias lsize='eza -l --sort=size --icons=auto'
else
alias ls='ls --color=auto'
alias ll='ls -l --color=auto'
alias la='ls -la --color=auto'
fi
# bat=== илиАлиасы для bat/batcat (еслис нетfallback — обычныйна cat) ===
if command -v bat >/dev/null 2>&1; then
alias cat='bat --paging=never --style=plain'
elif command -v batcat >/dev/null 2>&1; then
alias cat='batcat --paging=never --style=plain'
fi
export BAT_PAGER="less -FR"
export BAT_THEME="ansi"
# Jovial:=== «эмоции»Контекстные "эмоции" стрелки по контексту команды
typeset -g JOV_ARROW_DEFAULT='%(?.(◕‿◕).(╥﹏╥%))'
typeset -g JOV_ARROW_KUBE='%(?.(\_(ツ)_/¯).(╥﹏╥%))'
typeset -g JOV_ARROW_CONT='%(?.(⌐■_■).(╥﹏╥%))'
autoload -Uz add-zsh-hook
typeset -g JOV_LAST_CMD_BASE=''
jov_store_last_cmd() {
local line="${2:-$1}" # OMZ часто кладёт команду во 2-й аргумент
emulate -L zsh -o extendedglob
line="${line##[[:space:]]##}"
local base="${line%%[[:space:]]*}"
if [[ "$base" == (sudo|doas) ]]; then
local rest="${line#*[[:space:]]}"
base="${rest%%[[:space:]]*}"
fi
JOV_LAST_CMD_BASE="$base"
}
preexec_functions=(${preexec_functions:#jov_store_last_cmd})
preexec_functions+=('jov_store_last_cmd')
jov_apply_context_face() {
local expr="$JOV_ARROW_DEFAULT"
case "$JOV_LAST_CMD_BASE" in
kubectl|k|helm) expr="$JOV_ARROW_KUBE" ;;
docker|docker-compose|podman|nerdctl) expr="$JOV_ARROW_CONT" ;;
*) expr="$JOV_ARROW_DEFAULT" ;;
esac
JOVIAL_SYMBOL[arrow]="$expr"
JOVIAL_SYMBOL[arrow.git-clean]="$expr"
JOVIAL_SYMBOL[arrow.git-dirty]="$expr"
}
precmd_functions=(${precmd_functions:#jov_apply_context_face})
precmd_functions+=('jov_apply_context_face')
# Опционально: замени "code" на свой редактор, если VS Code не используешь
export EDITOR="code --wait --reuse-window"
export VISUAL="code --wait --reuse-window"
export SUDO_EDITOR="code --wait --reuse-window"
export SYSTEMD_EDITOR="code --wait --reuse-window"
export KUBE_EDITOR="code --wait --reuse-window"4) Плейбук Ansible: максимально «прямой» и читаемый
Ни циклов, ни переменных — всё в явном виде. Примечание: замените user на реальный логин. Все пути — конкретные и абсолютные.
#Jovial === PLAYBOOK (CHANGE ME где отмечено) =======================================
# Везде, где указан user и /home/user — замени на своего реального пользователя
# и его домашний каталог. Если путь к zsh не /usr/bin/zsh — замени тоже.
---
- name: Install & configure Zsh with Oh-My-Zsh + Jovial on multiple hosts
hosts: zsh_targets
gather_facts: yes
become: yes
tasks:
# 1) Создаём пользователя (если не существует) и выставляем логин-шелл Zsh
- name: Ensure user exists and set default shell to /usr/bin/zsh
ansible.builtin.user:
name: user # CHANGE ME: укажи ТВОЁ имя пользователя (должно совпадать с тем, под которым ты работаешь на сервере)
shell: /usr/bin/zsh # CHANGE ME при необходимости: на некоторых ОС путь может быть /bin/zsh. Проверь: `command -v zsh`
# 2) Базовые пакеты (под Debian/Ubuntu). Для других дистрибутивов замени на их аналоги (dnf/yum/apk/pacman).
- name: Install Zsh, Git, curl, ca-certificates, autojump
ansible.builtin.package:
name:
- zsh
- git
- curl
- ca-certificates
- autojump
state: present
# 3) eza — ставим, если пакет доступен (иначе не падаем)
- name: Install eza if available
ansible.builtin.package:
name: eza
state: present
ignore_errors: yes # OK, если пакета нет — .zshrc имеет fallback на ls
# 4) bat — если доступен (в Debian пакет может называться batcat; fallback есть в .zshrc)
- name: Install bat if available
ansible.builtin.package:
name: bat
state: present
ignore_errors: yes
# 5) Каталоги Oh-My-Zsh
- name: Create Oh-My-Zsh directory
ansible.builtin.file:
path: /home/user/.oh-my-zsh # CHANGE ME: /home/user → твой HOME
state: directory
owner: user # CHANGE ME: user → твой пользователь
group: user # CHANGE ME: user → твой пользователь
mode: "0755"
- name: Clone Oh-My-Zsh
ansible.builtin.git:
repo: https://github.com/ohmyzsh/ohmyzsh.git
dest: /home/user/.oh-my-zsh # CHANGE ME: /home/user → твой HOME
version: master
update: yes
become_user: user # CHANGE ME: user → твой пользователь (важно для владельцев)
- name: Create custom plugins directory
ansible.builtin.file:
path: /home/user/.oh-my-zsh/custom/plugins # CHANGE ME: /home/user → твой HOME
state: directory
owner: user # CHANGE ME
group: user # CHANGE ME
mode: "0755"
- name: Create custom themes directory
ansible.builtin.file:
path: /home/user/.oh-my-zsh/custom/themes # CHANGE ME: /home/user → твой HOME
state: directory
owner: user # CHANGE ME
group: user # CHANGE ME
mode: "0755"
# 6) Плагины — каждый отдельной задачей (читабельно и без «магии»)
- name: Clone zsh-autosuggestions
ansible.builtin.git:
repo: https://github.com/zsh-users/zsh-autosuggestions.git
dest: /home/user/.oh-my-zsh/custom/plugins/zsh-autosuggestions # CHANGE ME: /home/user → твой HOME
version: master
update: yes
become_user: user # CHANGE ME
- name: Clone zsh-syntax-highlighting
ansible.builtin.git:
repo: https://github.com/zsh-users/zsh-syntax-highlighting.git
dest: /home/user/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting # CHANGE ME
version: master
update: yes
become_user: user # CHANGE ME
- name: Clone zsh-history-enquirer
ansible.builtin.git:
repo: https://github.com/zthxxx/zsh-history-enquirer.git
dest: /home/user/.oh-my-zsh/custom/plugins/zsh-history-enquirer # CHANGE ME
version: master
update: yes
become_user: user # CHANGE ME
- name: Clone jovial (plugin + theme)
ansible.builtin.git:
repo: https://github.com/zthxxx/jovial.git
dest: /home/user/.oh-my-zsh/custom/plugins/jovial # CHANGE ME
version: master
update: yes
become_user: user # CHANGE ME
# 7) Симлинк темы Jovial
- name: Symlink jovial theme into custom themes
ansible.builtin.file:
src: /home/user/.oh-my-zsh/custom/plugins/jovial/jovial.zsh-theme # CHANGE ME
dest: /home/user/.oh-my-zsh/custom/themes/jovial.zsh-theme # CHANGE ME
state: link
owner: user # CHANGE ME
group: user # CHANGE ME
# 8) Устанавливаем .zshrc (с guard-строкой и без keychain)
- name: Install .zshrc
ansible.builtin.copy:
dest: /home/user/.zshrc # CHANGE ME: /home/user → твой HOME
owner: user # CHANGE ME
group: user # CHANGE ME
mode: "0644"
content: |
[ -n "$ZSH_VERSION" ] || return # не исполнять .zshrc в bash/VS Code
export ZSH="/home/user/.oh-my-zsh" # CHANGE ME: /home/user → твой HOME
ZSH_THEME="jovial"
plugins=(
git
autojump
bgnotify
zsh-history-enquirer
zsh-autosuggestions
jovial
zsh-syntax-highlighting # ДОЛЖЕН быть последним
)
source "/home/user/.oh-my-zsh/oh-my-zsh.sh" # CHANGE ME: /home/user → твой HOME
typeset -g JOVIAL_EXEC_THRESHOLD_SECONDS=1
export TERM="xterm-256color"
export COLORTERM="truecolor"
if command -v eza >/dev/null 2>&1; then
alias ls='eza --icons=auto --group-directories-first --git'
alias ll='eza -l --icons=auto --group-directories-first --git'
alias la='eza -la --icons=auto --group-directories-first --git'
alias lt='eza --tree --level=2 --icons=auto'
alias lsize='eza -l --sort=size --icons=auto'
else
alias ls='ls --color=auto'
alias ll='ls -l --color=auto'
alias la='ls -la --color=auto'
fi
if command -v bat >/dev/null 2>&1; then
alias cat='bat --paging=never --style=plain'
elif command -v batcat >/dev/null 2>&1; then
alias cat='batcat --paging=never --style=plain'
fi
export BAT_PAGER="less -FR"
export BAT_THEME="ansi"
typeset -g JOV_ARROW_DEFAULT='%(?.(◕‿◕).(╥﹏╥%))'
typeset -g JOV_ARROW_KUBE='%(?.(\_(ツ)_/¯).(╥﹏╥%))'
typeset -g JOV_ARROW_CONT='%(?.(⌐■_■).(╥﹏╥%))'
autoload -Uz add-zsh-hook
typeset -g JOV_LAST_CMD_BASE=''
jov_store_last_cmd() {
local line="${2:-$1}"
emulate -L zsh -o extendedglob
line="${line##[[:space:]]##}"
local base="${line%%[[:space:]]*}"
if [[ "$base" == (sudo|doas) ]]; then
local rest="${line#*[[:space:]]}"
base="${rest%%[[:space:]]*}"
fi
JOV_LAST_CMD_BASE="$base"
}
preexec_functions=(${preexec_functions:#jov_store_last_cmd})
preexec_functions+=('jov_store_last_cmd')
jov_apply_context_face() {
local expr="$JOV_ARROW_DEFAULT"
case "$JOV_LAST_CMD_BASE" in
kubectl|k|helm) expr="$JOV_ARROW_KUBE" ;;
docker|docker-compose|podman|nerdctl) expr="$JOV_ARROW_CONT" ;;
*) expr="$JOV_ARROW_DEFAULT" ;;
esac
JOVIAL_SYMBOL[arrow]="$expr"
JOVIAL_SYMBOL[arrow.git-clean]="$expr"
JOVIAL_SYMBOL[arrow.git-dirty]="$expr"
}
precmd_functions=(${precmd_functions:#jov_apply_context_face})
precmd_functions+=('jov_apply_context_face')
# Опционально:=== еслиРедакторы не используешь VS Code CLI — удали/замени на свой редактор===
export EDITOR="code{{ --waitzsh_editor --reuse-window"}}"
export VISUAL="code{{ --waitzsh_editor --reuse-window"}}"
{% if zsh_editor.startswith('code') %}
export SUDO_EDITOR="code{{ --waitzsh_editor --reuse-window"}}"
export SYSTEMD_EDITOR="code{{ --waitzsh_editor --reuse-window"}}"
export KUBE_EDITOR="code{{ --waitzsh_editor --reuse-window"}}"
{% endif %}
# 9)=== ФинальноДополнительные —алиасы убедиться,===
чтоalias дерево..='cd OMZ..'
принадлежитalias пользователю...='cd ../..'
alias ....='cd ../../..'
# ============================================================================
# ЭТАП 9: ФИНАЛЬНЫЕ НАСТРОЙКИ ПРАВ
# ============================================================================
- name: Ensure ownership on entire .oh-my-zsh directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh"
state: directory
recurse: yes
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
- name: Ensure ownership on .oh-my-zsh treezshrc
ansible.builtin.file:
path: "{{ zsh_home }}/home/user/.zshrc"
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
mode: "0644"
# ============================================================================
# ЭТАП 10: ПРОВЕРКА И ТЕСТИРОВАНИЕ
# ============================================================================
- name: Test zsh configuration
ansible.builtin.shell: |
{{ zsh_shell_path }} -c 'echo $ZSH_VERSION'
register: zsh_test
become_user: "{{ zsh_user }}"
changed_when: false
- name: Display zsh version
ansible.builtin.debug:
msg: "Zsh version: {{ zsh_test.stdout }}"
- name: Verify default shell is set
ansible.builtin.shell: |
getent passwd {{ zsh_user }} | cut -d: -f7
register: shell_check
changed_when: false
- name: Display configured shell
ansible.builtin.debug:
msg:
- "User: {{ zsh_user }}"
- "Default shell: {{ shell_check.stdout }}"
- "Expected: {{ zsh_shell_path }}"
- name: Final summary
ansible.builtin.debug:
msg:
- "✓ Zsh installation completed successfully"
- "✓ Oh-My-Zsh installed at: {{ zsh_home }}/.oh-my-zshzsh"
#- CHANGE"✓ MEConfiguration state:file: directory{{ recurse:zsh_home yes}}/.zshrc"
owner:- user"✓ #Theme: CHANGEJovial"
ME- group:"✓ userPlugins: #git, CHANGEautojump, MEbgnotify, zsh-autosuggestions, jovial, zsh-syntax-highlighting, zsh-history-enquirer"
- ""
- "⚠ User must re-login or run 'source {{ zsh_home }}/.zshrc' to apply changes"
4. Запуск плейбука:плейбука
Базовый запуск
ansible-playbook -i inventories/inventory.ini playbooks/zsh_deploy.yml
Запуск для конкретной группы
ansible-playbook -i inventories/inventory.ini playbooks/zsh_deploy.yml -l zsh_targetsdocker
Запуск с проверкой (dry-run)
ansible-playbook -i inventories/inventory.ini playbooks/zsh_deploy.yml --check
Запуск с подробным выводом
ansible-playbook -i inventories/inventory.ini playbooks/zsh_deploy.yml -v
Запуск для одного хоста
ansible-playbook -i inventories/inventory.ini playbooks/zsh_deploy.yml -l n-1-dsw
5)5. VSПроверка Codeпосле Remote-SSH:установки
Подключаемся избежатьк конфликтовсерверу
ssh bashmyuser@203.0.113.11
Проверяем shell
echo $SHELL
# Ожидается: /usr/bin/zsh или /bin/zsh
Проверяем тему
echo $ZSH_THEME
# Ожидается: jovial
Проверяем плагины
# Начните вводить команду — должны появиться серые автодополнения
# Введите команду — она должна подсвечиваться
6. Обновление конфигурации
КогдаЕсли VSнужно Codeобновить поднимает интегрированный терминал или сервер, он нередко стартует с bash. Чтобы у bash никогда не было желания «исполнить» ваштолько .zshrc, поставьтебез самуюпереустановки первую строку-сторож:всего:
[ansible-playbook -ni "$ZSH_VERSION"inventories/inventory.ini ]playbooks/zsh_deploy.yml ||--tags returnconfig
ТакжеДля вэтого Remote-настройкахдобавьте тег к задаче установки .zshrc:
- name: Install .zshrc configuration
ansible.builtin.copy:
# ... параметры ...
tags:
- config
7. Откат изменений (деинсталляция)
Создайте файл playbooks/zsh_uninstall.yml:
---
- name: Uninstall Zsh and Oh-My-Zsh
hosts: zsh_targets
gather_facts: yes
become: yes
tasks:
- name: Get user home directory
ansible.builtin.getent:
database: passwd
key: "{{ zsh_user }}"
register: user_getent
- name: Set zsh_home fact
ansible.builtin.set_fact:
zsh_home: "{{ user_getent.ansible_facts.getent_passwd[zsh_user][4] }}"
- name: Restore default shell to bash
ansible.builtin.user:
name: "{{ zsh_user }}"
shell: /bin/bash
- name: Remove Oh-My-Zsh directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh"
state: absent
- name: Remove .zshrc
ansible.builtin.file:
path: "{{ zsh_home }}/.zshrc"
state: absent
- name: Find .zshrc backups
ansible.builtin.find:
paths: "{{ zsh_home }}"
patterns: ".zshrc.backup.*"
register: zshrc_backups
- name: Restore latest .zshrc backup if exists
ansible.builtin.copy:
src: "{{ (zshrc_backups.files | sort(attribute='mtime') | last).path }}"
dest: "{{ zsh_home }}/.zshrc"
remote_src: yes
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
when: zshrc_backups.matched > 0
- name: Remove packages (Debian/Ubuntu)
ansible.builtin.apt:
name:
- zsh
- eza
- bat
- autojump
state: absent
purge: yes
when: ansible_os_family == "Debian"
- name: Remove packages (RedHat/Fedora)
ansible.builtin.dnf:
name:
- zsh
- eza
- bat
- autojump-zsh
state: absent
when: ansible_os_family == "RedHat"
- name: Autoremove unused packages (Debian/Ubuntu)
ansible.builtin.apt:
autoremove: yes
when: ansible_os_family == "Debian"
Запуск деинсталляции:
ansible-playbook -i inventories/inventory.ini playbooks/zsh_uninstall.yml
8. Настройка VS Code выберитеRemote-SSH
Для корректной работы с VS Code добавьте в :zshsettings.json
{
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/zsh",
"args": ["-l"]
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.inheritEnv": false
}
9. Типичные проблемы и решения
Проблема: "Permission denied" при клонировании репозиториев
Решение: Проверьте что become_user указан правильно и пользователь существует
- name: Clone repository
ansible.builtin.git:
# ...
become_user: "{{ zsh_user }}" # Важно!
Проблема: Git показывает изменения даже когда их нет
Решение: Используйте force: no чтобы не перезаписывать локальные изменения
- name: Clone Oh-My-Zsh repository
ansible.builtin.git:
# ...
force: no # Не перезаписывать
Проблема: Владельцы файлов некорректные
Решение: Всегда явно указывайте владельца через become_user при создании файлов
- name: Create directory
ansible.builtin.file:
path: "{{ zsh_home }}/.oh-my-zsh"
owner: "{{ zsh_user }}"
group: "{{ zsh_user }}"
10. Преимущества этого решения
✅ Кросс-платформенность — поддержка Debian, Ubuntu, CentOS, Fedora, Arch
✅ Автоматическое определение путей — не нужно хардкодить /usr/bin/zsh
✅ Идемпотентность — можно запускать многократно без проблем
✅ Бэкапы — автоматическое сохранение существующих конфигов
✅ Проверки — тестирование после установки
✅ Безопасность — корректная работа с правами и владельцами
✅ Прозрачность — все параметры в переменных, легко настроить
✅ Откат — простая деинсталляция при необходимости
11. Дополнительные возможности
Добавление собственных алиасов через переменные
В group_vars/zsh_targets.yml:
zsh_custom_aliases:
- { alias: 'update', command: 'sudo apt update && sudo apt upgrade -y' }
- { alias: 'gs', command: 'git status' }
- { alias: 'dps', command: 'docker ps' }
Проверка на хосте:
getent passwd $USER | cut -d: -f7 # ожидается /usr/bin/zsh
echo $SHELL # в интерактивном окне VS Code — /usr/bin/zsh6) Типичные ошибки и быстрые решения
|
|
| |
|---|---|---|---|
|
| В
|
|
|
|
|
|
|
|
| |
|
|
| |
|
|
|
7) Проверки после раскатки
# 1) Проверь, что ВЕЗДЕ заменил user и /home/user
grep -R --line-number -E '(^|/)(home/user|user\b)' inventories/ playbooks/ ~/.zshrc
# 2) На целевом хосте: логин-шелл
getent passwd $USER | cut -d: -f7 # ДОЛЖНО быть /usr/bin/zsh (или /bin/zsh, если так и решил)
# 3) Пути OMZ:
ls -ld /home/$USER/.oh-my-zsh # ВЛАДЕЛЕЦ — твой пользователь
head -n 1 /home/$USER/.zshrc # ДОЛЖНА быть строка-guard: [ -n "$ZSH_VERSION" ] || return8) Почему zsh-syntax-highlighting должен быть последним
Этот плагин подсвечивает ввод команды по мере набора. Ему нужна «финальная» форма строки после всех модификаций других плагинов/тем. Если подключить его раньше — подсветка может вести себя нестабильно или ломаться. Поэтому он — последний идобавьте в .zshrc,:
{% for item in zsh_custom_aliases | default([]) %}
alias {{ item.alias }}='{{ item.command }}'
{% endfor %}
Установка специфичных настроек для групп
Создайте group_vars/docker.yml:
zsh_custom_plugins:
- docker
- docker-compose
И подключайте их условно в нашей автоматизации.плейбуке.
9) Откат (деинсталляция)Заключение
#Это Вернутьполное, логин-шеллproduction-ready bash:решение sudoдля chshавтоматизации -sустановки /bin/bashZsh user
# Удалитьс Oh-My-Zsh и .zshrc:темой sudoJovial. rmОно -rfучитывает /home/user/.oh-my-zshвсе sudoподводные rmкамни, -fкоторые /home/user/.zshrcбыли #в (опционально)оригинальной удалить пакеты:
sudo apt remove --purge -y zsh eza bat autojump
sudo apt autoremove -y
(Если у вас другой менеджер пакетов — используйте его явные команды: dnf remove, apk del, pacman -R,версии, и т.добавляет п.)
10) Заключение
Мы получили прозрачный, воспроизводимый способ развернуть одинаковую zsh-среду на нескольких серверах. Без «магии» и «переменных-шаблонов»: только явные пути, конкретные имена и последовательные шаги. Такой подход удобенулучшений для проверки,стабильной аудита и быстрого дебага — особенно когда нужно массово наводить порядок на узлах, не опасаясь «побочек»работы в VSразличных Code и сервисах.окружениях.