#!/usr/bin/env bash # # synology-prompt.sh # # Universal Powerline-style SysAdmin prompt for Synology DSM. # # Features: # - No external prompt framework required # - Nerd Font / Powerline icons # - User and hostname # - Current working directory # - Git branch and repository state # - Current filesystem usage # - Indonesian date # - Clock # - Previous command exit status # - Automatic root warning palette # # Requirements: # - Bash # - Standard DSM utilities: date, df, awk # # Optional: # - Git # # Nerd Font only needs to be installed on the SSH client/terminal. # # This file is intended to be installed as: # # /etc/synology-prompt.sh # # and sourced from the user's interactive Bash environment. # # ============================================================================== # GUARD # ============================================================================== # Interactive shell only. case "$-" in *i*) ;; *) return 0 2>/dev/null || exit 0 ;; esac # Bash is required. if [ -z "${BASH_VERSION:-}" ]; then return 0 2>/dev/null || exit 0 fi # Prevent loading more than once in the same shell. if [ "${SYNO_PROMPT_LOADED:-0}" = "1" ]; then return 0 fi SYNO_PROMPT_LOADED=1 # ============================================================================== # CONFIGURATION # ============================================================================== # Maximum displayed PWD length. SYNO_MAX_PWD_LENGTH=35 # Show filesystem usage segment. SYNO_SHOW_DISK=1 # Show Git segment when inside a Git repository. SYNO_SHOW_GIT=1 # Show previous command exit code when non-zero. SYNO_SHOW_EXIT_CODE=1 # ============================================================================== # NERD FONT ICONS # # Fonts are rendered by the SSH client. # Nothing needs to be installed on DSM. # ============================================================================== SYNO_ICON_USER=$'\uf007' #  SYNO_ICON_HOST=$'\uf233' #  SYNO_ICON_FOLDER=$'\uf07b' #  SYNO_ICON_GIT=$'\ue725' #  SYNO_ICON_DISK=$'\uf0a0' #  SYNO_ICON_CALENDAR=$'\uf073' #  SYNO_ICON_CLOCK=$'\uf017' #  SYNO_SEPARATOR=$'\ue0b0' #  # ============================================================================== # INDONESIAN DATE # # Deliberately does not depend on id_ID locale being installed on DSM. # ============================================================================== _syno_day() { case "$(date +%u)" in 1) printf 'Senin' ;; 2) printf 'Selasa' ;; 3) printf 'Rabu' ;; 4) printf 'Kamis' ;; 5) printf 'Jumat' ;; 6) printf 'Sabtu' ;; 7) printf 'Minggu' ;; esac } _syno_month() { case "$(date +%m)" in 01) printf 'Januari' ;; 02) printf 'Februari' ;; 03) printf 'Maret' ;; 04) printf 'April' ;; 05) printf 'Mei' ;; 06) printf 'Juni' ;; 07) printf 'Juli' ;; 08) printf 'Agustus' ;; 09) printf 'September' ;; 10) printf 'Oktober' ;; 11) printf 'November' ;; 12) printf 'Desember' ;; esac } _syno_date() { printf '%s, %s %s %s' \ "$(_syno_day)" \ "$(date +%d)" \ "$(_syno_month)" \ "$(date +%Y)" } # ============================================================================== # WORKING DIRECTORY # ============================================================================== _syno_pwd() { local p="$PWD" # Replace the current user's home directory with ~. case "$p" in "$HOME") p="~" ;; "$HOME"/*) p="~${p#$HOME}" ;; esac # Shorten very long paths. if [ "${#p}" -gt "$SYNO_MAX_PWD_LENGTH" ]; then p="…${p: -$((SYNO_MAX_PWD_LENGTH - 1))}" fi printf '%s' "$p" } # ============================================================================== # GIT STATUS # # Symbols: # # clean no symbol # dirty ◔ # ahead ▲ # behind ▼ # diverged ◆ # dirty + ahead ◔ △ # dirty + behind ◔ ▽ # dirty + diverged ◔ ◇ # stash N🗎 # # The segment automatically disappears outside a Git repository. # ============================================================================== _syno_git() { [ "$SYNO_SHOW_GIT" = "1" ] || return command -v git >/dev/null 2>&1 || return git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return local branch local status local first_line local dirty=0 local ahead=0 local behind=0 local stash=0 local symbol="" branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null)" # Detached HEAD fallback. if [ -z "$branch" ]; then branch="$(git rev-parse --short HEAD 2>/dev/null)" fi [ -n "$branch" ] || return status="$(git status --porcelain -b 2>/dev/null)" first_line="$(printf '%s\n' "$status" | head -n 1)" # Lines after the ## branch line indicate working-tree/index changes. if printf '%s\n' "$status" | tail -n +2 | grep -q .; then dirty=1 fi case "$first_line" in *ahead*) ahead=1 ;; esac case "$first_line" in *behind*) behind=1 ;; esac if [ "$dirty" -eq 1 ] && [ "$ahead" -eq 1 ] && [ "$behind" -eq 1 ]; then symbol=" ◔ ◇" elif [ "$dirty" -eq 1 ] && [ "$ahead" -eq 1 ]; then symbol=" ◔ △" elif [ "$dirty" -eq 1 ] && [ "$behind" -eq 1 ]; then symbol=" ◔ ▽" elif [ "$dirty" -eq 1 ]; then symbol=" ◔" elif [ "$ahead" -eq 1 ] && [ "$behind" -eq 1 ]; then symbol=" ◆" elif [ "$ahead" -eq 1 ]; then symbol=" ▲" elif [ "$behind" -eq 1 ]; then symbol=" ▼" fi stash="$(git stash list 2>/dev/null | wc -l | tr -d ' ')" printf '%s %s%s' \ "$SYNO_ICON_GIT" \ "$branch" \ "$symbol" if [ "${stash:-0}" -gt 0 ] 2>/dev/null; then printf ' %s🗎' "$stash" fi } # ============================================================================== # FILESYSTEM USAGE # # Displays usage of the filesystem containing the current working directory. # # Example: # # /volume1/docker/... -> volume1 usage # /tmp/... -> filesystem containing /tmp # /root -> root filesystem usage # ============================================================================== _syno_disk_percent() { [ "$SYNO_SHOW_DISK" = "1" ] || return local usage usage="$( df -P "$PWD" 2>/dev/null | awk 'NR==2 {gsub(/%/, "", $5); print $5}' )" [ -n "$usage" ] || return printf '%s' "$usage" } # ============================================================================== # POWERLINE SEGMENT RENDERER # # Escape sequences are stored directly in PS1. # # \[ and \] tell Bash that ANSI escape sequences consume zero terminal columns. # This keeps cursor positioning and line editing correct. # ============================================================================== _syno_add_segment() { local text="$1" local fg="$2" local bg="$3" local next_bg="$4" PS1+="\[\e[1;38;5;${fg};48;5;${bg}m\]" PS1+=" ${text} " # Arrow: # foreground = current background # background = next segment background PS1+="\[\e[0;38;5;${bg};48;5;${next_bg}m\]" PS1+="${SYNO_SEPARATOR}" } _syno_add_last_segment() { local text="$1" local fg="$2" local bg="$3" PS1+="\[\e[1;38;5;${fg};48;5;${bg}m\]" PS1+=" ${text} " # Final arrow into terminal default background. PS1+="\[\e[0;38;5;${bg};49m\]" PS1+="${SYNO_SEPARATOR}" PS1+="\[\e[0m\]" } # ============================================================================== # PROMPT BUILDER # ============================================================================== _syno_prompt() { # IMPORTANT: # This must be the first command so $? still contains the exit status # of the user's previous command. local exit_code=$? local user_text local host_text local pwd_text local git_text="" local disk_text="" local disk_percent="" local date_text local clock_text user_text="${SYNO_ICON_USER} ${USER:-$(id -un)}" host_text="${SYNO_ICON_HOST} ${HOSTNAME:-$(hostname)}" pwd_text="${SYNO_ICON_FOLDER} $(_syno_pwd)" git_text="$(_syno_git)" disk_percent="$(_syno_disk_percent)" if [ -n "$disk_percent" ]; then disk_text="${SYNO_ICON_DISK} ${disk_percent}%" fi date_text="${SYNO_ICON_CALENDAR} $(_syno_date)" clock_text="${SYNO_ICON_CLOCK} $(date +%H:%M:%S)" # ========================================================================== # NORMAL USER PALETTE # ========================================================================== local user_fg=16 local user_bg=46 local host_fg=231 local host_bg=33 local pwd_fg=238 local pwd_bg=255 local git_fg=250 local git_bg=238 local disk_fg=231 local disk_bg=31 local date_fg=231 local date_bg=135 local clock_fg=231 local clock_bg=16 local input_fg=45 # ========================================================================== # ROOT WARNING PALETTE # # Root is deliberately visually obvious. # ========================================================================== if [ "$(id -u)" -eq 0 ]; then user_fg=231 user_bg=196 host_fg=231 host_bg=203 pwd_fg=16 pwd_bg=226 git_fg=231 git_bg=196 disk_fg=16 disk_bg=214 date_fg=231 date_bg=203 clock_fg=231 clock_bg=196 input_fg=203 fi # ========================================================================== # BUILD FIRST LINE # ========================================================================== PS1="\n╭" _syno_add_segment \ "$user_text" \ "$user_fg" \ "$user_bg" \ "$host_bg" _syno_add_segment \ "$host_text" \ "$host_fg" \ "$host_bg" \ "$pwd_bg" # -------------------------------------------------------------------------- # PWD -> optional Git -> optional Disk -> Date -> Clock # # We need to determine the background of the segment following PWD. # -------------------------------------------------------------------------- if [ -n "$git_text" ]; then _syno_add_segment \ "$pwd_text" \ "$pwd_fg" \ "$pwd_bg" \ "$git_bg" if [ -n "$disk_text" ]; then _syno_add_segment \ "$git_text" \ "$git_fg" \ "$git_bg" \ "$disk_bg" else _syno_add_segment \ "$git_text" \ "$git_fg" \ "$git_bg" \ "$date_bg" fi else if [ -n "$disk_text" ]; then _syno_add_segment \ "$pwd_text" \ "$pwd_fg" \ "$pwd_bg" \ "$disk_bg" else _syno_add_segment \ "$pwd_text" \ "$pwd_fg" \ "$pwd_bg" \ "$date_bg" fi fi if [ -n "$disk_text" ]; then _syno_add_segment \ "$disk_text" \ "$disk_fg" \ "$disk_bg" \ "$date_bg" fi _syno_add_segment \ "$date_text" \ "$date_fg" \ "$date_bg" \ "$clock_bg" _syno_add_last_segment \ "$clock_text" \ "$clock_fg" \ "$clock_bg" # ========================================================================== # BUILD SECOND LINE # ========================================================================== local symbol='$' if [ "$(id -u)" -eq 0 ]; then symbol='#' fi PS1+="\n╰> " # Previous command failed. if [ "$SYNO_SHOW_EXIT_CODE" = "1" ] && [ "$exit_code" -ne 0 ]; then PS1+="\[\e[1;38;5;196m\]" PS1+="✘ ${exit_code} " PS1+="\[\e[0m\]" fi PS1+="\[\e[1;38;5;${input_fg}m\]" PS1+="${symbol} " PS1+="\[\e[0m\]" } # ============================================================================== # ACTIVATE PROMPT # ============================================================================== PROMPT_COMMAND=_syno_prompt # ============================================================================== # OPTIONAL CONVENIENCE ALIASES # # Only simple commands known to work on DSM are used. # ============================================================================== alias ll='ls -lah' alias la='ls -A' alias l='ls -CF' alias dfh='df -h' alias duh='du -h' if command -v git >/dev/null 2>&1; then alias gs='git status' alias gl='git log --oneline --decorate --graph -15' fi # ============================================================================== # CLEAN TERMINAL FORMATTING ON EXIT # ============================================================================== trap 'printf "\033[0m"' EXIT