diff --git a/.config/zsh/rc.d/05-prompt.zsh b/.config/zsh/rc.d/05-prompt.zsh new file mode 100644 index 0000000..8fd835b --- /dev/null +++ b/.config/zsh/rc.d/05-prompt.zsh @@ -0,0 +1,32 @@ +# Prompt. Sourced by the stub BEFORE oh-my-zsh, and that placement is the whole +# point of the file. +# +# oh-my-zsh loads the theme while it is being sourced, and powerlevel10k reads +# its POWERLEVEL9K_* settings when it loads. Anything set afterwards is simply +# ignored - the first attempt put ZSH_THEME in the role file, which is sourced +# after oh-my-zsh, and the result was a shell with no powerlevel10k at all +# while still reporting the right value in ZSH_THEME. + +# Only claim the theme if it is actually installed, otherwise oh-my-zsh falls +# back to robbyrussell with a complaint. +if [[ -d "${ZSH_CUSTOM:-$ZSH/custom}/themes/powerlevel10k" ]]; then + ZSH_THEME="powerlevel10k/powerlevel10k" +fi + +# Colour by role. Not decoration: shron carries mail, web and cloud, and a +# glance at the prompt should be enough to know which machine the next command +# is about to hit. Same segments everywhere so nothing else has to be relearned. +case "$ROLE" in + server) + typeset -g POWERLEVEL9K_CONTEXT_BACKGROUND=red + # Show the host even locally. Noise on a desktop, the one thing worth + # always seeing here. + typeset -g POWERLEVEL9K_ALWAYS_SHOW_CONTEXT=true + ;; + mobile) + typeset -g POWERLEVEL9K_CONTEXT_BACKGROUND=green + ;; + *) + typeset -g POWERLEVEL9K_CONTEXT_BACKGROUND=blue + ;; +esac diff --git a/.config/zsh/rc.d/10-aliases.zsh b/.config/zsh/rc.d/10-aliases.zsh new file mode 100644 index 0000000..ba7922d --- /dev/null +++ b/.config/zsh/rc.d/10-aliases.zsh @@ -0,0 +1,25 @@ +# Aliases shared by every machine. +# +# Each one that depends on a command is guarded by whether that command exists. +# Without the guard, "alias ls='lsd'" turns ls into a broken command on any +# machine where lsd is not installed - the Mac, for one - and breaking ls is +# the sort of thing that makes a shared dotfile repository feel like a mistake. + +alias vi="nvim" +alias ducks="du -cksh * | sort -rn | head" + +if (( $+commands[lsd] )); then + alias ls='lsd' + alias l='lsd -l' + alias la='lsd -a' + alias lla='lsd -la' + alias lt='lsd --tree' +fi + +# Arch only. Present on beastix and shron, absent on the Mac. +(( $+commands[yay] )) && \ + alias update='yay -Syu --devel --noconfirm --diffmenu=false --cleanmenu=false --editmenu=false --answerupgrade None' + +# kitty's ssh wrapper, which carries the terminfo across. Falls back to nothing +# rather than shadowing ssh with something that is not there. +(( $+commands[kitten] )) && alias s="kitten ssh" diff --git a/.config/zsh/rc.d/20-functions.zsh b/.config/zsh/rc.d/20-functions.zsh new file mode 100644 index 0000000..235e742 --- /dev/null +++ b/.config/zsh/rc.d/20-functions.zsh @@ -0,0 +1,176 @@ +# Functions shared by every machine. +# +# The fzf-based ones are the bulk of it. They are defined unconditionally: a +# function that is never called costs nothing, and guarding each one would be +# more noise than the guard is worth. Only the docker group is gated, because +# those names are short enough to shadow something else on a machine without +# docker. + +# Which hosts are holding the most connections open. +detect-ddos() { + sudo netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n +} + +# cd into a directory picked with fzf. +fdir() { + local dir + dir=$(find "${1:-.}" -path '*/\.*' -prune \ + -o -type d -print 2> /dev/null | fzf +m) && + cd "$dir" +} + +# Pick a command out of history and put it on the command line rather than +# running it, so it can still be edited. +fhist() { + print -z $( ([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s --tac | sed -E 's/ *[0-9]*\*? *//' | sed -E 's/\\/\\\\/g') +} + +# Kill a process picked with fzf. Non-root sees only its own processes, which +# is also all it could kill. +fkill() { + local pid + if [ "$UID" != "0" ]; then + pid=$(ps -f -u "$UID" | sed 1d | fzf -m | awk '{print $2}') + else + pid=$(ps -ef | sed 1d | fzf -m | awk '{print $2}') + fi + + if [ "x$pid" != "x" ]; then + echo "$pid" | xargs kill -"${1:-9}" + fi +} + +# Kill tmux sessions picked with fzf. Needs zsh's =~ operator. +tmuxkillf () { + local sessions + sessions="$(tmux ls|fzf --exit-0 --multi)" || return $? + local i + for i in "${(f@)sessions}" + do + [[ $i =~ '([^:]*):.*' ]] && { + echo "Killing $match[1]" + tmux kill-session -t "$match[1]" + } + done +} + +# tm pick a session with fzf +# tm attach to it, creating it if needed +# Works from inside tmux too, where attaching would fail. +tm() { + [[ -n "$TMUX" ]] && change="switch-client" || change="attach-session" + if [ "$1" ]; then + tmux $change -t "$1" 2>/dev/null || (tmux new-session -d -s "$1" && tmux $change -t "$1"); return + fi + session=$(tmux list-sessions -F "#{session_name}" 2>/dev/null | fzf --exit-0) && tmux $change -t "$session" || echo "No sessions found." +} + +# Leave yazi in the directory it was left in, rather than where it started. +(( $+commands[yazi] )) && y() { + local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd + yazi "$@" --cwd-file="$tmp" + IFS= read -r -d '' cwd < "$tmp" + [ -n "$cwd" ] && [ "$cwd" != "$PWD" ] && builtin cd -- "$cwd" + rm -f -- "$tmp" +} + +# Docker helpers, all fzf pickers. Present on all three machines today, but +# "ds" and "da" are short enough to be worth gating rather than defining blind. +if (( $+commands[docker] )); then + # start a stopped container and attach to it + da() { + local cid + cid=$(docker ps -a | sed 1d | fzf -1 -q "$1" | awk '{print $1}') + [ -n "$cid" ] && docker start "$cid" && docker attach "$cid" + } + + # stop a running container + ds() { + local cid + cid=$(docker ps | sed 1d | fzf -q "$1" | awk '{print $1}') + [ -n "$cid" ] && docker stop "$cid" + } + + # remove containers, multi-select. The single-select version that used to sit + # above this one was dead code: it had the same name and was overwritten on + # every shell start. + drm() { + docker ps -a | sed 1d | fzf -q "$1" --no-sort -m --tac | awk '{ print $1 }' | xargs -r docker rm + } + + # remove images, multi-select + drmi() { + docker images | sed 1d | fzf -q "$1" --no-sort -m --tac | awk '{ print $3 }' | xargs -r docker rmi + } +fi + +# Arch package hygiene: remove orphans, but write down what was removed first +# so the decision can be undone. +if (( $+commands[yay] )); then + yay_remove_orphans() { + local backup_dir="$HOME/.config/yay" + local date_str backup_file orphans + + date_str="$(date +%Y-%m-%d_%H-%M-%S)" + backup_file="$backup_dir/remove_orph_${date_str}.bak" + mkdir -p "$backup_dir" + + orphans=("${(@f)$(yay -Qtdq)}") + + if [[ ${#orphans[@]} -eq 0 || -z "${orphans[1]}" ]]; then + echo "✔ Keine verwaisten Pakete gefunden." + return 0 + fi + + print -l -- "${orphans[@]}" > "$backup_file" + + echo "📦 Backup der verwaisten Pakete erstellt:" + echo " $backup_file" + echo + echo "🧹 Entferne folgende Pakete:" + print -l -- "${orphans[@]}" + echo + + yay -Rns "${orphans[@]}" + } + + yay_restore_orphans() { + local backup_dir="$HOME/.config/yay" + local backup_file packages selected + + backup_file=$(ls -1 "$backup_dir"/remove_orph_*.bak 2>/dev/null | fzf \ + --prompt="Backup auswählen: " \ + --height=40% \ + --reverse) + + [[ -z "$backup_file" ]] && { + echo "✖ Kein Backup ausgewählt." + return 1 + } + + packages=("${(@f)$(cat "$backup_file")}") + + if [[ ${#packages[@]} -eq 0 ]]; then + echo "✖ Backup ist leer." + return 1 + fi + + selected=("${(@f)$(printf "%s\n" "${packages[@]}" | fzf \ + --multi \ + --prompt="Pakete auswählen (TAB): " \ + --height=60% \ + --reverse)}") + + [[ ${#selected[@]} -eq 0 ]] && { + echo "✖ Keine Pakete ausgewählt." + return 1 + } + + echo + echo "📦 Installiere folgende Pakete:" + printf " - %s\n" "${selected[@]}" + echo + + yay -S "${selected[@]}" + } +fi diff --git a/.config/zsh/rc.d/30-path.zsh b/.config/zsh/rc.d/30-path.zsh new file mode 100644 index 0000000..2019c36 --- /dev/null +++ b/.config/zsh/rc.d/30-path.zsh @@ -0,0 +1,45 @@ +# PATH and environment shared by every machine. +# +# Entries are added only if the directory exists. The list this replaces +# carried ~/bin, ~/.scripts and /usr/X11R6/bin, none of which are present on +# this machine - a PATH full of missing directories costs a stat on every +# lookup and hides the fact that something was never installed. +# +# It also exported PYTHONPATH=/usr/lib/python3.9/site-packages. Python here is +# 3.14 and that directory does not exist; pointing PYTHONPATH at a missing +# 3.9 tree is at best inert and at worst confusing, so it is gone. + +# $HOME rather than a hardcoded /home/templis: the Mac puts it under /Users. +_path_add() { + [[ -d "$1" ]] || return 0 + case ":$PATH:" in + *":$1:"*) ;; + *) PATH="$1:$PATH" ;; + esac +} + +_path_add "$HOME/.local/bin" # pipx +_path_add "$HOME/perl5/bin" # local::lib +_path_add "$HOME/bin" +_path_add "$HOME/.scripts" +export PATH +unfunction _path_add + +# local::lib, only where it is actually set up. +if [[ -d "$HOME/perl5/lib/perl5" ]]; then + export PERL5LIB="$HOME/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}" + export PERL_LOCAL_LIB_ROOT="$HOME/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}" + export PERL_MB_OPT="--install_base \"$HOME/perl5\"" + export PERL_MM_OPT="INSTALL_BASE=$HOME/perl5" +fi + +export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border' + +# ghostty announces itself but its terminfo is not everywhere, which leaves +# remote hosts unable to draw anything. +if [[ "$TERM_PROGRAM" == "ghostty" ]]; then + export TERM=xterm-256color +fi + +# powerlevel10k's own configuration, where it exists. +[[ ! -f "$HOME/.p10k.zsh" ]] || source "$HOME/.p10k.zsh" diff --git a/.config/zsh/rc.d/50-desktop.zsh b/.config/zsh/rc.d/50-desktop.zsh new file mode 100644 index 0000000..4753069 --- /dev/null +++ b/.config/zsh/rc.d/50-desktop.zsh @@ -0,0 +1,40 @@ +# Role: desktop (beastix - Arch, KDE, X11) +# +# Everything here is either about running a graphical session or about things +# only this machine has. None of it belongs on a headless server or a Mac. + +# Qt tries wayland first and falls back to xcb, which covers both session types +# without having to know which one is running. +export QT_QPA_PLATFORM="wayland;xcb" +export MOZ_ENABLE_WAYLAND=1 + +# The previous configuration exported XDG_SESSION_TYPE=x11 and then, six lines +# later, tested it for "wayland" to decide whether to enable MOZ_ENABLE_WAYLAND. +# That branch could never be taken. XDG_SESSION_TYPE is set by the session +# itself and overriding it lies to everything that reads it, so it is left +# alone; MOZ_ENABLE_WAYLAND is simply set once, above. + +export MUTTER_DEBUG_KMS_THREAD_TYPE=user + +# Steam prefixes for Path of Exile, used by the trade tooling. +export poe2="$HOME/.local/share/Steam/steamapps/compatdata/2694490/pfx/drive_c/users/steamuser/My Documents/My Games/Path of Exile 2/" +export poe="$HOME/.local/share/Steam/steamapps/compatdata/238960/pfx/drive_c/users/steamuser/My Documents/My Games/Path of Exile/" + +# Signal needs a secrets service, and KDE does not start gnome-keyring itself. +# +# This used to exist twice: once here and once as a local custom oh-my-zsh +# plugin named signal-keyring, so it ran on every shell start in both places. +# The plugin is not in this repository and does not exist on the other two +# machines, where naming it in the plugin list produced a warning at every +# login. Keeping the inline version and dropping the plugin fixes both. +if ! pgrep -f gnome-keyring-daemon >/dev/null; then + eval $(gnome-keyring-daemon --start --components=secrets) + export $(gnome-keyring-daemon --start --components=secrets | grep ^SSH_AUTH_SOCK) +fi + +# Toolchains that only this machine carries. +for _d in "$HOME/.cargo/bin" "$HOME/.lmstudio/bin"; do + [[ -d "$_d" ]] && case ":$PATH:" in *":$_d:"*) ;; *) PATH="$_d:$PATH" ;; esac +done +unset _d +export PATH diff --git a/.config/zsh/rc.d/50-mobile.zsh b/.config/zsh/rc.d/50-mobile.zsh new file mode 100644 index 0000000..8eb842a --- /dev/null +++ b/.config/zsh/rc.d/50-mobile.zsh @@ -0,0 +1,16 @@ +# Role: mobile (the Mac - macOS on Apple Silicon) +# +# Homebrew's shellenv belongs in .zprofile, not here: a non-login shell would +# otherwise have no /opt/homebrew/bin in PATH. That is not theory - during the +# design phase "ssh mac brew --version" came back empty for exactly that +# reason, and was briefly mistaken for Homebrew not being installed. + +# Apple Silicon puts Homebrew under /opt/homebrew; Intel Macs use /usr/local. +# Checked rather than assumed, so the same file works on either. +for _b in /opt/homebrew /usr/local; do + if [[ -x "$_b/bin/brew" ]]; then + eval "$("$_b/bin/brew" shellenv)" + break + fi +done +unset _b diff --git a/.config/zsh/rc.d/50-server.zsh b/.config/zsh/rc.d/50-server.zsh new file mode 100644 index 0000000..63ae574 --- /dev/null +++ b/.config/zsh/rc.d/50-server.zsh @@ -0,0 +1,6 @@ +# Role: server (shron - Arch, headless) +# +# Deliberately thin. A server shell wants to be recognisable and otherwise get +# out of the way; anything that assumes a graphical session belongs in +# 50-desktop.zsh. + diff --git a/.zshrc b/.zshrc index be7df63..b7706f4 100644 --- a/.zshrc +++ b/.zshrc @@ -1,134 +1,63 @@ -# Path to your oh-my-zsh configuration. - ZSH=$HOME/.oh-my-zsh +# Stub only. Everything of substance lives in ~/.config/zsh/rc.d/. +# +# Load order, and why it is this one: +# +# 1. p10k instant prompt must be the first thing that runs +# 2. role needed before plugins, see below +# 3. plugins must be set before oh-my-zsh reads them +# 4. oh-my-zsh +# 5. rc.d/[1-4]*.zsh shared by every machine +# 6. rc.d/50-.zsh exactly one of desktop/server/mobile +# 7. rc.d/90-local.zsh machine-local, never in the repository +# +# Step 3 is the reason the role is resolved so early: oh-my-zsh consumes the +# plugins array while sourcing, so nothing loaded afterwards can influence it. -# Set name of the theme to load. -# Look in ~/.oh-my-zsh/themes/ -# Optionally, if you set this to "random", it'll load a random theme each -# time that oh-my-zsh is loaded. - ZSH_THEME="ys" +# Must stay at the top: this block may print, and anything above it that also +# prints breaks the instant prompt. Absent on machines without powerlevel10k, +# where the test simply fails and nothing happens. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi -# Example aliases -# alias zshconfig="mate ~/.zshrc" -# alias ohmyzsh="mate ~/.oh-my-zsh" -# alias vi="vim" +export ZSH="$HOME/.oh-my-zsh" -# Set to this to use case-sensitive completion -# CASE_SENSITIVE="true" +# Defaults to desktop so a machine without the file still gets a working shell. +# Servers must have it in place before the first login, or shron comes up on +# the desktop profile - blue prompt on a production mail server. +ROLE=$(cat "$HOME/.config/dotconfs/role" 2>/dev/null || echo desktop) +export ROLE -# Comment this out to disable bi-weekly auto-update checks -# DISABLE_AUTO_UPDATE="true" +# One list for every machine, filtered by what is actually installed. A plugin +# named but not present makes oh-my-zsh complain on every single shell start, +# and the alternative - a separate list per role - means editing three files to +# add one plugin. +_want_plugins=(git archlinux colored-man-pages colorize zsh-interactive-cd fzf) +plugins=() +for _p in $_want_plugins; do + if [[ -d "$ZSH/plugins/$_p" || -d "${ZSH_CUSTOM:-$ZSH/custom}/plugins/$_p" ]]; then + plugins+=("$_p") + fi +done +unset _p _want_plugins -# Uncomment to change how many often would you like to wait before auto-updates occur? (in days) -# export UPDATE_ZSH_DAYS=13 +zstyle ':omz:update' mode auto -# Uncomment following line if you want to disable colors in ls -# DISABLE_LS_COLORS="true" +# Also before oh-my-zsh: it loads the theme while sourcing, and powerlevel10k +# reads its settings when it loads. Putting either in the role file - which is +# sourced afterwards - leaves the value set and the theme unloaded. +[[ -r "$HOME/.config/zsh/rc.d/05-prompt.zsh" ]] && source "$HOME/.config/zsh/rc.d/05-prompt.zsh" -# Uncomment following line if you want to disable autosetting terminal title. -# DISABLE_AUTO_TITLE="true" +source "$ZSH/oh-my-zsh.sh" -# Uncomment following line if you want red dots to be displayed while waiting for completion -# COMPLETION_WAITING_DOTS="true" -# Example format: plugins=(rails git textmate ruby lighthouse) -#ALIASES -# vi starts vim - alias vi="nvim" -# update for archlinux -# in debian like oses you should change it to sudo apt update && sudo apt upgrade -# for nixos sudo nix-channel --update && sudo nixos rebuild-switch - alias update="yaourt -Syua --noconfirm" -#check directory sizes - alias ducks="du -cksh * | sort -rn | head" -# for backup in git - alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME' +# (N) makes an empty directory a no-op instead of an error. +for _f in "$HOME"/.config/zsh/rc.d/[1-4]*.zsh(N); do + source "$_f" +done +unset _f -# bind page up and page down? can't remember but think this must be. :) - bindkey "\033[1~" beginning-of-line - bindkey "\033[4~" end-of-line +[[ -r "$HOME/.config/zsh/rc.d/50-$ROLE.zsh" ]] && source "$HOME/.config/zsh/rc.d/50-$ROLE.zsh" -#Add Plugins to oh my zsh - plugins=(git archlinux history-substring-search) - -#include the oh my zsh config - source $ZSH/oh-my-zsh.sh - -# Customize to your needs... - export PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/X11R6/bin:$HOME/.scripts - export PYTHONPATH=/usr/lib/python3.9/site-packages - -# if colors of ls don't work as expected the following line delete all colors from ls -# export LS_COLORS="rs=0:di=01;96:ln=04;01;35:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30; 43:ca=30;41:tw=30;42:$" - -# Add dircolors plugin from: -# add it with: yaourt -S zsh-dircolors-solarized-git -# alternatively you can add it with: git clone --recursive git://github.com/joel-porquet/zsh-dircolors-solarized $ZSH_CUSTOM/plugins/zsh-dircolors-solarized -# and than add it to plugins line above (line 40) -# enable it with: setupsolarized -#source /usr/share/zsh/plugins/zsh-dircolors-solarized/zsh-dircolors-solarized.zsh - -# function to detect ddos attacks - function detect-ddos { - sudo netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -n - } - -# function to simple block suspicious IPs -# simply type ipt-block ipadress - function ipt-block { - sudo iptables -A INPUT -s $1 -j DROP - echo "permblocked $1" - } - -function ipt-block_all_incoming { - # Set default chain policies - sudo iptables -P INPUT DROP - sudo iptables -P FORWARD DROP - sudo iptables -P OUTPUT ACCEPT - # Accept on localhost - sudo iptables -A INPUT -i lo -j ACCEPT - sudo iptables -A OUTPUT -o lo -j ACCEPT - # Allow established sessions to receive traffic - sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -} - -#function to load actual IP Blocklist from openbl and ban them out - function update-blacklist { - CHAINLIST=$(sudo /sbin/iptables -nL | grep 'Chain block-traffic-from-openbl' | cut -d\ -f 2) - - if [ -z $CHAINLIST ]; then - sudo /sbin/iptables -N block-traffic-from-openbl - sudo /sbin/iptables -A INPUT -j block-traffic-from-openbl - fi - - BLACKLIST=$(/usr/bin/curl -fs http://www.openbl.org/lists/base_7days.txt.gz | gunzip | egrep "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1, 3}\.[0-9]{1,3}") - - if [ $? -ne 0 ]; then - echo "Blacklist download failed." - exit - fi - sudo /sbin/iptables -F block-traffic-from-openbl - - IPCOUNT=$(echo $BLACKLIST | tr ' ' '\n' | wc -l) - echo "Adding $IPCOUNT IPs to blacklist. - $(date)" - - echo $BLACKLIST | tr ' ' '\n' | while read -r line ; do - case "$line" in \#*) continue ;; esac - sudo /sbin/iptables -A block-traffic-from-openbl -p tcp -s $line -j REJECT --reject-with tcp-reset - done - } -function inventarisierung { - NETZ=$(ip route | awk '/scope link/ {print $1}') - read "KUNDE?Bitte Kundenurl eingeben: " - sudo nmap -v -O -oG ~/cloud.tueit.de/Kunden/$KUNDE/$KUNDE.txt $NETZ - grep "OS:" ~/cloud.tueit.de/Kunden/$KUNDE/$KUNDE.txt | sed 's/Host: //' | sed 's/Ports.*OS://' | sed 's/Seq.*$//' | sed 's/(//' | sed 's/)//' - grep "OS:" ~/cloud.tueit.de/Kunden/$KUNDE/$KUNDE.txt | sed 's/Host: //' | sed 's/Ports.*OS://' | sed 's/Seq.*$//' | sed 's/(//' | sed 's/)//' | awk '{print "\"" $1 "\";\""$2"\";\"" $3 " " $4 " " $5 " " $6 " " $7 " " $8 " " $9 " " $10 " " $11 " " $12 " " $13 " " $14 "\""}' >~/cloud.tueit.de/Kunden/$KUNDE/$KUNDE.csv -} - -# add some PERL Path variables - PATH="$HOME/perl5/bin${PATH+:}${PATH}"; export PATH; - PERL5LIB="$HOME/perl5/lib/perl5${PERL5LIB+:}${PERL5LIB}"; export PERL5LIB; - PERL_LOCAL_LIB_ROOT="$HOME/perl5${PERL_LOCAL_LIB_ROOT+:}${PERL_LOCAL_LIB_ROOT}"; export PERL_LOCAL_LIB_ROOT; - PERL_MB_OPT="--install_base \"$HOME/perl5\""; export PERL_MB_OPT; - PERL_MM_OPT="INSTALL_BASE=$HOME/perl5"; export PERL_MM_OPT; - export TERM="xterm-256color" - -source /home/templis/.config/broot/launcher/bash/br +# Last on purpose: this one can override anything above without the repository +# needing to know about it. +[[ -r "$HOME/.config/zsh/rc.d/90-local.zsh" ]] && source "$HOME/.config/zsh/rc.d/90-local.zsh"