chore: first commit
This commit is contained in:
parent
5c4016357f
commit
c612cc7839
29
.editorconfig
Normal file
29
.editorconfig
Normal file
@ -0,0 +1,29 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[*.{js,py}]
|
||||
charset = utf-8
|
||||
|
||||
# 2 space indentation
|
||||
[*.{yml,sh},Vagrantfile,Dockerfile*]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
# 4 space indentation
|
||||
[*.{py,md}]
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
tab_width = 8
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
inventory/*
|
||||
!inventory/hosts.example
|
||||
!inventory/host_vars/
|
||||
inventory/host_vars/*
|
||||
!inventory/host_vars/example.yml
|
||||
inventory_data/
|
||||
playbooks/*
|
||||
!playbooks/example.yml
|
||||
TODO.md
|
||||
22
GIST.md
Normal file
22
GIST.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Ansible Gists
|
||||
|
||||
Scripts that took time to write and that I'm not sure I will use again.
|
||||
|
||||
## Convert netmask to CIDR notation
|
||||
|
||||
```yml
|
||||
- name: Convert netmask to CIDR notation
|
||||
set_fact:
|
||||
cidr_notation: >-
|
||||
{{
|
||||
(interface.netmask.split('.') |
|
||||
map('int') |
|
||||
map('string') |
|
||||
map('regex_replace', '^(.*)$', '\\1|int|format("08b")') |
|
||||
map('regex_replace', '^(.*)$', '{{\\1}}') |
|
||||
join('') |
|
||||
regex_replace('0+$', '') |
|
||||
length)
|
||||
}}
|
||||
when: interface.netmask is defined and network_file.stat.exists != true
|
||||
```
|
||||
40
README.md
Normal file
40
README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Homelab Ansible Playbooks
|
||||
|
||||
This repository contains Ansible playbooks and roles I use to manage my NAS and several VMs 👨💻.
|
||||
|
||||
This project is designed for personal/familial scale maintenance, if you find this useful for your use, want to share advises or security concerns, feel free to drop me a line.
|
||||
|
||||
This is a good playground to learn and I encourage you to adapt these roles to your needs. While they might not be production-ready for all environments, I'm open to adapting them for [Ansible Galaxy]((https://galaxy.ansible.com)) if there's community interest!
|
||||
|
||||
## Requirements
|
||||
|
||||
```sh
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
ansible-playbook -i inventory.yml playbook.yml
|
||||
```
|
||||
|
||||
## Target devices configuration
|
||||
|
||||
Requirements:
|
||||
|
||||
- sshd up and running
|
||||
- public key copied:
|
||||
|
||||
```sh
|
||||
ssh-copy-id -i ~/.ssh/id_rsa.pub username@remote_host
|
||||
```
|
||||
|
||||
- python3 installed (`pacman -Syu python3`)
|
||||
|
||||
## Developping
|
||||
|
||||
Linting:
|
||||
|
||||
```sh
|
||||
npx prettier --write .
|
||||
```
|
||||
5
ansible.cfg
Normal file
5
ansible.cfg
Normal file
@ -0,0 +1,5 @@
|
||||
[defaults]
|
||||
interpreter_python=/usr/bin/python3
|
||||
|
||||
[ssh_connection]
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/tmp/ansible-ssh-%h-%p-%r
|
||||
113
inventory/host_vars/example.yml
Normal file
113
inventory/host_vars/example.yml
Normal file
@ -0,0 +1,113 @@
|
||||
# Network configuration
|
||||
# ---------------------
|
||||
network_interfaces:
|
||||
- name: lan0
|
||||
type: ethernet
|
||||
mac_address: 02:a0:c9:8d:7e:b6
|
||||
ipv4:
|
||||
address: 192.168.1.2/24
|
||||
gateway: 192.168.1.254
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
- name: lan1
|
||||
type: ethernet
|
||||
mac_address: 0a:3f:5b:1c:d2:e4
|
||||
|
||||
# NTP servers configuration
|
||||
# -------------------------
|
||||
ntp_pools:
|
||||
- "0.uk.pool.ntp.org"
|
||||
- "1.uk.pool.ntp.org"
|
||||
- "2.uk.pool.ntp.org"
|
||||
- "3.uk.pool.ntp.org"
|
||||
ntp_timezone: "Europe/London"
|
||||
ntp_allowed_networks:
|
||||
- "127.0.0.1"
|
||||
- "::1"
|
||||
- "192.168.1.0 mask 255.255.255.0"
|
||||
- "192.168.20.0 mask 255.255.255.224"
|
||||
ntp_firewall_allowed_sources:
|
||||
- 192.168.1.0/24 # lan0
|
||||
- 192.168.20.0/27 # wg0
|
||||
|
||||
disk_partitioning:
|
||||
- device: /dev/nvme0n1
|
||||
layout_file: inventory_data/partition_layouts/omer.nvme0n1.sfdisk
|
||||
partitions:
|
||||
- name: EFI
|
||||
device: /dev/nvme0n1p1
|
||||
size: 512M
|
||||
type: EFI
|
||||
- name: SWAP
|
||||
device: /dev/nvme0n1p2
|
||||
size: 1G
|
||||
type: swap
|
||||
- name: ROOT
|
||||
device: /dev/nvme0n1p3
|
||||
size: 500G
|
||||
type: ext4
|
||||
- name: SLOG
|
||||
device: /dev/nvme0n1p4
|
||||
size: 400G
|
||||
type: zfs
|
||||
- name: CLUB
|
||||
device: /dev/nvme0n1p5
|
||||
size: 2.7TiB
|
||||
type: zfs
|
||||
|
||||
# ZFS pool configuration
|
||||
# ----------------------
|
||||
zfs_pools:
|
||||
- name: omer
|
||||
type: raidz1
|
||||
devices:
|
||||
- ata-SAMSUNG_MZ7LN512HMJP-00000_S1G2NSAF934567
|
||||
- ata-SAMSUNG_MZ7LN512HMJP-00000_S1G3NSAF934568
|
||||
options:
|
||||
ashift: 12
|
||||
root: /mnt/omer
|
||||
state: present
|
||||
|
||||
zfs_datasets:
|
||||
- name: omer/photos
|
||||
extra_zfs_properties:
|
||||
mountpoint: /mnt/omer/photos
|
||||
state: present
|
||||
- name: omer/movies
|
||||
extra_zfs_properties:
|
||||
mountpoint: /mnt/omer/movies
|
||||
state: present
|
||||
|
||||
# Wireguard "client" VPN configuration
|
||||
# ------------------------------------
|
||||
wireguard_address: 192.168.20.4/27
|
||||
wireguard_peers:
|
||||
- name: "Marge server"
|
||||
public_key: fB6zC8oWpQxN4yR2sT1uA7vJ9kH3mG5eD0cLlI8bV6aF2dP3eXwZ1qY4rU7tO9
|
||||
allowed_ips:
|
||||
- 192.168.20.1/32
|
||||
endpoint: 192.168.1.56:51820
|
||||
wireguard_dns: 192.168.20.1
|
||||
wireguard_server_mode: false
|
||||
|
||||
# NFS server configuration
|
||||
# ------------------------
|
||||
nfs_clients:
|
||||
- name: all_wg0_rw_clients
|
||||
host: "192.168.20.0/255.255.255.224"
|
||||
options: "rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000,insecure"
|
||||
- name: laptop_lan0_rw_clients
|
||||
host: "192.168.1.167"
|
||||
options: "rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000,insecure"
|
||||
nfs_shares:
|
||||
- dir: /mnt/omer/movies
|
||||
clients: "{{ nfs_clients }}"
|
||||
- dir: /mnt/omer/photos
|
||||
clients: "{{ nfs_clients }}"
|
||||
nfs_server_firewall_allowed_sources:
|
||||
- 192.168.1.0/24 # lan0
|
||||
- 192.168.20.0/27 # wg0
|
||||
nfs_bind_addresses:
|
||||
- 192.168.20.4
|
||||
- 192.168.1.2
|
||||
10
inventory/hosts.example
Normal file
10
inventory/hosts.example
Normal file
@ -0,0 +1,10 @@
|
||||
# hosts.yml
|
||||
all:
|
||||
children:
|
||||
servers:
|
||||
hosts:
|
||||
lisa:
|
||||
bart:
|
||||
marge:
|
||||
homer:
|
||||
maggie:
|
||||
10
playbook.yml
Normal file
10
playbook.yml
Normal file
@ -0,0 +1,10 @@
|
||||
- hosts: all
|
||||
become: true
|
||||
roles:
|
||||
- role: networking
|
||||
- role: sshd
|
||||
- role: disks
|
||||
- role: wireguard
|
||||
- role: zsh
|
||||
- role: archlinux
|
||||
- role: podman
|
||||
6
playbooks/example.yml
Normal file
6
playbooks/example.yml
Normal file
@ -0,0 +1,6 @@
|
||||
- hosts: marge
|
||||
become: true
|
||||
roles:
|
||||
- role: ntpd
|
||||
- role: fail2ban
|
||||
- role: unbound
|
||||
3
requirements.yml
Normal file
3
requirements.yml
Normal file
@ -0,0 +1,3 @@
|
||||
collections:
|
||||
- name: ansible.netcommon
|
||||
- name: community.general
|
||||
5
roles/archlinux/defaults/main.yml
Normal file
5
roles/archlinux/defaults/main.yml
Normal file
@ -0,0 +1,5 @@
|
||||
arch_locale: en_US.UTF-8
|
||||
yay_src_path: /opt/yay
|
||||
yay_git_repo: https://aur.archlinux.org/yay.git
|
||||
paru_src_path: /opt/paru
|
||||
paru_git_repo: https://aur.archlinux.org/paru.git
|
||||
15
roles/archlinux/tasks/locales.yml
Normal file
15
roles/archlinux/tasks/locales.yml
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Configure locales
|
||||
block:
|
||||
- name: activate locale
|
||||
command:
|
||||
cmd: localectl set-locale LANG={{ arch_locale }}
|
||||
- name: edit /etc/locale.gen
|
||||
lineinfile:
|
||||
dest: /etc/locale.gen
|
||||
state: present
|
||||
regexp: "{{ arch_locale }}"
|
||||
line: "{{ arch_locale }} UTF-8"
|
||||
- name: regenerate locales
|
||||
command:
|
||||
cmd: locale-gen
|
||||
12
roles/archlinux/tasks/main.yml
Normal file
12
roles/archlinux/tasks/main.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Skip Archlinux installation
|
||||
meta: end_play
|
||||
when: ansible_facts['os_family'] != 'Archlinux'
|
||||
|
||||
- name: Archlinux base setup
|
||||
include_tasks: "{{ item }}"
|
||||
loop:
|
||||
- pacman.yml
|
||||
- locales.yml
|
||||
- yay.yml
|
||||
- paru.yml
|
||||
45
roles/archlinux/tasks/pacman.yml
Normal file
45
roles/archlinux/tasks/pacman.yml
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
- name: Check if pacman is not locked
|
||||
stat:
|
||||
path: /var/lib/pacman/db.lck
|
||||
register: pacman_lock
|
||||
failed_when: pacman_lock.stat.exists
|
||||
changed_when: false
|
||||
|
||||
## Only if your are sure of what you are doing
|
||||
# - name: Unlock pacman lock
|
||||
# file:
|
||||
# path: /var/lib/pacman/db.lck
|
||||
# state: absent
|
||||
|
||||
- name: Install reflector (looking for fastest mirror)
|
||||
pacman:
|
||||
name: reflector
|
||||
state: present
|
||||
|
||||
- name: Stat pacman mirrorlist
|
||||
stat:
|
||||
path: /etc/pacman.d/mirrorlist
|
||||
register: mirrorlist
|
||||
|
||||
# Probably not here if it's a fresh install
|
||||
- name: Stat pacman mirrorlist.bak
|
||||
stat:
|
||||
path: /etc/pacman.d/mirrorlist.bak
|
||||
register: mirrorlist_bak
|
||||
|
||||
- name: Backup and update pacman mirrorlist if older than 7 days
|
||||
shell: >
|
||||
cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak &&
|
||||
reflector --latest 20 --protocol https --sort rate
|
||||
--save /etc/pacman.d/mirrorlist
|
||||
when: mirrorlist_bak.stat.exists is false or
|
||||
(mirrorlist.stat.exists and
|
||||
(ansible_date_time.epoch | int - mirrorlist.stat.mtime) > 604800)
|
||||
|
||||
- name: Configure pacman to output colors
|
||||
lineinfile:
|
||||
dest: /etc/pacman.conf
|
||||
state: present
|
||||
regexp: "^(.*)Color"
|
||||
line: "Color"
|
||||
60
roles/archlinux/tasks/paru.yml
Normal file
60
roles/archlinux/tasks/paru.yml
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Check if paru is already installed
|
||||
stat:
|
||||
path: /usr/bin/paru
|
||||
register: paru
|
||||
|
||||
- name: Install paru
|
||||
block:
|
||||
- name: Install build dependencies
|
||||
package:
|
||||
name:
|
||||
- base-devel
|
||||
- git
|
||||
state: present
|
||||
|
||||
- name: Disable sudo password prompt (makepkg sudoers hack)
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- command:
|
||||
cmd: whoami
|
||||
no_log: true
|
||||
become: false
|
||||
register: main_user
|
||||
|
||||
- set_fact:
|
||||
main_user: "{{ main_user.stdout }}"
|
||||
no_log: true
|
||||
|
||||
- name: Create paru sources dir
|
||||
file:
|
||||
path: "{{ paru_src_path }}"
|
||||
state: directory
|
||||
owner: "{{ main_user }}"
|
||||
|
||||
- name: Clone git sources
|
||||
become: false
|
||||
git:
|
||||
repo: "{{ paru_git_repo }}"
|
||||
dest: "{{ paru_src_path }}"
|
||||
|
||||
# note: this only works because SUDOERS password prompt is disabled
|
||||
- name: Build and install
|
||||
become: false
|
||||
command:
|
||||
chdir: "{{ paru_src_path }}"
|
||||
cmd: "makepkg -si -f --noconfirm"
|
||||
|
||||
- name: Restore sudo with password prompt
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL:ALL) ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
when: not paru.stat.exists
|
||||
60
roles/archlinux/tasks/yay.yml
Normal file
60
roles/archlinux/tasks/yay.yml
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
- name: Check if yay is already installed
|
||||
stat:
|
||||
path: /usr/bin/yay
|
||||
register: yay
|
||||
|
||||
- name: Install yay
|
||||
block:
|
||||
- name: Install build dependencies
|
||||
package:
|
||||
name:
|
||||
- base-devel
|
||||
- git
|
||||
state: present
|
||||
|
||||
- name: Disable sudo password prompt (makepkg sudoers hack)
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- command:
|
||||
cmd: whoami
|
||||
no_log: true
|
||||
become: false
|
||||
register: main_user
|
||||
|
||||
- set_fact:
|
||||
main_user: "{{ main_user.stdout }}"
|
||||
no_log: true
|
||||
|
||||
- name: Create yay sources dir
|
||||
file:
|
||||
path: "{{ yay_src_path }}"
|
||||
state: directory
|
||||
owner: "{{ main_user }}"
|
||||
|
||||
- name: Clone git sources
|
||||
become: false
|
||||
git:
|
||||
repo: "{{ yay_git_repo }}"
|
||||
dest: "{{ yay_src_path }}"
|
||||
|
||||
# note: this only works because SUDOERS password prompt is disabled
|
||||
- name: Build and install
|
||||
become: false
|
||||
command:
|
||||
chdir: "{{ yay_src_path }}"
|
||||
cmd: "makepkg -si -f --noconfirm"
|
||||
|
||||
- name: Restore sudo with password prompt
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL:ALL) ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
when: not yay.stat.exists
|
||||
1
roles/disks/defaults/main.yml
Normal file
1
roles/disks/defaults/main.yml
Normal file
@ -0,0 +1 @@
|
||||
ssd_trim_periodicity: monthly
|
||||
7
roles/disks/tasks/main.yml
Normal file
7
roles/disks/tasks/main.yml
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Ensure disks are formatted correctly
|
||||
include_tasks: partitioning.yml
|
||||
loop: "{{ disk_partitioning | default([]) }}"
|
||||
|
||||
- name: Enable trim SSD if there is at least one
|
||||
include_tasks: trim-ssd.yml
|
||||
42
roles/disks/tasks/partitioning.yml
Normal file
42
roles/disks/tasks/partitioning.yml
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Install sfdisk
|
||||
package:
|
||||
name: util-linux
|
||||
state: present
|
||||
changed_when: false
|
||||
|
||||
- name: Install hdparm
|
||||
package:
|
||||
name: hdparm
|
||||
state: present
|
||||
changed_when: false
|
||||
|
||||
- name: Load expected layout from file (controller side)
|
||||
set_fact:
|
||||
expected_layout: "{{ lookup('file', item.layout_file) }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Get current layout from remote
|
||||
command: "sfdisk --dump {{ item.device }}"
|
||||
register: current_layout
|
||||
changed_when: false
|
||||
|
||||
- name: Compare expected vs current layout
|
||||
vars:
|
||||
current_clean: "{{ current_layout.stdout | trim | regex_replace('\\s+', ' ') }}"
|
||||
expected_clean: "{{ expected_layout | trim | regex_replace('\\s+', ' ') }}"
|
||||
set_fact:
|
||||
layout_differs: "{{ current_clean != expected_clean }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Copy layout file to remote (only if different)
|
||||
copy:
|
||||
content: "{{ expected_layout }}"
|
||||
dest: "/tmp/expected-{{ item.device | basename }}.sfdisk"
|
||||
mode: "0644"
|
||||
when: layout_differs
|
||||
|
||||
- name: Apply partition table using sfdisk
|
||||
command: >
|
||||
sfdisk {{ item.device }} < {{ item.layout_file }}
|
||||
when: layout_differs
|
||||
43
roles/disks/tasks/trim-ssd.yml
Normal file
43
roles/disks/tasks/trim-ssd.yml
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
# see: https://wiki.archlinux.org/title/Solid_state_drive#Periodic_TRIM
|
||||
- name: Check if there is at least one SSD
|
||||
set_fact:
|
||||
has_at_least_one_ssd: "{{ ansible_facts.devices | dict2items | selectattr('value.rotational', 'equalto', '0') | list | length > 0}}"
|
||||
changed_when: false
|
||||
|
||||
- name: Skip trim role
|
||||
meta: end_play
|
||||
when: not has_at_least_one_ssd
|
||||
|
||||
- name: install trim tools
|
||||
package:
|
||||
name: util-linux
|
||||
state: present
|
||||
changed_when: false
|
||||
|
||||
- name: edit trim periodicity if needed
|
||||
template:
|
||||
src: templates/fstrim.timer.j2
|
||||
dest: "/etc/systemd/system/fstrim.timer.d/override.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: timer_config
|
||||
|
||||
- name: systemd daemon reload
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
when: timer_config.changed
|
||||
|
||||
- name: enable periodic trim
|
||||
systemd:
|
||||
name: fstrim.timer
|
||||
enabled: yes
|
||||
state: started
|
||||
changed_when: false
|
||||
|
||||
- name: install nvme-cli
|
||||
package:
|
||||
name: nvme-cli
|
||||
state: present
|
||||
changed_when: false
|
||||
2
roles/disks/templates/fstrim.timer.j2
Normal file
2
roles/disks/templates/fstrim.timer.j2
Normal file
@ -0,0 +1,2 @@
|
||||
[Timer]
|
||||
OnCalendar={{ ssd_trim_periodicity }}
|
||||
1
roles/docker/defaults/main.yml
Normal file
1
roles/docker/defaults/main.yml
Normal file
@ -0,0 +1 @@
|
||||
docker_projects_dir: /opt/docker
|
||||
53
roles/docker/tasks/main.yml
Normal file
53
roles/docker/tasks/main.yml
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
# see: https://docs.docker.com/engine/storage/drivers/zfs-driver/
|
||||
# see: https://github.com/portainer/portainer
|
||||
#
|
||||
# Archlinux: only if your target is meant to frequently build docker images
|
||||
# see: https://stackoverflow.com/a/78352698
|
||||
|
||||
- name: uninstall docker
|
||||
block:
|
||||
- name: Include uninstall tasks
|
||||
include_tasks: uninstall.yml
|
||||
- name: Skip docker installation
|
||||
meta: end_play
|
||||
when: uninstall_docker | lower in ['yes', 'y']
|
||||
|
||||
- name: install docker
|
||||
package:
|
||||
name: docker
|
||||
|
||||
- name: enable the service
|
||||
service:
|
||||
name: "docker"
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- command:
|
||||
cmd: whoami
|
||||
no_log: true
|
||||
become: false
|
||||
register: main_user
|
||||
|
||||
- set_fact:
|
||||
main_user: "{{ main_user.stdout }}"
|
||||
no_log: true
|
||||
|
||||
- name: create projects directory
|
||||
file:
|
||||
path: "{{ docker_projects_dir }}"
|
||||
state: directory
|
||||
owner: "{{ main_user }}"
|
||||
group: "{{ main_user }}"
|
||||
|
||||
- name: allow user to use docker
|
||||
user:
|
||||
name: "{{ main_user }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
register: docker_group
|
||||
|
||||
- name: inform the user that user needs to logout and login again
|
||||
debug:
|
||||
msg: "Please logout and login again to make sure the user is added to the docker group"
|
||||
when: docker_group.changed
|
||||
19
roles/docker/tasks/uninstall.yml
Normal file
19
roles/docker/tasks/uninstall.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: uninstall docker
|
||||
package:
|
||||
name: docker
|
||||
state: absent
|
||||
|
||||
- name: prompt the user for confirmation
|
||||
ansible.builtin.pause:
|
||||
prompt: "[IRREVERSIBLE] Are you sure you want to delete {{ docker_projects_dir }}?"
|
||||
echo: yes
|
||||
register: confirmation
|
||||
|
||||
- name: remote projects directory
|
||||
file:
|
||||
path: "{{ docker_projects_dir }}"
|
||||
state: absent
|
||||
owner: "{{ main_user }}"
|
||||
group: "{{ main_user }}"
|
||||
when: confirmation.user_input | lower in ['yes', 'y']
|
||||
2
roles/fail2ban/defaults/main.yml
Normal file
2
roles/fail2ban/defaults/main.yml
Normal file
@ -0,0 +1,2 @@
|
||||
fail2ban_firewall: ufw
|
||||
fail2ban_backend: systemd
|
||||
59
roles/fail2ban/tasks/main.yml
Normal file
59
roles/fail2ban/tasks/main.yml
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
# see: https://wiki.archlinux.org/title/Fail2ban
|
||||
- name: Install fail2ban
|
||||
package:
|
||||
name: fail2ban
|
||||
state: present
|
||||
|
||||
- name: Ensure fail2ban configuration is only owned by root
|
||||
file:
|
||||
path: /etc/fail2ban
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0700
|
||||
recurse: yes
|
||||
|
||||
- name: Install Fail2ban Config
|
||||
block:
|
||||
- name: General configuration
|
||||
template:
|
||||
src: jail.local.j2
|
||||
dest: /etc/fail2ban/jail.local
|
||||
mode: "0600"
|
||||
- name: Service custom jail
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
mode: "0600"
|
||||
loop:
|
||||
- { src: sshd-jail.local.j2, dest: /etc/fail2ban/jail.d/sshd.local }
|
||||
- { src: nginx-jail.local.j2, dest: /etc/fail2ban/jail.d/nginx.local }
|
||||
|
||||
- name: Service hardening (read-only root rights)
|
||||
block:
|
||||
- name: Check if hardening configuration is already applied
|
||||
stat:
|
||||
path: /etc/systemd/system/fail2ban.service.d/override.conf
|
||||
register: override_conf
|
||||
- name: Create configuration directory
|
||||
file:
|
||||
path: /etc/systemd/system/fail2ban.service.d
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0700
|
||||
- name: Apply hardening configuration
|
||||
template:
|
||||
src: hardened.fail2ban.conf.j2
|
||||
dest: /etc/systemd/system/fail2ban.service.d/override.conf
|
||||
when: not override_conf.stat.exists
|
||||
- name: Reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
when: not override_conf.stat.exists
|
||||
|
||||
- name: Start and enable fail2ban
|
||||
service:
|
||||
name: fail2ban
|
||||
state: started
|
||||
enabled: yes
|
||||
11
roles/fail2ban/templates/hardened.fail2ban.conf.j2
Normal file
11
roles/fail2ban/templates/hardened.fail2ban.conf.j2
Normal file
@ -0,0 +1,11 @@
|
||||
[Service]
|
||||
PrivateDevices=yes
|
||||
PrivateTmp=yes
|
||||
ProtectHome=read-only
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=-/var/run/fail2ban
|
||||
ReadWritePaths=-/var/lib/fail2ban
|
||||
ReadWritePaths=-/var/log/fail2ban.log
|
||||
ReadWritePaths=-/var/spool/postfix/maildrop
|
||||
ReadWritePaths=-/run/xtables.lock
|
||||
CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW
|
||||
7
roles/fail2ban/templates/jail.local.j2
Normal file
7
roles/fail2ban/templates/jail.local.j2
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
bantime = 1d
|
||||
banaction = {{fail2ban_firewall}}
|
||||
allowipv6 = true
|
||||
ignoreip = 127.0.0.1/8
|
||||
backend = {{fail2ban_backend}}
|
||||
ignoreself = true
|
||||
6
roles/fail2ban/templates/nginx-jail.local.j2
Normal file
6
roles/fail2ban/templates/nginx-jail.local.j2
Normal file
@ -0,0 +1,6 @@
|
||||
[nginx-http-auth]
|
||||
enabled = true
|
||||
port = http, https
|
||||
maxretry = 2
|
||||
findtime = 1d
|
||||
bantime = 2w
|
||||
6
roles/fail2ban/templates/sshd-jail.local.j2
Normal file
6
roles/fail2ban/templates/sshd-jail.local.j2
Normal file
@ -0,0 +1,6 @@
|
||||
[sshd]
|
||||
enabled = true
|
||||
filter = sshd
|
||||
maxretry = 5
|
||||
findtime = 1d
|
||||
bantime = 2w
|
||||
3
roles/monitoring/tasks/main.yml
Normal file
3
roles/monitoring/tasks/main.yml
Normal file
@ -0,0 +1,3 @@
|
||||
- name: install oryx
|
||||
cmd: paru -S oryx
|
||||
when: ansible_facts['os_family'] == 'Archlinux'
|
||||
31
roles/net-config/README.md
Normal file
31
roles/net-config/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# net-config
|
||||
|
||||
This role configures a network interface.
|
||||
|
||||
## Requirements
|
||||
|
||||
None
|
||||
|
||||
## Example Playbook
|
||||
|
||||
```yaml
|
||||
- hosts: servers
|
||||
roles:
|
||||
- role: net-config
|
||||
interface:
|
||||
name: lan0
|
||||
mac_address: 02:a0:c9:8d:7e:b6
|
||||
address: 192.168.1.2/24
|
||||
gateway: 192.168.1.254
|
||||
nameservers:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author Information
|
||||
|
||||
Jokester <main@jokester.fr>
|
||||
19
roles/net-config/meta/main.yml
Normal file
19
roles/net-config/meta/main.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
galaxy_info:
|
||||
author: Jokester <main@jokester.fr>
|
||||
description: Configure the network as set in the inventory
|
||||
license: MIT
|
||||
min_ansible_version: "2.10"
|
||||
galaxy_tags:
|
||||
- network
|
||||
- configuration
|
||||
- linux
|
||||
- debian
|
||||
- archlinux
|
||||
platforms:
|
||||
- name: Debian
|
||||
versions:
|
||||
- all
|
||||
- name: ArchLinux
|
||||
versions:
|
||||
- all
|
||||
36
roles/net-config/tasks/main.yml
Normal file
36
roles/net-config/tasks/main.yml
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
- name: Check if the interface ipv4 address is defined
|
||||
block:
|
||||
- debug:
|
||||
msg: "Warning: iface {{ interface.name }} has no defined ipv4 address, skipping configuration"
|
||||
- name: Skip net-config role for {{ interface.name }}
|
||||
meta: end_play
|
||||
when: interface.ipv4.address is not defined
|
||||
|
||||
- name: Check if the interface is already configured
|
||||
stat:
|
||||
path: /etc/systemd/network/20-{{ interface.name }}.network
|
||||
register: network_file
|
||||
|
||||
- name: What patch is needed
|
||||
debug:
|
||||
msg: >-
|
||||
{%- if network_file.stat.exists == true -%}
|
||||
iface {{ interface.name }} is already configured, no action needed.
|
||||
{%- else -%}
|
||||
iface {{ interface.name }} will be configured.
|
||||
{%- endif -%}
|
||||
|
||||
- name: Create systemd-network link file
|
||||
when: network_file.stat.exists != true
|
||||
template:
|
||||
src: systemd.network.j2
|
||||
dest: /etc/systemd/network/20-{{ interface.name }}.network
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Notify a reload is required
|
||||
set_fact:
|
||||
network_reload_required: true
|
||||
when: network_file.stat.exists != true
|
||||
9
roles/net-config/templates/etc-interfaces.j2
Normal file
9
roles/net-config/templates/etc-interfaces.j2
Normal file
@ -0,0 +1,9 @@
|
||||
# This file describes the network interfaces available on your system
|
||||
# and how to activate them. For more information, see interfaces(5).
|
||||
|
||||
# The primary network interface
|
||||
allow-hotplug {{ interface.name }}
|
||||
iface {{ interface.name }} inet dhcp
|
||||
|
||||
# This is an autoconfigured IPv6 interface
|
||||
iface {{ interface.name }} inet6 auto
|
||||
9
roles/net-config/templates/systemd.network.j2
Normal file
9
roles/net-config/templates/systemd.network.j2
Normal file
@ -0,0 +1,9 @@
|
||||
[Match]
|
||||
Name={{ interface.name }}
|
||||
|
||||
[Network]
|
||||
Address={{ interface.ipv4.address }}
|
||||
Gateway={{ interface.ipv4.gateway }}
|
||||
{% for dns in interface.ipv4.nameservers %}
|
||||
DNS={{ dns }}
|
||||
{% endfor %}
|
||||
32
roles/net-persist/README.md
Normal file
32
roles/net-persist/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# net-persist
|
||||
|
||||
This role prevent the machine interface to change its name, thus to make unexpected changes to the network configuration. This rely on the mac address of the interface to map it to a static interface name.
|
||||
|
||||
If for some reason you might change your mac address (on a virtual machine for example), please update your inventory accordingly.
|
||||
|
||||
## Requirements
|
||||
|
||||
None
|
||||
|
||||
## Input variables
|
||||
|
||||
- `interface`:
|
||||
|
||||
```python
|
||||
{
|
||||
'mac_address': '02:a0:c9:8d:7e:b6',
|
||||
'ip': '192.168.1.2',
|
||||
'netmask': '255.255.255.0',
|
||||
'gateway': '192.168.1.254',
|
||||
'dns': ['1.1.1.1', '8.8.8.8'],
|
||||
'name': 'lan0'
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author Information
|
||||
|
||||
Jokester <main@jokester.fr>
|
||||
19
roles/net-persist/meta/main.yml
Normal file
19
roles/net-persist/meta/main.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
galaxy_info:
|
||||
author: Jokester <main@jokester.fr>
|
||||
description: Force the primary network interface to be 'lan0'
|
||||
license: MIT
|
||||
min_ansible_version: "2.10"
|
||||
galaxy_tags:
|
||||
- network
|
||||
- persistent
|
||||
- debian
|
||||
- archlinux
|
||||
- lan0
|
||||
platforms:
|
||||
- name: Debian
|
||||
versions:
|
||||
- all
|
||||
- name: ArchLinux
|
||||
versions:
|
||||
- all
|
||||
34
roles/net-persist/tasks/main.yml
Normal file
34
roles/net-persist/tasks/main.yml
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
- name: Check if the interface is already named as expected
|
||||
set_fact:
|
||||
interface_original_name: "{{ ansible_facts.interfaces
|
||||
| select('in', ansible_facts)
|
||||
| map('extract', ansible_facts)
|
||||
| selectattr('pciid', 'defined')
|
||||
| selectattr('macaddress', 'equalto', interface.mac_address)
|
||||
| map(attribute='device')
|
||||
| first
|
||||
}}"
|
||||
|
||||
- name: What patch is needed
|
||||
debug:
|
||||
msg: >-
|
||||
{%- if interface_original_name != interface.name -%}
|
||||
iface {{ interface_original_name }} ({{ interface.mac_address }}) will be patched to {{ interface.name }}.
|
||||
{%- else -%}
|
||||
iface {{ interface.name }} is already set, no action needed.
|
||||
{%- endif -%}
|
||||
|
||||
- name: Create persistent-net link file
|
||||
when: interface_original_name != interface.name
|
||||
template:
|
||||
src: persistent-net.link.j2
|
||||
dest: /etc/systemd/network/10-persistent-net-{{ interface.name }}.link
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Notify a reboot is required
|
||||
set_fact:
|
||||
reboot_required: true
|
||||
when: interface_original_name != interface.name
|
||||
10
roles/net-persist/templates/persistent-net.link.j2
Normal file
10
roles/net-persist/templates/persistent-net.link.j2
Normal file
@ -0,0 +1,10 @@
|
||||
# -----------------------------------------------------------------------------------
|
||||
# Forcing {{ interface.name }} to be predictable
|
||||
# see https://wiki.debian.org/NetworkInterfaceNames#CUSTOM_SCHEMES_USING_.LINK_FILES
|
||||
# see https://wiki.archlinux.org/title/Network_configuration#Change_interface_name
|
||||
# -----------------------------------------------------------------------------------
|
||||
[Match]
|
||||
MACAddress={{ interface.mac_address }}
|
||||
|
||||
[Link]
|
||||
Name={{ interface.name }}
|
||||
41
roles/networking/README.md
Normal file
41
roles/networking/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Networking
|
||||
|
||||
This role configures the networking on the target machine.
|
||||
|
||||
## Requirements
|
||||
|
||||
Roles:
|
||||
|
||||
- net-persist
|
||||
- net-config
|
||||
|
||||
## Inventory Variables
|
||||
|
||||
| Name | Description | Required |
|
||||
| ------------------ | ----------------------------------------------- | -------- |
|
||||
| network.interfaces | A dictionary of network interfaces to configure | yes |
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
network:
|
||||
interfaces:
|
||||
lan0:
|
||||
mac_address: 02:a0:c9:8d:7e:b6
|
||||
ip: 192.168.1.2
|
||||
netmask: 255.255.255.0
|
||||
gateway: 192.168.1.254
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
lan1:
|
||||
mac_address: 0a:3f:5b:1c:d2:e4
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author Information
|
||||
|
||||
Jokester <main@jokester.fr>
|
||||
18
roles/networking/meta/main.yml
Normal file
18
roles/networking/meta/main.yml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
galaxy_info:
|
||||
author: Jokester <main@jokester.fr>
|
||||
description: Meta role to configure the network
|
||||
license: MIT
|
||||
min_ansible_version: "2.10"
|
||||
galaxy_tags:
|
||||
- network
|
||||
- linux
|
||||
- debian
|
||||
- archlinux
|
||||
platforms:
|
||||
- name: Debian
|
||||
versions:
|
||||
- all
|
||||
- name: ArchLinux
|
||||
versions:
|
||||
- all
|
||||
31
roles/networking/tasks/main.yml
Normal file
31
roles/networking/tasks/main.yml
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
- name: "Setup persistent network interface(s)"
|
||||
include_role:
|
||||
name: net-persist
|
||||
public: yes
|
||||
vars:
|
||||
interface: "{{ item }}"
|
||||
loop: "{{ hostvars[inventory_hostname].network_interfaces | default([]) }}"
|
||||
|
||||
- name: "Configure network interface(s)"
|
||||
include_role:
|
||||
name: net-config
|
||||
public: yes
|
||||
vars:
|
||||
interface: "{{ item }}"
|
||||
loop: "{{ hostvars[inventory_hostname].network_interfaces | default([]) }}"
|
||||
|
||||
- name: Reload networkd and resolved
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: reloaded
|
||||
daemon_reload: yes
|
||||
loop:
|
||||
- systemd-networkd
|
||||
- systemd-resolved
|
||||
when: reboot_required is false and network_reload_required is true
|
||||
|
||||
- name: Reboot the machine
|
||||
when: reboot_required is true
|
||||
ansible.builtin.reboot:
|
||||
reboot_timeout: 60
|
||||
38
roles/nfs-server/README.md
Normal file
38
roles/nfs-server/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# NFS Server
|
||||
|
||||
This configuration is meant to be simple. We do not use a keberos server, nor fine-grained user ACLs here. I try not to mess up with ZFS options either.
|
||||
|
||||
Security is only guaranteed by the network (and firewal). Security is based on the IP address of the client, so I suggest to use a VPN if you want to avoid ARP poisoning on your LAN.
|
||||
|
||||
## In a nutshell
|
||||
|
||||
**Supports:**
|
||||
|
||||
- NFSv4 (TCP/UDP)
|
||||
- UFW firewal configuration
|
||||
- Reload service and exportfs on configuration change
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- Access control limited to the IP address of the client (unsecure)
|
||||
|
||||
## Inventory
|
||||
|
||||
Example of `nfs_shares` you can declare:
|
||||
|
||||
```yaml
|
||||
nfs_shares:
|
||||
- dir: "/srv/nfs/photos"
|
||||
clients:
|
||||
- host: "192.168.1.100" # privileged user with write a access
|
||||
options: "rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000,insecure"
|
||||
- host: "192.168.1.0/24" # readonly access for other lan clients
|
||||
options: "ro,sync,no_subtree_check"
|
||||
```
|
||||
|
||||
> Note: to make the share accessible from MacOS, you might use the `insecure` option (allowing to bind port numbers > 1024).
|
||||
|
||||
## Ressources
|
||||
|
||||
- https://wiki.archlinux.org/title/NFS
|
||||
- https://www.fkylewright.com/wordpress/2023/06/functional-automount-of-network-shares-in-macos/
|
||||
19
roles/nfs-server/defaults/main.yml
Normal file
19
roles/nfs-server/defaults/main.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
# Example:
|
||||
# nfs_shares:
|
||||
# - dir: "/srv/nfs/photos"
|
||||
# clients:
|
||||
# - host: "192.168.1.100" # privileged user with write a access
|
||||
# options: "rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000,insecure"
|
||||
# - host: "192.168.1.0/24" # readonly access for other lan clients
|
||||
# options: "ro,sync,no_subtree_check"
|
||||
nfs_shares: []
|
||||
|
||||
nfs_configuration_file: "/etc/nfs.conf"
|
||||
|
||||
nfs_exports_file: "/etc/exports"
|
||||
|
||||
nfs_port: 2049
|
||||
|
||||
nfs_server_firewall_allowed_sources:
|
||||
- 127.0.0.0/8
|
||||
9
roles/nfs-server/handlers/main.yml
Normal file
9
roles/nfs-server/handlers/main.yml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: "Reload systemd and restart nfs-server"
|
||||
ansible.builtin.systemd:
|
||||
name: "nfsv4-server"
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
|
||||
- name: "Update exportfs"
|
||||
ansible.builtin.command: exportfs -ra
|
||||
38
roles/nfs-server/tasks/main.yml
Normal file
38
roles/nfs-server/tasks/main.yml
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
- name: install nfs-server
|
||||
package:
|
||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('nfs-utils', 'nfs-kernel-server') }}"
|
||||
state: present
|
||||
|
||||
- name: configure nfs configuration
|
||||
ansible.builtin.template:
|
||||
src: templates/nfs.conf.j2
|
||||
dest: "{{ nfs_configuration_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Reload systemd and restart nfs-server
|
||||
|
||||
- name: configure nfs-server exports
|
||||
ansible.builtin.template:
|
||||
src: templates/exports.j2
|
||||
dest: "{{ nfs_exports_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: Update exportfs
|
||||
|
||||
- name: systemd service for nfs-server is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: nfsv4-server
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: setup firewall rules for nfs on port
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
src: "{{ item }}"
|
||||
port: "{{ nfs_port }}"
|
||||
proto: any
|
||||
direction: in
|
||||
with_items: "{{ nfs_server_firewall_allowed_sources | default([]) }}"
|
||||
8
roles/nfs-server/templates/exports.j2
Normal file
8
roles/nfs-server/templates/exports.j2
Normal file
@ -0,0 +1,8 @@
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
|
||||
{% for share in nfs_shares %}
|
||||
{% for client in share.clients %}
|
||||
{{ share.dir }} {{ client.host }}({{ client.options }})
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
4
roles/nfs-server/templates/nfs.conf.j2
Normal file
4
roles/nfs-server/templates/nfs.conf.j2
Normal file
@ -0,0 +1,4 @@
|
||||
[nfsd]
|
||||
{% for ip in nfs_bind_addresses %}
|
||||
host={{ ip }}
|
||||
{% endfor %}
|
||||
26
roles/ntpd/defaults/main.yml
Normal file
26
roles/ntpd/defaults/main.yml
Normal file
@ -0,0 +1,26 @@
|
||||
# NTP configuration file
|
||||
ntp_config_file: "/etc/ntp.conf"
|
||||
|
||||
# NTP servers to use.
|
||||
ntp_pools: -" 0.uk.pool.ntp.org"
|
||||
-" 1.uk.pool.ntp.org"
|
||||
-" 2.uk.pool.ntp.org"
|
||||
-" 3.uk.pool.ntp.org"
|
||||
|
||||
# System timezone
|
||||
ntp_timezone: "Europe/London"
|
||||
|
||||
# NTP drift file location
|
||||
# (keeps track of your clock's time deviation)
|
||||
ntp_drift_file: "/var/lib/ntp/ntp.drift"
|
||||
|
||||
# NTP security restrictions
|
||||
ntp_restrict: "kod nomodify notrap nopeer noquery limited"
|
||||
|
||||
# Networks allowed to query this ntpd server
|
||||
ntp_allowed_networks:
|
||||
- "127.0.0.1"
|
||||
- "::1"
|
||||
# - "192.168.1.0 mask 255.255.255.0"
|
||||
|
||||
ntp_port: 123
|
||||
6
roles/ntpd/handlers/main.yml
Normal file
6
roles/ntpd/handlers/main.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: "Restart ntpd service"
|
||||
ansible.builtin.systemd:
|
||||
name: "ntpd"
|
||||
state: restarted
|
||||
reload: yes
|
||||
48
roles/ntpd/tasks/main.yml
Normal file
48
roles/ntpd/tasks/main.yml
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
- name: install NTP package
|
||||
package:
|
||||
name: "ntp"
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: set system timezone to {{ ntp_timezone }}"
|
||||
community.general.timezone:
|
||||
name: "{{ ntp_timezone }}"
|
||||
notify: "Restart ntpd service"
|
||||
|
||||
- name: ensure NTP drift file directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ ntp_drift_file | dirname }}"
|
||||
state: directory
|
||||
owner: "ntp"
|
||||
group: "ntp"
|
||||
mode: "0750"
|
||||
|
||||
- name: setup systems timezone
|
||||
community.general.timezone:
|
||||
name: "{{ ntp_timezone }}"
|
||||
notify: Restart chronyd # Redémarrer chrony peut être utile après un changement de TZ pour qu'il la prenne bien en compte dans ses logs/opérations
|
||||
|
||||
- name: "configure {{ ntp_config_file }}"
|
||||
ansible.builtin.template:
|
||||
src: "ntp.conf.j2"
|
||||
dest: "{{ ntp_config_file }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
notify: "Restart ntpd service"
|
||||
|
||||
- name: "ensure ntpd service is started and enabled"
|
||||
ansible.builtin.systemd:
|
||||
name: "ntpd"
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: "configure ufw firewall"
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ ntp_port }}"
|
||||
proto: udp
|
||||
src: "{{ item }}"
|
||||
direction: in
|
||||
loop: "{{ ntp_firewall_allowed_sources | default([]) }}"
|
||||
21
roles/ntpd/templates/ntp.conf.j2
Normal file
21
roles/ntpd/templates/ntp.conf.j2
Normal file
@ -0,0 +1,21 @@
|
||||
# {{ ansible_managed }}
|
||||
#
|
||||
# NTP configuration file for ntpd
|
||||
|
||||
restrict default {{ ntp_restrict }}
|
||||
|
||||
{% for network in ntp_allowed_networks %}
|
||||
restrict {{ network }}
|
||||
{% endfor %}
|
||||
|
||||
# Use servers from the NTP Pool Project. 'iburst' speeds up initial synchronization.
|
||||
{% for pool_host in ntp_pools %}
|
||||
pool {{ pool_host }} iburst
|
||||
{% endfor %}
|
||||
|
||||
# Frequency drift file
|
||||
driftfile {{ ntp_drift_file }}
|
||||
|
||||
# Disable the monitoring facility (monlist) to prevent ntpq -c monlist DDOS attacks.
|
||||
# @see CVE-2013-5211
|
||||
disable monitor
|
||||
1
roles/podman/defaults/main.yml
Normal file
1
roles/podman/defaults/main.yml
Normal file
@ -0,0 +1 @@
|
||||
podman_projects_dir: /opt/podman
|
||||
20
roles/podman/tasks/main.yml
Normal file
20
roles/podman/tasks/main.yml
Normal file
@ -0,0 +1,20 @@
|
||||
- name: install podman
|
||||
package:
|
||||
name: podman
|
||||
|
||||
- command:
|
||||
cmd: whoami
|
||||
no_log: true
|
||||
become: false
|
||||
register: main_user
|
||||
|
||||
- set_fact:
|
||||
main_user: "{{ main_user.stdout }}"
|
||||
no_log: true
|
||||
|
||||
- name: create projects directory
|
||||
file:
|
||||
path: "{{ podman_projects_dir }}"
|
||||
state: directory
|
||||
owner: "{{ main_user }}"
|
||||
group: "{{ main_user }}"
|
||||
8
roles/sshd/defaults/main.yml
Normal file
8
roles/sshd/defaults/main.yml
Normal file
@ -0,0 +1,8 @@
|
||||
ssh_port: 22
|
||||
ssh_allowed_network: "192.168.1.0/24"
|
||||
ssh_allowed_vpn_network: "192.168.27.0/27"
|
||||
ssh_users: "jokester" # space separated if many
|
||||
ssh_config_dir: "/etc/ssh"
|
||||
sshd_config: "{{ ssh_config_dir}}/sshd_config"
|
||||
sshd_banner: "{{ ssh_config_dir}}/banner"
|
||||
sshd_binary: "/usr/sbin/sshd"
|
||||
78
roles/sshd/tasks/main.yml
Normal file
78
roles/sshd/tasks/main.yml
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
- include_vars: "{{ item }}"
|
||||
with_first_found:
|
||||
- "vars/{{ ansible_facts['os_family'] }}.yml"
|
||||
- "vars/debian.yml"
|
||||
|
||||
- name: Install OpenSSH
|
||||
package:
|
||||
name: "{{ ssh_package_name }}"
|
||||
state: present
|
||||
|
||||
- name: Install UFW
|
||||
package:
|
||||
name: ufw
|
||||
state: present
|
||||
|
||||
- name: Enable SSH
|
||||
service:
|
||||
name: "{{ ssh_service_name }}"
|
||||
enabled: yes
|
||||
|
||||
- name: Allow SSH incoming connection on local network
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
from: "{{ ssh_allowed_network }}"
|
||||
direction: in
|
||||
|
||||
- name: Allow SSH incoming connection on vpn network
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ ssh_port }}"
|
||||
proto: tcp
|
||||
from: "{{ ssh_allowed_vpn_network }}"
|
||||
direction: in
|
||||
|
||||
- name: Add SSH public key to authorized_keys
|
||||
authorized_key:
|
||||
user: "{{ item }}"
|
||||
state: present
|
||||
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
|
||||
comment: "{{ lookup('env', 'USER') | default('ansible') }}@{{ lookup('pipe', 'hostname -s') }}"
|
||||
loop: "{{ ssh_users.split() }}"
|
||||
|
||||
- name: Create an SSH banner
|
||||
template:
|
||||
src: templates/sshd_banner.j2
|
||||
dest: "{{ sshd_banner }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Remove motd on Debian
|
||||
file:
|
||||
path: /etc/motd
|
||||
state: absent
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: Hardening sshd_config
|
||||
template:
|
||||
src: templates/sshd_config.j2
|
||||
dest: "{{ sshd_config }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0600"
|
||||
validate: "{{ sshd_binary }} -t -f %s"
|
||||
register: ssh_hardening_task
|
||||
|
||||
- name: Restart SSH service
|
||||
service:
|
||||
name: "{{ ssh_service_name }}"
|
||||
state: restarted
|
||||
when: ssh_hardening_task.changed
|
||||
|
||||
- name: Enable UFW
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
47
roles/sshd/templates/sshd_banner.j2
Normal file
47
roles/sshd/templates/sshd_banner.j2
Normal file
@ -0,0 +1,47 @@
|
||||
*******************************************
|
||||
GALACTIC EMPIRE SECURE TERMINAL
|
||||
*******************************************
|
||||
{% if ansible_host == 'andromeda' %}
|
||||
⣠⣴⣾⣿⣿⣿⣿⣷⣦⣄
|
||||
⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄
|
||||
⢀⣿⣿⣿⣿⡿⠛⢿⡿⠛⢻⣿⣿⣿⣿⡀ <IMPERIAL SECURITY
|
||||
⢸⣿⣿⣿⣿⡇ ⢸⣷⣶⣾⣿⣿⣿⣿⡇ IDENTIFICATION DROID
|
||||
⠈⠉⠉⠉⠉⠁ ⠈⠉⠉⠉⠉⠉⠉⠉⠁
|
||||
⢀⣤⣀⣾⣿⣿⣿⠟⠛⠛⠛⠛⠻⣿⣿⣿⣷⣀⣤⡀
|
||||
⢸⣿⣿⣿⣿⣿⣿⣤⣤⣤⣤⣤⣤⣿⣿⣿⣿⣿⣿⡇
|
||||
⢸⣿⣿⣿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣿⡇
|
||||
⢸⣿⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣿⣿⡇
|
||||
⢸⣿⡟⢿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⡿⢻⣿⡇
|
||||
⢸⣿⡇⠈⠙⠛⢛⣿⣿⣤⣤⣿⣿⡛⠛⠋⠁⢸⣿⡇
|
||||
⣤⣼⣿⣧⣤⡀ ⠙⠛⠛⠛⠛⠛⠛⠋ ⢀⣤⣼⣿⣧⣤
|
||||
⠛⠛⠛⠛⠛⠁ ⠈⠛⠛⠛⠛⠛
|
||||
{% elif ansible_host == 'omega' %}
|
||||
⣀⣤⣴⣶⣾⣿⣿⣿⣿⣷⡶⠦
|
||||
⢀⣴⣾⣿⣿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣤⡄
|
||||
⣰⣿⣿⣿⠋ ⠈⢻⣿⣿⣿⣿⣿⣿⡟⠛⠛⠃
|
||||
⣼⣿⣿⣿⡇ ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧
|
||||
⢰⣿⣿⣿⣿⣧⡀ ⣠⣿⣿⣿⣿⣿⣿⠿⠟⠛⠁
|
||||
⣾⣿⣿⣿⣿⣿⣿⣶⣤⣤⣴⣾⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣶
|
||||
⣉⠉⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠉⣉
|
||||
⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⠿⠿
|
||||
⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠛⠛⠋⠉
|
||||
⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤⣤⡄
|
||||
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
|
||||
⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡄
|
||||
⠉⠛⠻⠿⢿⣿⣿⣿⣿⠟⠉⠉⠉⠉
|
||||
{% else %}
|
||||
ACCESS DENIED - UNKNOWN STAR SYSTEM
|
||||
{% endif %}
|
||||
|
||||
You have reached a terminal of the Galactic
|
||||
Empire's secure network. Unauthorized access
|
||||
will result in tracking and possible Force
|
||||
action.
|
||||
|
||||
{% if ansible_hostname is defined %}
|
||||
Server: {{ ansible_hostname }}
|
||||
{% endif %}
|
||||
|
||||
*******************************************
|
||||
Beep beep-wooOOoo! Brrrp! Zzt zzt-whirl!
|
||||
*******************************************
|
||||
64
roles/sshd/templates/sshd_config.j2
Normal file
64
roles/sshd/templates/sshd_config.j2
Normal file
@ -0,0 +1,64 @@
|
||||
# Hardened SSH Configuration
|
||||
# Protocol version
|
||||
Protocol 2
|
||||
|
||||
# Address family
|
||||
AddressFamily inet
|
||||
|
||||
# Supported authentication methods
|
||||
AuthenticationMethods publickey
|
||||
|
||||
# Authentication
|
||||
PermitRootLogin no
|
||||
MaxAuthTries 3
|
||||
MaxSessions 2
|
||||
PubkeyAuthentication yes
|
||||
PasswordAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
ChallengeResponseAuthentication no
|
||||
KerberosAuthentication no
|
||||
GSSAPIAuthentication no
|
||||
UsePAM yes
|
||||
|
||||
# Login timeout and grace period
|
||||
LoginGraceTime 30s
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
MaxStartups 10:30:60
|
||||
|
||||
# Forwarding
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
X11Forwarding no
|
||||
PermitTTY yes
|
||||
|
||||
# User environment
|
||||
PermitUserEnvironment no
|
||||
|
||||
# Logging and auditing
|
||||
SyslogFacility AUTH
|
||||
LogLevel VERBOSE
|
||||
|
||||
# Banner
|
||||
Banner /etc/ssh/banner
|
||||
|
||||
# SFTP
|
||||
Subsystem sftp internal-sftp
|
||||
|
||||
# Idle timeout (1 hour)
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 12
|
||||
|
||||
# Restrict access to specific users/groups (customize as needed)
|
||||
AllowUsers {{ ssh_users }}
|
||||
# AllowGroups sshusers wheel
|
||||
|
||||
# Other security settings
|
||||
HostbasedAuthentication no
|
||||
IgnoreRhosts yes
|
||||
PermitUserRC no
|
||||
StrictModes yes
|
||||
Compression no
|
||||
{% if ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian' %}
|
||||
UsePrivilegeSeparation sandbox
|
||||
{% endif %}
|
||||
2
roles/sshd/vars/archlinux.yml
Normal file
2
roles/sshd/vars/archlinux.yml
Normal file
@ -0,0 +1,2 @@
|
||||
ssh_package_name: "openssh"
|
||||
ssh_service_name: "sshd"
|
||||
2
roles/sshd/vars/debian.yml
Normal file
2
roles/sshd/vars/debian.yml
Normal file
@ -0,0 +1,2 @@
|
||||
ssh_package_name: "openssh-server"
|
||||
ssh_service_name: "ssh"
|
||||
70
roles/unbound/README.md
Normal file
70
roles/unbound/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Testing
|
||||
|
||||
## DNS leaks
|
||||
|
||||
```
|
||||
browse https://www.dnsleaktest.com/
|
||||
```
|
||||
|
||||
## DNSSEC
|
||||
|
||||
Testing DNSSEC validation
|
||||
|
||||
At this point we have a working server with supposedly working DNSSEC validation. Obviously we work on ’trust, but verify’. To check that we have indeed a working validating server, we can run the following command:
|
||||
|
||||
```sh
|
||||
dig www.nic.cz. +dnssec
|
||||
```
|
||||
|
||||
The header section of the result should look like this:
|
||||
|
||||
```
|
||||
; <<>> DiG 9.4.2-P2 <<>> www.nic.cz. +dnssec
|
||||
;; global options: printcmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18417
|
||||
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
|
||||
```
|
||||
|
||||
See the bolded ‘ad’ in the flags line? Now compare this to the output of the same command, but run on my MacBook using the ISP’s resolver:
|
||||
|
||||
```
|
||||
; <<>> DiG 9.10.6 <<>> www.nic.cz. +dnssec
|
||||
;; global options: +cmd
|
||||
;; Got answer:
|
||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12527
|
||||
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
|
||||
```
|
||||
|
||||
The ISP’s resolver doesn’t support DNSSEC in this case, so you can see the ‘ad’ flag missing. That flag indicates that the result from the upstream server validated.
|
||||
|
||||
# Race condition with wireguard
|
||||
|
||||
On unbound side:
|
||||
|
||||
```
|
||||
systemd[1]: Starting unbound.service - Unbound DNS server...
|
||||
unbound[74430]: [1747167722] unbound[74430:0] error: can't bind socket: Cannot assign requested address for 192.168.27.1>
|
||||
unbound[74430]: [1747167722] unbound[74430:0] fatal error: could not open ports
|
||||
systemd[1]: unbound.service: Main process exited, code=exited, status=1/FAILURE
|
||||
systemd[1]: unbound.service: Failed with result 'exit-code'.
|
||||
systemd[1]: Failed to start unbound.service - Unbound DNS server.
|
||||
```
|
||||
|
||||
On wireguard side:
|
||||
|
||||
```
|
||||
systemd[1]: Starting wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0...
|
||||
wg-quick[72187]: [#] ip link add wg0 type wireguard
|
||||
wg-quick[72187]: [#] wg setconf wg0 /dev/fd/63
|
||||
wg-quick[72187]: [#] ip -4 address add 192.168.27.1/27 dev wg0
|
||||
wg-quick[72187]: [#] ip link set mtu 1420 up dev wg0
|
||||
wg-quick[72215]: [#] resolvconf -a tun.wg0 -m 0 -x
|
||||
wg-quick[72261]: [1747167556] unbound-control[72261:0] warning: control-enable is 'no' in the config file.
|
||||
wg-quick[72261]: [1747167556] unbound-control[72261:0] error: connect: Connection refused for 127.0.0.1 port 8953
|
||||
wg-quick[72217]: run-parts: /etc/resolvconf/update.d/unbound exited with return code 1
|
||||
wg-quick[72187]: [#] ip link delete dev wg0
|
||||
systemd[1]: wg-quick@wg0.service: Main process exited, code=exited, status=1/FAILURE
|
||||
systemd[1]: wg-quick@wg0.service: Failed with result 'exit-code'.
|
||||
systemd[1]: Failed to start wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0.
|
||||
```
|
||||
16
roles/unbound/defaults/main.yml
Normal file
16
roles/unbound/defaults/main.yml
Normal file
@ -0,0 +1,16 @@
|
||||
unbound_config_base_path: /etc/unbound
|
||||
unbound_config_path: "{{ unbound_config_base_path }}/unbound.conf"
|
||||
unbound_root_hints_path: "{{ unbound_config_base_path }}/root.hints"
|
||||
unbound_anchor_root_key: "{{ unbound_config_base_path }}/root.key"
|
||||
unbound_ad_servers_config_path: "{{ unbound_config_base_path }}/ad_servers.conf"
|
||||
unbound_custom_lan_config_path: "{{ unbound_config_base_path }}/lan.conf"
|
||||
unbound_custom_vpn_config_path: "{{ unbound_config_base_path }}/vpn.conf"
|
||||
unbound_custom_lan_domain: "example.lan"
|
||||
unbound_port: 53
|
||||
unbound_apparmor_profile_path: /etc/apparmor.d/usr.sbin.unbound
|
||||
unbound_firewall_allowed_sources:
|
||||
- 192.168.1.0/24 # lan0
|
||||
- 192.168.27.0/27 # wg0
|
||||
unbound_custom_lan_records:
|
||||
"example.lan":
|
||||
v4: 192.168.1.2
|
||||
16
roles/unbound/handlers/main.yml
Normal file
16
roles/unbound/handlers/main.yml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Check Unbound config syntax
|
||||
ansible.builtin.command: unbound-checkconf "{{ unbound_config_path }}"
|
||||
register: unbound_validation
|
||||
changed_when: false
|
||||
failed_when: unbound_validation.rc != 0
|
||||
|
||||
- name: Reload systemd and restart unbound
|
||||
ansible.builtin.systemd:
|
||||
name: unbound
|
||||
state: restarted
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Reload AppArmor profile
|
||||
ansible.builtin.command: apparmor_parser -r {{ unbound_apparmor_profile_path }}
|
||||
when: ansible_facts.apparmor.status == "enabled"
|
||||
131
roles/unbound/tasks/main.yml
Normal file
131
roles/unbound/tasks/main.yml
Normal file
@ -0,0 +1,131 @@
|
||||
---
|
||||
# see: https://calomel.org/unbound_dns.html
|
||||
# see: https://wiki.archlinux.org/title/Unbound
|
||||
- name: install unbound
|
||||
package:
|
||||
name: unbound
|
||||
state: present
|
||||
|
||||
# Note: on archlinux this is already shipped within unbound
|
||||
- name: install unbound-anchor on debian/ubuntu
|
||||
package:
|
||||
name: unbound-anchor
|
||||
state: present
|
||||
when: ansible_facts['os_family'] == 'Debian'
|
||||
|
||||
- name: ensure unbound configuration is owned by unbound
|
||||
ansible.builtin.shell: |
|
||||
find "{{ unbound_config_base_path }}" -type d -exec chmod 755 {} \;
|
||||
find "{{ unbound_config_base_path }}" -type f -exec chmod 644 {} \;
|
||||
chown -R unbound:unbound "{{ unbound_config_base_path }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: ensure apparmor profile for unbound exists
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apparmor.d/usr.sbin.unbound
|
||||
content: |
|
||||
/etc/unbound/** r,
|
||||
/var/lib/unbound/** rwk,
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: ansible_facts.apparmor.status == "enabled"
|
||||
notify:
|
||||
- Reload AppArmor profile
|
||||
|
||||
- name: check if root.hints exists
|
||||
stat:
|
||||
path: "{{ unbound_root_hints_path }}"
|
||||
register: root_hints
|
||||
|
||||
- name: update root.hints (if older than 6 months or missing)
|
||||
block:
|
||||
- name: download latest root hints from internic
|
||||
ansible.builtin.get_url:
|
||||
url: https://www.internic.net/domain/named.root
|
||||
dest: "{{ unbound_root_hints_path }}"
|
||||
owner: unbound
|
||||
group: unbound
|
||||
mode: "0644"
|
||||
when: >
|
||||
(not root_hints.stat.exists) or
|
||||
(ansible_date_time.epoch | int - root_hints.stat.mtime > 15552000)
|
||||
|
||||
- name: check if unbound ad_servers configuration exists
|
||||
stat:
|
||||
path: "{{ unbound_ad_servers_config_path }}"
|
||||
register: ad_servers
|
||||
|
||||
- name: update the ad_servers list if older than 2 weeks or missing
|
||||
block:
|
||||
- name: download stevenblack's hosts file
|
||||
ansible.builtin.get_url:
|
||||
url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
dest: /tmp/hosts.txt
|
||||
mode: "0644"
|
||||
|
||||
- name: convert hosts file to unbound format
|
||||
ansible.builtin.shell: |
|
||||
grep '^0\.0\.0\.0' /tmp/hosts.txt | awk '{print "local-zone: \""$2"\" always_nxdomain"}' > "{{ unbound_ad_servers_config_path }}" &&
|
||||
chown unbound:unbound "{{ unbound_ad_servers_config_path }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: clean up temporary file
|
||||
ansible.builtin.file:
|
||||
path: /tmp/hosts.txt
|
||||
state: absent
|
||||
when: >
|
||||
(not ad_servers.stat.exists) or
|
||||
(ansible_date_time.epoch | int - ad_servers.stat.mtime > 1209600)
|
||||
|
||||
- name: initialize dnssec trust anchor if missing
|
||||
ansible.builtin.command: unbound-anchor -a {{ unbound_anchor_root_key }}
|
||||
args:
|
||||
creates: "{{ unbound_anchor_root_key }}"
|
||||
|
||||
- name: install unbound config
|
||||
template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
owner: unbound
|
||||
group: unbound
|
||||
loop:
|
||||
- { src: unbound.conf.j2, dest: "{{ unbound_config_path }}" }
|
||||
- { src: custom-lan.conf.j2, dest: "{{ unbound_custom_lan_config_path }}" }
|
||||
- { src: custom-vpn.conf.j2, dest: "{{ unbound_custom_vpn_config_path }}" }
|
||||
notify:
|
||||
- Check Unbound config syntax
|
||||
- Reload systemd and restart unbound
|
||||
|
||||
- name: make sure unbound starts after wg-quick@wg0
|
||||
block:
|
||||
- name: ensure unbound.service.d directory exists
|
||||
ansible.builtin.file:
|
||||
path: /etc/systemd/system/unbound.service.d
|
||||
state: directory
|
||||
mode: "0755"
|
||||
- name: configure unbound systemd service
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/unbound.service.d/override.conf
|
||||
content: |
|
||||
[Unit]
|
||||
After=wg-quick@wg0.service
|
||||
Requires=wg-quick@wg0.service
|
||||
notify: Reload systemd and restart unbound
|
||||
|
||||
- name: enables unbound service
|
||||
ansible.builtin.service:
|
||||
name: unbound
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
- name: firewall ufw rules for unbound
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ unbound_port }}"
|
||||
proto: any
|
||||
src: "{{ item }}"
|
||||
direction: in
|
||||
loop: "{{ unbound_firewall_allowed_sources | default([]) }}"
|
||||
9
roles/unbound/templates/custom-lan.conf.j2
Normal file
9
roles/unbound/templates/custom-lan.conf.j2
Normal file
@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
view:
|
||||
name: "lan"
|
||||
{% for host, ips in unbound_custom_lan_records.items() %}
|
||||
local-data: "{{ host }}. IN A {{ ips.v4 }}"
|
||||
{% if ips.v6 is defined %}
|
||||
local-data: "{{ host }}. IN AAAA {{ ips.v6 }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
9
roles/unbound/templates/custom-vpn.conf.j2
Normal file
9
roles/unbound/templates/custom-vpn.conf.j2
Normal file
@ -0,0 +1,9 @@
|
||||
# {{ ansible_managed }}
|
||||
view:
|
||||
name: "vpn"
|
||||
{% for host, ips in unbound_custom_vpn_records.items() %}
|
||||
local-data: "{{ host }}. IN A {{ ips.v4 }}"
|
||||
{% if ips.v6 is defined %}
|
||||
local-data: "{{ host }}. IN AAAA {{ ips.v6 }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
110
roles/unbound/templates/unbound.conf.j2
Normal file
110
roles/unbound/templates/unbound.conf.j2
Normal file
@ -0,0 +1,110 @@
|
||||
# {{ ansible_managed }}
|
||||
# see: https://unix.stackexchange.com/a/617194
|
||||
# see: https://unix.stackexchange.com/questions/617091/can-you-specify-a-different-configuration-for-different-interfaces-in-unbound
|
||||
# see: https://www.dnsleaktest.com
|
||||
server:
|
||||
verbosity: 0
|
||||
|
||||
# listening port
|
||||
port: {{ unbound_port }}
|
||||
|
||||
# Define interfaces binds
|
||||
interface: lo
|
||||
interface: lan0
|
||||
interface: wg0
|
||||
|
||||
# Define access controls (note that ufw might be also configured)
|
||||
access-control: 0.0.0.0/0 refuse
|
||||
access-control: 192.168.1.0/24 allow # lan0 interface
|
||||
access-control: 192.168.27.0/27 allow # wg0 interface
|
||||
access-control: ::0/0 refuse
|
||||
access-control: ::1 allow
|
||||
|
||||
# Specify custom local answers for each interface by using views:
|
||||
access-control-view: 192.168.1.56/24 lan
|
||||
access-control-view: 192.168.27.1/27 vpn
|
||||
|
||||
do-ip4: yes
|
||||
do-udp: yes
|
||||
do-tcp: yes
|
||||
do-ip6: yes
|
||||
|
||||
# You want to leave this to no unless you have *native* IPv6. With 6to4 and
|
||||
# Terredo tunnels your web browser should favor IPv4 for the same reasons
|
||||
prefer-ip6: no
|
||||
|
||||
# Use this only when you downloaded the list of primary root servers!
|
||||
# If you use the default dns-root-data package, unbound will find it automatically
|
||||
root-hints: "{{ unbound_root_hints_path }}"
|
||||
|
||||
# enable to not answer id.server and hostname.bind queries.
|
||||
hide-identity: yes
|
||||
|
||||
# enable to not answer version.server and version.bind queries.
|
||||
hide-version: yes
|
||||
|
||||
# Trust glue only if it is within the server's authority
|
||||
harden-glue: yes
|
||||
|
||||
# Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes BOGUS
|
||||
harden-dnssec-stripped: yes
|
||||
|
||||
# Don't use Capitalization randomization as it known to cause DNSSEC issues sometimes
|
||||
# see https://discourse.pi-hole.net/t/unbound-stubby-or-dnscrypt-proxy/9378 for further details
|
||||
use-caps-for-id: no
|
||||
|
||||
# the time to live (TTL) value lower bound, in seconds. Default 0.
|
||||
# If more than an hour could easily give trouble due to stale data.
|
||||
cache-min-ttl: 3600
|
||||
|
||||
# the time to live (TTL) value cap for RRsets and messages in the
|
||||
# cache. Items are not cached for longer. In seconds.
|
||||
cache-max-ttl: 86400
|
||||
|
||||
# Reduce EDNS reassembly buffer size.
|
||||
# IP fragmentation is unreliable on the Internet today, and can cause
|
||||
# transmission failures when large DNS messages are sent via UDP. Even
|
||||
# when fragmentation does work, it may not be secure; it is theoretically
|
||||
# possible to spoof parts of a fragmented DNS message, without easy
|
||||
# detection at the receiving end. Recently, there was an excellent study
|
||||
# >>> Defragmenting DNS - Determining the optimal maximum UDP response size for DNS <<<
|
||||
# by Axel Koolhaas, and Tjeerd Slokker (https://indico.dns-oarc.net/event/36/contributions/776/)
|
||||
# in collaboration with NLnet Labs explored DNS using real world data from the
|
||||
# the RIPE Atlas probes and the researchers suggested different values for
|
||||
# IPv4 and IPv6 and in different scenarios. They advise that servers should
|
||||
# be configured to limit DNS messages sent over UDP to a size that will not
|
||||
# trigger fragmentation on typical network links. DNS servers can switch
|
||||
# from UDP to TCP when a DNS response is too big to fit in this limited
|
||||
# buffer size. This value has also been suggested in DNS Flag Day 2020.
|
||||
edns-buffer-size: 1232
|
||||
|
||||
# Perform prefetching of close to expired message cache entries
|
||||
# This only applies to domains that have been frequently queried
|
||||
prefetch: yes
|
||||
|
||||
# One thread should be sufficient, can be increased on beefy machines.
|
||||
# In reality for most users running on small networks or on a single machine,
|
||||
# it should be unnecessary to seek performance enhancement by increasing num-threads above 1.
|
||||
num-threads: 1
|
||||
|
||||
# Ensure kernel buffer is large enough to not lose messages in traffic spikes
|
||||
so-rcvbuf: 1m
|
||||
|
||||
# Ensure privacy of local IP ranges
|
||||
private-address: 192.168.0.0/16
|
||||
private-address: 169.254.0.0/16
|
||||
private-address: 172.16.0.0/12
|
||||
private-address: 10.0.0.0/8
|
||||
private-address: fd00::/8
|
||||
private-address: fe80::/10
|
||||
|
||||
# Allow the domain (and its subdomains) to contain private addresses.
|
||||
# local-data statements are allowed to contain private addresses too.
|
||||
private-domain: "{{ unbound_custom_lan_domain }}"
|
||||
|
||||
# Enable DNSSEC
|
||||
auto-trust-anchor-file: "{{ unbound_anchor_root_key }}"
|
||||
|
||||
include: "{{ unbound_ad_servers_config_path }}"
|
||||
include: "{{ unbound_custom_lan_config_path }}"
|
||||
include: "{{ unbound_custom_vpn_config_path }}"
|
||||
8
roles/wireguard/defaults/main.yml
Normal file
8
roles/wireguard/defaults/main.yml
Normal file
@ -0,0 +1,8 @@
|
||||
wireguard_primary_interface: "{{ network_interfaces.0.name }}"
|
||||
wireguard_port: 51820 # static port to receive input connections
|
||||
wireguard_server_mode: true # enables NAT and open port
|
||||
wireguard_interface: wg0
|
||||
wireguard_config_base_path: /etc/wireguard
|
||||
wireguard_address: 192.168.27.1/27
|
||||
wireguard_dns: 192.168.27.1
|
||||
wireguard_peers: []
|
||||
60
roles/wireguard/tasks/main.yml
Normal file
60
roles/wireguard/tasks/main.yml
Normal file
@ -0,0 +1,60 @@
|
||||
- name: install wireguard
|
||||
package:
|
||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('wireguard-tools', 'wireguard') }}"
|
||||
state: present
|
||||
|
||||
# to support "DNS=" if used in a "client way"
|
||||
- name: install openresolv/resolveconf
|
||||
package:
|
||||
name: "{{ (ansible_facts['os_family'] == 'Archlinux') | ternary('openresolv', 'resolvconf') }}"
|
||||
state: present
|
||||
|
||||
- name: ensure wireguard configuration is only owned by root
|
||||
file:
|
||||
path: "{{ wireguard_config_base_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0700
|
||||
recurse: yes
|
||||
|
||||
- name: check if private key exists
|
||||
stat:
|
||||
path: "{{ wireguard_config_base_path }}/privatekey"
|
||||
register: pkey_file
|
||||
|
||||
- name: generate wireguard keys if not present
|
||||
shell: wg genkey | tee {{ wireguard_config_base_path }}/privatekey | wg pubkey > {{ wireguard_config_base_path }}/publickey
|
||||
when: not pkey_file.stat.exists
|
||||
|
||||
- name: retrieve wireguard private key from file
|
||||
slurp:
|
||||
src: "{{ wireguard_config_base_path }}/privatekey"
|
||||
register: private_key
|
||||
|
||||
- name: set wireguard private key
|
||||
set_fact:
|
||||
wireguard_private_key: "{{ private_key['content'] | b64decode }}"
|
||||
|
||||
- name: disable "dns=" instruction if unbound is used to avoid race conditions at startup
|
||||
set_fact:
|
||||
wireguard_dns:
|
||||
when: unbound_custom_lan_records is defined
|
||||
|
||||
- name: install wireguard config
|
||||
template:
|
||||
src: wireguard.conf.j2
|
||||
dest: /etc/wireguard/{{ wireguard_interface }}.conf
|
||||
|
||||
- name: start and enable service
|
||||
service:
|
||||
name: wg-quick@{{ wireguard_interface }}
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
|
||||
- name: configure the firewall for wireguard
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: "{{ wireguard_port }}"
|
||||
proto: udp
|
||||
direction: in
|
||||
16
roles/wireguard/templates/wireguard.conf.j2
Normal file
16
roles/wireguard/templates/wireguard.conf.j2
Normal file
@ -0,0 +1,16 @@
|
||||
[Interface]
|
||||
Address = {{ wireguard_address }}
|
||||
{% if wireguard_dns %}DNS = {{ wireguard_dns }}
|
||||
{% endif %}
|
||||
PrivateKey = {{ wireguard_private_key }}
|
||||
{% if wireguard_server_mode %}PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ wireguard_primary_interface }} -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o {{ wireguard_primary_interface }} -j MASQUERADE
|
||||
ListenPort = {{ wireguard_port }}
|
||||
{% endif %}
|
||||
|
||||
{% for peer in wireguard_peers %}# {{ peer.name }}
|
||||
[Peer]
|
||||
PublicKey = {{ peer.public_key }}
|
||||
AllowedIPs = {{ peer.allowed_ips | join(',') }}
|
||||
{% if peer.endpoint is defined %}Endpoint = {{ peer.endpoint }}{% endif %}
|
||||
{% endfor %}
|
||||
43
roles/zfs/README.md
Normal file
43
roles/zfs/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Disks
|
||||
|
||||
Ansible community support for ZFS is limited to create filesystems, volumes and snapshots. There is no support for managing zpools, so here it is.
|
||||
|
||||
## Inventory
|
||||
|
||||
Here is an example inventory file you can use with this role:
|
||||
|
||||
```yaml
|
||||
zfs_pools:
|
||||
- name: peace
|
||||
type: raidz1
|
||||
devices:
|
||||
- ata-SOME-DISK-LABEL-1
|
||||
- ata-SOME-DISK-LABEL-2
|
||||
options:
|
||||
ashift: 12
|
||||
root: /mnt/peace
|
||||
state: present
|
||||
```
|
||||
|
||||
And you will get raid1 zpool peace with two disks, with 12 ashift.
|
||||
|
||||
You can use a variety of options, see the [zpoolprops(7)](https://openzfs.github.io/openzfs-docs/man/master/7/zpoolprops.7.html) man page.
|
||||
|
||||
And for your zfs filesystems:
|
||||
|
||||
```yaml
|
||||
zfs_datasets:
|
||||
- name: peace/pictures
|
||||
state: present
|
||||
- name: peace/movies
|
||||
state: present
|
||||
extra_zfs_properties:
|
||||
mountpoint: /mnt/peace/movies
|
||||
quota: 500G
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- https://docs.ansible.com/ansible/latest/collections/community/general/zfs_module.html
|
||||
- https://github.com/mrlesmithjr/ansible-zfs/blob/master/tasks/manage_zfs.yml
|
||||
- https://wiki.archlinux.org/title/ZFS
|
||||
0
roles/zfs/defaults/main.yml
Normal file
0
roles/zfs/defaults/main.yml
Normal file
16
roles/zfs/tasks/dataset-ownership.yml
Normal file
16
roles/zfs/tasks/dataset-ownership.yml
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
# due to Ansible limitations, we cannot loop over a block, so we loop over a distinct tasks file...
|
||||
# @see https://stackoverflow.com/a/58911694
|
||||
- name: set ownership on dataset mountpoint
|
||||
block:
|
||||
- name: get the mountpoint
|
||||
ansible.builtin.shell: "zfs get -H -o value mountpoint {{ dataset.name }}"
|
||||
register: mountpoint
|
||||
changed_when: false
|
||||
- name: set owner of mountpoints
|
||||
file:
|
||||
path: "{{ mountpoint.stdout }}"
|
||||
owner: "{{ dataset.user | default(main_user) }}"
|
||||
group: "{{ dataset.group | default(main_user) }}"
|
||||
state: directory
|
||||
recurse: true
|
||||
25
roles/zfs/tasks/datasets.yml
Normal file
25
roles/zfs/tasks/datasets.yml
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
# see: https://docs.ansible.com/ansible/latest/collections/community/general/zfs_module.html
|
||||
- name: managing filesystems, volumes, snapshots
|
||||
zfs:
|
||||
name: "{{ item.name }}"
|
||||
state: "{{ item.state }}"
|
||||
extra_zfs_properties: "{{ item.extra_zfs_properties|default(omit) }}"
|
||||
origin: "{{ item.origin|default(omit) }}"
|
||||
with_items: "{{ zfs_datasets }}"
|
||||
|
||||
- command:
|
||||
cmd: whoami
|
||||
no_log: true
|
||||
become: false
|
||||
register: main_user
|
||||
|
||||
- set_fact:
|
||||
main_user: "{{ main_user.stdout }}"
|
||||
no_log: true
|
||||
|
||||
- name: set dataset ownership
|
||||
include_tasks: "./dataset-ownership.yml"
|
||||
loop: "{{ zfs_datasets }}"
|
||||
loop_control:
|
||||
loop_var: dataset
|
||||
12
roles/zfs/tasks/delete-pool.yml
Normal file
12
roles/zfs/tasks/delete-pool.yml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
# due to Ansible limitations, we cannot loop over a block, so we loop over a distinct tasks file...
|
||||
# @see https://stackoverflow.com/a/58911694
|
||||
- name: prompt the user for confirmation
|
||||
ansible.builtin.pause:
|
||||
prompt: "[IRREVERSIBLE] Are you sure you want to delete zpool {{ zpool.name }}?"
|
||||
echo: yes
|
||||
register: confirmation
|
||||
|
||||
- name: deleting zpool
|
||||
ansible.builtin.command: "zpool destroy {{ zpool.name }}"
|
||||
when: confirmation.user_input | lower in ['yes', 'y']
|
||||
91
roles/zfs/tasks/install.yml
Normal file
91
roles/zfs/tasks/install.yml
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
- name: Check if zfs-linux-lts is installed
|
||||
command: pacman -Qi zfs-dkms
|
||||
register: zfs_dkms_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install zfs
|
||||
when: zfs_dkms_installed.stderr
|
||||
block:
|
||||
- name: disable SUDOERS password prompt for makepkg
|
||||
no_log: true
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
# Note: I successfully tried zfs-linux-lts and zfs-linux-lts-headers, but
|
||||
# the next kernel upgrade failed: cyclic dependency.
|
||||
# Using dkms with the lts linux kernel is a better approach IMO.
|
||||
- name: Install zfs
|
||||
become: false
|
||||
command:
|
||||
cmd: "paru -S --noconfirm zfs-dkms zfs-utils"
|
||||
|
||||
- name: Restore SUDOERS password prompt after yay
|
||||
no_log: true
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL:ALL) ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- name: check if /etc/hostid is present
|
||||
stat:
|
||||
path: /etc/hostid
|
||||
register: hostid
|
||||
changed_when: false
|
||||
|
||||
- name: generate /etc/hostid if not present
|
||||
when: not hostid.stat.exists
|
||||
command: zgenhostid $(hostid)
|
||||
|
||||
- name: Check if zrepl is installed
|
||||
command: pacman -Qi zrepl
|
||||
register: zrepl_installed
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Install zrepl
|
||||
when: zrepl_installed.stderr
|
||||
block:
|
||||
- name: disable SUDOERS password prompt for makepkg
|
||||
no_log: true
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- name: Install zrepl
|
||||
become: false
|
||||
command:
|
||||
cmd: "paru -S --noconfirm zrepl"
|
||||
|
||||
- name: Restore SUDOERS password prompt after paru
|
||||
no_log: true
|
||||
lineinfile:
|
||||
dest: /etc/sudoers
|
||||
state: present
|
||||
regexp: "^#?%wheel"
|
||||
line: "%wheel ALL=(ALL:ALL) ALL"
|
||||
validate: /usr/sbin/visudo -cf %s
|
||||
|
||||
- name: Enable zfs services
|
||||
service:
|
||||
name: "{{ item }}"
|
||||
enabled: true
|
||||
state: started
|
||||
loop:
|
||||
- zfs.target
|
||||
- zfs-import.target
|
||||
- zfs-volumes.target
|
||||
- zfs-import-scan.service
|
||||
- zfs-volume-wait.service
|
||||
# Please note /etc/zfs/zpool.cache should not be present, as it is deprecated
|
||||
# and we are using zfs-import-scan.service, which rely on blkid.
|
||||
9
roles/zfs/tasks/main.yml
Normal file
9
roles/zfs/tasks/main.yml
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: Install ZFS
|
||||
include_tasks: install.yml
|
||||
|
||||
- name: Configure Zpools
|
||||
include_tasks: pools.yml
|
||||
|
||||
- name: "Setup ZFS datasets: filesystems, snapshots, volumes"
|
||||
include_tasks: datasets.yml
|
||||
47
roles/zfs/tasks/pools.yml
Normal file
47
roles/zfs/tasks/pools.yml
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
# tasks file for zfs management
|
||||
# Based on: https://github.com/mrlesmithjr/ansible-zfs/blob/master/tasks/manage_zfs.yml
|
||||
# Expected variables in your inventory: zfs_pools.
|
||||
|
||||
- name: checking existing zpool(s)
|
||||
ansible.builtin.shell: "zpool list -H -o name"
|
||||
changed_when: false
|
||||
register: current_zp_state
|
||||
check_mode: no
|
||||
when: zfs_pools is defined
|
||||
|
||||
- name: gather zpool status
|
||||
ansible.builtin.shell: zpool status
|
||||
changed_when: false
|
||||
register: zpool_devices
|
||||
when: zfs_pools is defined
|
||||
|
||||
- name: creating basic zpool(s)
|
||||
ansible.builtin.command: "zpool create {{ '-o '+ item.options.items() |map('join', '=') | join (' -o ') if item.options is defined else '' }} {{ item.name }} {{ item.devices|join (' ') }}"
|
||||
with_items: "{{ zfs_pools }}"
|
||||
when:
|
||||
- zfs_pools is defined
|
||||
- item.type == "basic"
|
||||
- item.name not in current_zp_state.stdout
|
||||
- item.state == "present"
|
||||
- item.devices[0] not in zpool_devices.stdout
|
||||
|
||||
- name: creating mirror/zraid zpool(s)
|
||||
ansible.builtin.command: "zpool create {{ '-o '+ item.options.items() |map('join', '=') | join (' -o ') if item.options is defined else '' }} {{ item.name }} {{ item.type }} {{ item.devices|join (' ') }}"
|
||||
with_items: "{{ zfs_pools }}"
|
||||
when:
|
||||
- zfs_pools is defined
|
||||
- item.type != "basic"
|
||||
- item.name not in current_zp_state.stdout
|
||||
- item.state == "present"
|
||||
- item.devices[0] not in zpool_devices.stdout
|
||||
|
||||
- name: deleting zpool(s) with care
|
||||
include_tasks: "./delete-pool.yml"
|
||||
when:
|
||||
- zfs_pools is defined
|
||||
- zpool.name in current_zp_state.stdout_lines
|
||||
- zpool.state == "absent"
|
||||
loop: "{{ zfs_pools }}"
|
||||
loop_control:
|
||||
loop_var: zpool
|
||||
9
roles/zsh/defaults/main.yml
Normal file
9
roles/zsh/defaults/main.yml
Normal file
@ -0,0 +1,9 @@
|
||||
zsh_home: "{{ '/root' if zsh_user == 'root' else '/home/' + zsh_user }}"
|
||||
zsh_base_config: "{{ zsh_home }}/.zshrc"
|
||||
zsh_config_path: "{{ zsh_home }}/.config/zsh"
|
||||
zsh_config_file: "{{ zsh_config_path }}/.zshrc"
|
||||
zsh_p10k_theme_config: "{{ zsh_config_path }}/p10k.zsh"
|
||||
zsh_users:
|
||||
- jokester
|
||||
- root
|
||||
zsh_plugins_path: "/opt/zsh/plugins"
|
||||
14
roles/zsh/tasks/main.yml
Normal file
14
roles/zsh/tasks/main.yml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: install zsh
|
||||
package:
|
||||
name: zsh
|
||||
state: present
|
||||
|
||||
- name: install zsh plugins
|
||||
include_tasks: plugins.yml
|
||||
|
||||
- name: setup zsh for the user(s)
|
||||
include_tasks: user-setup.yml
|
||||
vars:
|
||||
zsh_user: "{{ item }}"
|
||||
loop: "{{ zsh_users | default([]) }}"
|
||||
50
roles/zsh/tasks/plugins.yml
Normal file
50
roles/zsh/tasks/plugins.yml
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
- name: ensure plugins directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ zsh_plugins_path }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: users
|
||||
mode: "0755"
|
||||
|
||||
- name: add a readme file to advice from where this comes
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ zsh_plugins_path }}/README.md"
|
||||
content: |
|
||||
# zsh plugins
|
||||
Managed by Ansible
|
||||
mode: "0644"
|
||||
group: users
|
||||
owner: root
|
||||
|
||||
- name: "git clone plugins"
|
||||
git:
|
||||
repo: "{{ item.repo }}"
|
||||
dest: "{{ item.dest }}"
|
||||
update: yes
|
||||
version: master
|
||||
loop:
|
||||
- {
|
||||
repo: https://github.com/zsh-users/zsh-syntax-highlighting.git,
|
||||
dest: "{{ zsh_plugins_path }}/zsh-syntax-highlighting",
|
||||
}
|
||||
- {
|
||||
repo: https://github.com/zsh-users/zsh-autosuggestions.git,
|
||||
dest: "{{ zsh_plugins_path }}/zsh-autosuggestions",
|
||||
}
|
||||
- {
|
||||
repo: https://github.com/romkatv/powerlevel10k.git,
|
||||
dest: "{{ zsh_plugins_path }}/powerlevel10k",
|
||||
}
|
||||
|
||||
- name: assert plugins are available for any user
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
owner: root
|
||||
group: users
|
||||
mode: "0755"
|
||||
state: directory
|
||||
loop:
|
||||
- "{{ zsh_plugins_path }}/zsh-syntax-highlighting"
|
||||
- "{{ zsh_plugins_path }}/zsh-autosuggestions"
|
||||
- "{{ zsh_plugins_path }}/powerlevel10k"
|
||||
44
roles/zsh/tasks/user-setup.yml
Normal file
44
roles/zsh/tasks/user-setup.yml
Normal file
@ -0,0 +1,44 @@
|
||||
- name: setup zsh base config
|
||||
ansible.builtin.template:
|
||||
src: main.zshrc.j2
|
||||
dest: "{{ zsh_base_config }}"
|
||||
owner: "{{ zsh_user }}"
|
||||
group: "{{ zsh_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: setup .config/zsh directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ zsh_config_path }}"
|
||||
state: directory
|
||||
owner: "{{ zsh_user }}"
|
||||
group: "{{ zsh_user }}"
|
||||
mode: "0700"
|
||||
|
||||
- name: configure zsh config
|
||||
ansible.builtin.template:
|
||||
src: zshrc.j2
|
||||
dest: "{{ zsh_config_file }}"
|
||||
owner: "{{ zsh_user }}"
|
||||
group: "{{ zsh_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: copy aliases
|
||||
ansible.builtin.copy:
|
||||
src: ./templates/aliases
|
||||
dest: "{{ zsh_config_path }}/aliases"
|
||||
owner: "{{ zsh_user }}"
|
||||
group: "{{ zsh_user }}"
|
||||
mode: "0600"
|
||||
|
||||
- name: change default shell to zsh
|
||||
user:
|
||||
name: "{{ zsh_user }}"
|
||||
shell: /bin/zsh
|
||||
|
||||
- name: configure powerlevel10k theme
|
||||
ansible.builtin.copy:
|
||||
src: "./templates/{{ 'root.p10k.zsh' if zsh_user == 'root' else 'user.p10k.zsh' }}"
|
||||
dest: "{{ zsh_p10k_theme_config }}"
|
||||
owner: "{{ zsh_user }}"
|
||||
group: "{{ zsh_user }}"
|
||||
mode: "0600"
|
||||
3
roles/zsh/templates/aliases
Normal file
3
roles/zsh/templates/aliases
Normal file
@ -0,0 +1,3 @@
|
||||
alias dc="docker compose"
|
||||
alias pc="podman compose"
|
||||
alias w="cd ~/workspace"
|
||||
2
roles/zsh/templates/main.zshrc.j2
Normal file
2
roles/zsh/templates/main.zshrc.j2
Normal file
@ -0,0 +1,2 @@
|
||||
# {{ ansible_managed }}
|
||||
source {{ zsh_config_file }}
|
||||
788
roles/zsh/templates/root.p10k.zsh
Normal file
788
roles/zsh/templates/root.p10k.zsh
Normal file
@ -0,0 +1,788 @@
|
||||
'builtin' 'local' '-a' 'p10k_config_opts'
|
||||
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
|
||||
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
|
||||
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
|
||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
||||
|
||||
() {
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
|
||||
|
||||
[[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
|
||||
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
|
||||
os_icon # os identifier
|
||||
dir # current directory
|
||||
vcs # git status
|
||||
# prompt_char # prompt symbol
|
||||
)
|
||||
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
|
||||
status # exit code of the last command
|
||||
command_execution_time # duration of the last command
|
||||
background_jobs # presence of background jobs
|
||||
direnv # direnv status (https://direnv.net/)
|
||||
asdf # asdf version manager (https://github.com/asdf-vm/asdf)
|
||||
virtualenv # python virtual environment (https://docs.python.org/3/library/venv.html)
|
||||
anaconda # conda environment (https://conda.io/)
|
||||
pyenv # python environment (https://github.com/pyenv/pyenv)
|
||||
goenv # go environment (https://github.com/syndbg/goenv)
|
||||
nodenv # node.js version from nodenv (https://github.com/nodenv/nodenv)
|
||||
nvm # node.js version from nvm (https://github.com/nvm-sh/nvm)
|
||||
nodeenv # node.js environment (https://github.com/ekalinin/nodeenv)
|
||||
# node_version # node.js version
|
||||
# go_version # go version (https://golang.org)
|
||||
# rust_version # rustc version (https://www.rust-lang.org)
|
||||
# dotnet_version # .NET version (https://dotnet.microsoft.com)
|
||||
# php_version # php version (https://www.php.net/)
|
||||
# laravel_version # laravel php framework version (https://laravel.com/)
|
||||
# java_version # java version (https://www.java.com/)
|
||||
# package # name@version from package.json (https://docs.npmjs.com/files/package.json)
|
||||
rbenv # ruby version from rbenv (https://github.com/rbenv/rbenv)
|
||||
rvm # ruby version from rvm (https://rvm.io)
|
||||
fvm # flutter version management (https://github.com/leoafarias/fvm)
|
||||
luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
|
||||
jenv # java version from jenv (https://github.com/jenv/jenv)
|
||||
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
|
||||
perlbrew # perl version from perlbrew (https://github.com/gugod/App-perlbrew)
|
||||
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
|
||||
scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
|
||||
haskell_stack # haskell version from stack (https://haskellstack.org/)
|
||||
kubecontext # current kubernetes context (https://kubernetes.io/)
|
||||
terraform # terraform workspace (https://www.terraform.io)
|
||||
# terraform_version # terraform version (https://www.terraform.io)
|
||||
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
|
||||
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
|
||||
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
|
||||
gcloud # google cloud cli account and project (https://cloud.google.com/)
|
||||
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
|
||||
toolbox # toolbox name (https://github.com/containers/toolbox)
|
||||
context # user@hostname
|
||||
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
|
||||
ranger # ranger shell (https://github.com/ranger/ranger)
|
||||
yazi # yazi shell (https://github.com/sxyazi/yazi)
|
||||
nnn # nnn shell (https://github.com/jarun/nnn)
|
||||
lf # lf shell (https://github.com/gokcehan/lf)
|
||||
xplr # xplr shell (https://github.com/sayanarijit/xplr)
|
||||
vim_shell # vim shell indicator (:sh)
|
||||
midnight_commander # midnight commander shell (https://midnight-commander.org/)
|
||||
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
|
||||
chezmoi_shell # chezmoi shell (https://www.chezmoi.io/)
|
||||
vi_mode # vi mode (you don't need this if you've enabled prompt_char)
|
||||
# vpn_ip # virtual private network indicator
|
||||
# load # CPU load
|
||||
# disk_usage # disk usage
|
||||
# ram # free RAM
|
||||
# swap # used swap
|
||||
todo # todo items (https://github.com/todotxt/todo.txt-cli)
|
||||
timewarrior # timewarrior tracking status (https://timewarrior.net/)
|
||||
taskwarrior # taskwarrior task count (https://taskwarrior.org/)
|
||||
per_directory_history # Oh My Zsh per-directory-history local/global indicator
|
||||
# cpu_arch # CPU architecture
|
||||
time # current time
|
||||
# ip # ip address and bandwidth usage for a specified network interface
|
||||
# public_ip # public IP address
|
||||
# proxy # system-wide http/https/ftp proxy
|
||||
# battery # internal battery
|
||||
# wifi # wifi speed
|
||||
# example # example user-defined segment (see prompt_example function below)
|
||||
)
|
||||
|
||||
typeset -g POWERLEVEL9K_MODE=nerdfont-v3
|
||||
typeset -g POWERLEVEL9K_ICON_PADDING=none
|
||||
|
||||
typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=
|
||||
|
||||
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
|
||||
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX='%242F╭─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX='%242F├─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX='%242F╰─'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX='%242F─╮'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX='%242F─┤'
|
||||
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX='%242F─╯'
|
||||
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_BACKGROUND=
|
||||
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_GAP_BACKGROUND=
|
||||
if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
|
||||
# The color of the filler. You'll probably want to match the color of POWERLEVEL9K_MULTILINE
|
||||
# ornaments defined above.
|
||||
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242
|
||||
# Start filler from the edge of the screen if there are no left segments on the first line.
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}'
|
||||
# End filler on the edge of the screen if there are no right segments on the first line.
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}'
|
||||
fi
|
||||
|
||||
typeset -g POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR='\uE0B1'
|
||||
typeset -g POWERLEVEL9K_RIGHT_SUBSEGMENT_SEPARATOR='\uE0B3'
|
||||
typeset -g POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR='\uE0B0'
|
||||
typeset -g POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR='\uE0B2'
|
||||
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='\uE0B0'
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='\uE0B2'
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=''
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL=''
|
||||
typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=
|
||||
|
||||
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=232
|
||||
typeset -g POWERLEVEL9K_OS_ICON_BACKGROUND=7
|
||||
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_BACKGROUND=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯'
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮'
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_{LEFT,RIGHT}_WHITESPACE=
|
||||
|
||||
typeset -g POWERLEVEL9K_DIR_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_DIR_FOREGROUND=254
|
||||
typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique
|
||||
typeset -g POWERLEVEL9K_SHORTEN_DELIMITER=
|
||||
typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=250
|
||||
typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=255
|
||||
typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true
|
||||
local anchor_files=(
|
||||
.bzr
|
||||
.citc
|
||||
.git
|
||||
.hg
|
||||
.node-version
|
||||
.python-version
|
||||
.go-version
|
||||
.ruby-version
|
||||
.lua-version
|
||||
.java-version
|
||||
.perl-version
|
||||
.php-version
|
||||
.tool-versions
|
||||
.mise.toml
|
||||
.shorten_folder_marker
|
||||
.svn
|
||||
.terraform
|
||||
CVS
|
||||
Cargo.toml
|
||||
composer.json
|
||||
go.mod
|
||||
package.json
|
||||
stack.yaml
|
||||
)
|
||||
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
|
||||
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
|
||||
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
|
||||
typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80
|
||||
typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40
|
||||
typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50
|
||||
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
|
||||
|
||||
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
|
||||
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_CLEAN_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_VCS_MODIFIED_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_VCS_CONFLICTED_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=8
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON='\uF126 '
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?'
|
||||
|
||||
function my_git_formatter() {
|
||||
emulate -L zsh
|
||||
|
||||
if [[ -n $P9K_CONTENT ]]; then
|
||||
# If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from
|
||||
# gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
|
||||
typeset -g my_git_format=$P9K_CONTENT
|
||||
return
|
||||
fi
|
||||
|
||||
# Styling for different parts of Git status.
|
||||
local meta='%7F' # white foreground
|
||||
local clean='%0F' # black foreground
|
||||
local modified='%0F' # black foreground
|
||||
local untracked='%0F' # black foreground
|
||||
local conflicted='%1F' # red foreground
|
||||
|
||||
local res
|
||||
|
||||
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
|
||||
local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
|
||||
# If local branch name is at most 32 characters long, show it in full.
|
||||
# Otherwise show the first 12 … the last 12.
|
||||
# Tip: To always show local branch name in full without truncation, delete the next line.
|
||||
(( $#branch > 32 )) && branch[13,-13]="…" # <-- this line
|
||||
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
|
||||
fi
|
||||
|
||||
if [[ -n $VCS_STATUS_TAG
|
||||
# Show tag only if not on a branch.
|
||||
# Tip: To always show tag, delete the next line.
|
||||
&& -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
|
||||
]]; then
|
||||
local tag=${(V)VCS_STATUS_TAG}
|
||||
# If tag name is at most 32 characters long, show it in full.
|
||||
# Otherwise show the first 12 … the last 12.
|
||||
# Tip: To always show tag name in full without truncation, delete the next line.
|
||||
(( $#tag > 32 )) && tag[13,-13]="…" # <-- this line
|
||||
res+="${meta}#${clean}${tag//\%/%%}"
|
||||
fi
|
||||
|
||||
# Display the current Git commit if there is no branch and no tag.
|
||||
# Tip: To always display the current Git commit, delete the next line.
|
||||
[[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
|
||||
res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
|
||||
|
||||
# Show tracking branch name if it differs from local branch.
|
||||
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
|
||||
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
|
||||
fi
|
||||
|
||||
# Display "wip" if the latest commit's summary contains "wip" or "WIP".
|
||||
if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
|
||||
res+=" ${modified}wip"
|
||||
fi
|
||||
|
||||
if (( VCS_STATUS_COMMITS_AHEAD || VCS_STATUS_COMMITS_BEHIND )); then
|
||||
# ⇣42 if behind the remote.
|
||||
(( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
|
||||
# ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
|
||||
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
|
||||
(( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
|
||||
elif [[ -n $VCS_STATUS_REMOTE_BRANCH ]]; then
|
||||
# Tip: Uncomment the next line to display '=' if up to date with the remote.
|
||||
# res+=" ${clean}="
|
||||
fi
|
||||
|
||||
# ⇠42 if behind the push remote.
|
||||
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
|
||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
|
||||
# ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
|
||||
(( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
|
||||
# *42 if have stashes.
|
||||
(( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}"
|
||||
# 'merge' if the repo is in an unusual state.
|
||||
[[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}"
|
||||
# ~42 if have merge conflicts.
|
||||
(( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
|
||||
# +42 if have staged changes.
|
||||
(( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
|
||||
# !42 if have unstaged changes.
|
||||
(( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
|
||||
# ?42 if have untracked files. It's really a question mark, your font isn't broken.
|
||||
# See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon.
|
||||
# Remove the next line if you don't want to see untracked files at all.
|
||||
(( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}"
|
||||
# "─" if the number of unstaged files is unknown. This can happen due to
|
||||
# POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower
|
||||
# than the number of files in the Git index, or due to bash.showDirtyState being set to false
|
||||
# in the repository config. The number of staged and untracked files may also be unknown
|
||||
# in this case.
|
||||
(( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─"
|
||||
|
||||
typeset -g my_git_format=$res
|
||||
}
|
||||
functions -M my_git_formatter 2>/dev/null
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
|
||||
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter()))+${my_git_format}}'
|
||||
typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_OK=true
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔'
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔'
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_STATUS_OK_PIPE_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR=true
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true
|
||||
typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
|
||||
|
||||
typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=6
|
||||
typeset -g POWERLEVEL9K_BACKGROUND_JOBS_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false
|
||||
|
||||
typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_DIRENV_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_BACKGROUND=7
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global)
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB=
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_RUBY_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_PYTHON_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_GOLANG_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_NODEJS_BACKGROUND=2
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_RUST_BACKGROUND=208
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_BACKGROUND=5
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_FLUTTER_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_LUA_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_ASDF_JAVA_BACKGROUND=7
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_PERL_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_ERLANG_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_ELIXIR_BACKGROUND=5
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_POSTGRES_BACKGROUND=6
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_PHP_BACKGROUND=5
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_HASKELL_BACKGROUND=3
|
||||
|
||||
typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ASDF_JULIA_BACKGROUND=2
|
||||
|
||||
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_NORDVPN_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION=
|
||||
typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION=
|
||||
|
||||
typeset -g POWERLEVEL9K_RANGER_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_RANGER_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_YAZI_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_YAZI_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_NNN_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_NNN_BACKGROUND=6
|
||||
|
||||
typeset -g POWERLEVEL9K_LF_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_LF_BACKGROUND=6
|
||||
|
||||
typeset -g POWERLEVEL9K_XPLR_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_XPLR_BACKGROUND=6
|
||||
|
||||
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VIM_SHELL_BACKGROUND=2
|
||||
|
||||
typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_NIX_SHELL_BACKGROUND=4
|
||||
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_CHEZMOI_SHELL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_CHEZMOI_SHELL_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_BACKGROUND=1
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95
|
||||
typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false
|
||||
|
||||
typeset -g POWERLEVEL9K_VI_MODE_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VI_COMMAND_MODE_STRING=NORMAL
|
||||
typeset -g POWERLEVEL9K_VI_MODE_NORMAL_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_VI_VISUAL_MODE_STRING=VISUAL
|
||||
typeset -g POWERLEVEL9K_VI_MODE_VISUAL_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_VI_OVERWRITE_MODE_STRING=OVERTYPE
|
||||
typeset -g POWERLEVEL9K_VI_MODE_OVERWRITE_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_VI_INSERT_MODE_STRING=
|
||||
typeset -g POWERLEVEL9K_VI_MODE_INSERT_FOREGROUND=8
|
||||
|
||||
typeset -g POWERLEVEL9K_RAM_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_RAM_BACKGROUND=3
|
||||
|
||||
typeset -g POWERLEVEL9K_SWAP_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_SWAP_BACKGROUND=3
|
||||
|
||||
typeset -g POWERLEVEL9K_LOAD_WHICH=5
|
||||
typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_LOAD_NORMAL_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_LOAD_WARNING_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_LOAD_CRITICAL_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_TODO_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_TODO_BACKGROUND=8
|
||||
typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true
|
||||
typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=255
|
||||
typeset -g POWERLEVEL9K_TIMEWARRIOR_BACKGROUND=8
|
||||
|
||||
typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}'
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_TASKWARRIOR_BACKGROUND=6
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_LOCAL_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PER_DIRECTORY_HISTORY_GLOBAL_BACKGROUND=3
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_CPU_ARCH_BACKGROUND=3
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_CONTEXT_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%n@%m'
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m'
|
||||
typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m'
|
||||
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION=
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
|
||||
|
||||
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_ANACONDA_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_PYENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PYENV_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_GOENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_GOENV_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_NODENV_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_NODENV_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_NVM_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_NVM_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_NVM_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_NVM_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_NODEENV_BACKGROUND=0
|
||||
typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false
|
||||
typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER=
|
||||
|
||||
typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_NODE_VERSION_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true
|
||||
|
||||
typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=255
|
||||
typeset -g POWERLEVEL9K_GO_VERSION_BACKGROUND=2
|
||||
typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true
|
||||
|
||||
typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_RUST_VERSION_BACKGROUND=208
|
||||
typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true
|
||||
|
||||
typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_DOTNET_VERSION_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true
|
||||
|
||||
typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PHP_VERSION_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true
|
||||
|
||||
typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_LARAVEL_VERSION_BACKGROUND=7
|
||||
|
||||
typeset -g POWERLEVEL9K_RBENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_RBENV_BACKGROUND=1
|
||||
typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_JAVA_VERSION_BACKGROUND=7
|
||||
typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false
|
||||
|
||||
typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PACKAGE_BACKGROUND=6
|
||||
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_RVM_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_RVM_BACKGROUND=240
|
||||
typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false
|
||||
typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false
|
||||
|
||||
typeset -g POWERLEVEL9K_FVM_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_FVM_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_LUAENV_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_JENV_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_JENV_BACKGROUND=7
|
||||
typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_PLENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PLENV_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_PERLBREW_FOREGROUND=67
|
||||
typeset -g POWERLEVEL9K_PERLBREW_PROJECT_ONLY=true
|
||||
typeset -g POWERLEVEL9K_PERLBREW_SHOW_PREFIX=false
|
||||
|
||||
typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_PHPENV_BACKGROUND=5
|
||||
typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_SCALAENV_BACKGROUND=1
|
||||
typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
|
||||
typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
|
||||
typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
|
||||
|
||||
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_HASKELL_STACK_BACKGROUND=3
|
||||
|
||||
typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local)
|
||||
typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true
|
||||
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
|
||||
# '*prod*' PROD # These values are examples that are unlikely
|
||||
# '*test*' TEST # to match your needs. Customize them as needed.
|
||||
'*' OTHER)
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=4
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_OTHER_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=4
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_SHOW_ON_COMMAND='terraform|tf'
|
||||
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern|kubeseal|skaffold|kubent|kubecolor|cmctl|sparkctl'
|
||||
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
|
||||
# '*prod*' PROD # These values are examples that are unlikely
|
||||
# '*test*' TEST # to match your needs. Customize them as needed.
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_BACKGROUND=5
|
||||
|
||||
typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION=
|
||||
POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}'
|
||||
POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}'
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|cdk|terraform|pulumi|terragrunt'
|
||||
|
||||
typeset -g POWERLEVEL9K_AWS_CLASSES=(
|
||||
# '*prod*' PROD # These values are examples that are unlikely
|
||||
# '*test*' TEST # to match your needs. Customize them as needed.
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_AWS_DEFAULT_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
|
||||
|
||||
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_AWS_EB_ENV_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
|
||||
|
||||
typeset -g POWERLEVEL9K_AZURE_CLASSES=(
|
||||
# '*prod*' PROD # These values are examples that are unlikely
|
||||
# '*test*' TEST # to match your needs. Customize them as needed.
|
||||
'*' OTHER)
|
||||
|
||||
typeset -g POWERLEVEL9K_AZURE_OTHER_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_AZURE_OTHER_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs|gsutil'
|
||||
typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_GCLOUD_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
|
||||
typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
|
||||
|
||||
typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
|
||||
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
|
||||
# '*:*prod*:*' PROD # These values are examples that are unlikely
|
||||
# '*:*test*:*' TEST # to match your needs. Customize them as needed.
|
||||
'*' DEFAULT)
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_BACKGROUND=4
|
||||
|
||||
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
|
||||
|
||||
typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_TOOLBOX_BACKGROUND=3
|
||||
typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
|
||||
|
||||
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=7
|
||||
typeset -g POWERLEVEL9K_PUBLIC_IP_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_VPN_IP_BACKGROUND=6
|
||||
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
|
||||
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*|(zt.*)'
|
||||
typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false
|
||||
|
||||
typeset -g POWERLEVEL9K_IP_BACKGROUND=4
|
||||
typeset -g POWERLEVEL9K_IP_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='${P9K_IP_RX_RATE:+⇣$P9K_IP_RX_RATE }${P9K_IP_TX_RATE:+⇡$P9K_IP_TX_RATE }$P9K_IP_IP'
|
||||
typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
|
||||
|
||||
typeset -g POWERLEVEL9K_PROXY_FOREGROUND=4
|
||||
typeset -g POWERLEVEL9K_PROXY_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20
|
||||
typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=1
|
||||
typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=2
|
||||
typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_BATTERY_STAGES='\UF008E\UF007A\UF007B\UF007C\UF007D\UF007E\UF007F\UF0080\UF0081\UF0082\UF0079'
|
||||
typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false
|
||||
typeset -g POWERLEVEL9K_BATTERY_BACKGROUND=0
|
||||
|
||||
typeset -g POWERLEVEL9K_WIFI_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_WIFI_BACKGROUND=4
|
||||
|
||||
|
||||
typeset -g POWERLEVEL9K_TIME_FOREGROUND=0
|
||||
typeset -g POWERLEVEL9K_TIME_BACKGROUND=7
|
||||
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
|
||||
typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
|
||||
|
||||
function prompt_example() {
|
||||
p10k segment -b 1 -f 3 -i '⭐' -t 'hello, %n'
|
||||
}
|
||||
|
||||
function instant_prompt_example() {
|
||||
# Since prompt_example always makes the same `p10k segment` calls, we can call it from
|
||||
# instant_prompt_example. This will give us the same `example` prompt segment in the instant
|
||||
# and regular prompts.
|
||||
prompt_example
|
||||
}
|
||||
|
||||
typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=3
|
||||
typeset -g POWERLEVEL9K_EXAMPLE_BACKGROUND=1
|
||||
|
||||
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
|
||||
|
||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
|
||||
|
||||
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
|
||||
|
||||
(( ! $+functions[p10k] )) || p10k reload
|
||||
}
|
||||
|
||||
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
|
||||
|
||||
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
|
||||
'builtin' 'unset' 'p10k_config_opts'
|
||||
199
roles/zsh/templates/user.p10k.zsh
Normal file
199
roles/zsh/templates/user.p10k.zsh
Normal file
@ -0,0 +1,199 @@
|
||||
# Generated by Powerlevel10k configuration wizard on 2025-05-12 at 22:01 CEST.
|
||||
# Based on romkatv/powerlevel10k/config/p10k-pure.zsh, checksum 07533.
|
||||
# Wizard options: nerdfont-v3 + powerline, small icons, pure, rprompt, 24h time,
|
||||
# 2 lines, compact, transient_prompt, instant_prompt=verbose.
|
||||
# Type `p10k configure` to generate another config.
|
||||
#
|
||||
# Config file for Powerlevel10k with the style of Pure (https://github.com/sindresorhus/pure).
|
||||
#
|
||||
# Differences from Pure:
|
||||
#
|
||||
# - Git:
|
||||
# - `@c4d3ec2c` instead of something like `v1.4.0~11` when in detached HEAD state.
|
||||
# - No automatic `git fetch` (the same as in Pure with `PURE_GIT_PULL=0`).
|
||||
#
|
||||
# Apart from the differences listed above, the replication of Pure prompt is exact. This includes
|
||||
# even the questionable parts. For example, just like in Pure, there is no indication of Git status
|
||||
# being stale; prompt symbol is the same in command, visual and overwrite vi modes; when prompt
|
||||
# doesn't fit on one line, it wraps around with no attempt to shorten it.
|
||||
#
|
||||
# If you like the general style of Pure but not particularly attached to all its quirks, type
|
||||
# `p10k configure` and pick "Lean" style. This will give you slick minimalist prompt while taking
|
||||
# advantage of Powerlevel10k features that aren't present in Pure.
|
||||
|
||||
# Temporarily change options.
|
||||
'builtin' 'local' '-a' 'p10k_config_opts'
|
||||
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
|
||||
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
|
||||
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
|
||||
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
|
||||
|
||||
() {
|
||||
emulate -L zsh -o extended_glob
|
||||
|
||||
# Unset all configuration options.
|
||||
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
|
||||
|
||||
# Zsh >= 5.1 is required.
|
||||
[[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return
|
||||
|
||||
# Prompt colors.
|
||||
local grey='242'
|
||||
local red='1'
|
||||
local yellow='3'
|
||||
local blue='4'
|
||||
local magenta='5'
|
||||
local cyan='6'
|
||||
local white='7'
|
||||
|
||||
# Left prompt segments.
|
||||
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
|
||||
# =========================[ Line #1 ]=========================
|
||||
# context # user@host
|
||||
dir # current directory
|
||||
vcs # git status
|
||||
# command_execution_time # previous command duration
|
||||
# =========================[ Line #2 ]=========================
|
||||
newline # \n
|
||||
# virtualenv # python virtual environment
|
||||
prompt_char # prompt symbol
|
||||
)
|
||||
|
||||
# Right prompt segments.
|
||||
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
|
||||
# =========================[ Line #1 ]=========================
|
||||
command_execution_time # previous command duration
|
||||
virtualenv # python virtual environment
|
||||
context # user@host
|
||||
time # current time
|
||||
# =========================[ Line #2 ]=========================
|
||||
newline # \n
|
||||
)
|
||||
|
||||
# Basic style options that define the overall prompt look.
|
||||
typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
|
||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
|
||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
|
||||
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
|
||||
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons
|
||||
|
||||
# Add an empty line before each prompt except the first. This doesn't emulate the bug
|
||||
# in Pure that makes prompt drift down whenever you use the Alt-C binding from fzf or similar.
|
||||
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
|
||||
|
||||
# Magenta prompt symbol if the last command succeeded.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta
|
||||
# Red prompt symbol if the last command failed.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red
|
||||
# Default prompt symbol.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯'
|
||||
# Prompt symbol in command vi mode.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮'
|
||||
# Prompt symbol in visual vi mode is the same as in command mode.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='❮'
|
||||
# Prompt symbol in overwrite vi mode is the same as in command mode.
|
||||
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false
|
||||
|
||||
# Grey Python Virtual Environment.
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$grey
|
||||
# Don't show Python version.
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
|
||||
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
|
||||
|
||||
# Blue current directory.
|
||||
typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue
|
||||
|
||||
# Context format when root: user@host. The first part white, the rest grey.
|
||||
typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f"
|
||||
# Context format when not root: user@host. The whole thing grey.
|
||||
typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f"
|
||||
# Don't show context unless root or in SSH.
|
||||
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION=
|
||||
|
||||
# Show previous command duration only if it's >= 5s.
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5
|
||||
# Don't show fractional seconds. Thus, 7s rather than 7.3s.
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
|
||||
# Duration format: 1d 2h 3m 4s.
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
|
||||
# Yellow previous command duration.
|
||||
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow
|
||||
|
||||
# Grey Git prompt. This makes stale prompts indistinguishable from up-to-date ones.
|
||||
typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey
|
||||
|
||||
# Disable async loading indicator to make directories that aren't Git repositories
|
||||
# indistinguishable from large Git repositories without known state.
|
||||
typeset -g POWERLEVEL9K_VCS_LOADING_TEXT=
|
||||
|
||||
# Don't wait for Git status even for a millisecond, so that prompt always updates
|
||||
# asynchronously when Git state changes.
|
||||
typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0
|
||||
|
||||
# Cyan ahead/behind arrows.
|
||||
typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan
|
||||
# Don't show remote branch, current tag or stashes.
|
||||
typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind)
|
||||
# Don't show the branch icon.
|
||||
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
|
||||
# When in detached HEAD state, show @commit where branch normally goes.
|
||||
typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@'
|
||||
# Don't show staged, unstaged, untracked indicators.
|
||||
typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON=
|
||||
# Show '*' when there are staged, unstaged or untracked files.
|
||||
typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*'
|
||||
# Show '⇣' if local branch is behind remote.
|
||||
typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣'
|
||||
# Show '⇡' if local branch is ahead of remote.
|
||||
typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡'
|
||||
# Don't show the number of commits next to the ahead/behind arrows.
|
||||
typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1
|
||||
# Remove space between '⇣' and '⇡' and all trailing spaces.
|
||||
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }'
|
||||
|
||||
# Grey current time.
|
||||
typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey
|
||||
# Format for the current time: 09:51:02. See `man 3 strftime`.
|
||||
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
|
||||
# If set to true, time will update when you hit enter. This way prompts for the past
|
||||
# commands will contain the start times of their commands rather than the end times of
|
||||
# their preceding commands.
|
||||
typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
|
||||
|
||||
# Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
|
||||
# when accepting a command line. Supported values:
|
||||
#
|
||||
# - off: Don't change prompt when accepting a command line.
|
||||
# - always: Trim down prompt when accepting a command line.
|
||||
# - same-dir: Trim down prompt when accepting a command line unless this is the first command
|
||||
# typed after changing current working directory.
|
||||
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=always
|
||||
|
||||
# Instant prompt mode.
|
||||
#
|
||||
# - off: Disable instant prompt. Choose this if you've tried instant prompt and found
|
||||
# it incompatible with your zsh configuration files.
|
||||
# - quiet: Enable instant prompt and don't print warnings when detecting console output
|
||||
# during zsh initialization. Choose this if you've read and understood
|
||||
# https://github.com/romkatv/powerlevel10k#instant-prompt.
|
||||
# - verbose: Enable instant prompt and print a warning when detecting console output during
|
||||
# zsh initialization. Choose this if you've never tried instant prompt, haven't
|
||||
# seen the warning, or if you are unsure what this all means.
|
||||
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
|
||||
|
||||
# Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
|
||||
# For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
|
||||
# can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
|
||||
# really need it.
|
||||
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
|
||||
|
||||
# If p10k is already loaded, reload configuration.
|
||||
# This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
|
||||
(( ! $+functions[p10k] )) || p10k reload
|
||||
}
|
||||
|
||||
# Tell `p10k configure` which file it should overwrite.
|
||||
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
|
||||
|
||||
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
|
||||
'builtin' 'unset' 'p10k_config_opts'
|
||||
24
roles/zsh/templates/zshrc.j2
Normal file
24
roles/zsh/templates/zshrc.j2
Normal file
@ -0,0 +1,24 @@
|
||||
# {{ ansible_managed }}
|
||||
export ZSH_PLUGINS={{ zsh_plugins_path }}
|
||||
|
||||
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
|
||||
# Initialization code that may require console input (password prompts, [y/n]
|
||||
# confirmations, etc.) must go above this block; everything else may go below.
|
||||
if [[ -r "$HOME/.cache/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
|
||||
source "$HOME/.cache/p10k-instant-prompt-${(%):-%n}.zsh"
|
||||
fi
|
||||
|
||||
# To customize prompt, run `p10k configure` or edit the following file.
|
||||
source {{ zsh_p10k_theme_config }}
|
||||
|
||||
# Enable syntax highlighting for zsh
|
||||
source $ZSH_PLUGINS/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
|
||||
|
||||
# Enable autosuggestions
|
||||
source $ZSH_PLUGINS/zsh-autosuggestions/zsh-autosuggestions.zsh
|
||||
|
||||
# Enable powerlevel10k theme
|
||||
source $ZSH_PLUGINS/powerlevel10k/powerlevel10k.zsh-theme
|
||||
|
||||
export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8'
|
||||
export PROMPT="%n@%m:%~$ "
|
||||
Loading…
Reference in New Issue
Block a user