Zuletzt aktiv 1790666318

synology-prompt.sh Orginalformat
1#!/usr/bin/env bash
2#
3# synology-prompt.sh
4#
5# Universal Powerline-style SysAdmin prompt for Synology DSM.
6#
7# Features:
8# - No external prompt framework required
9# - Nerd Font / Powerline icons
10# - User and hostname
11# - Current working directory
12# - Git branch and repository state
13# - Current filesystem usage
14# - Indonesian date
15# - Clock
16# - Previous command exit status
17# - Automatic root warning palette
18#
19# Requirements:
20# - Bash
21# - Standard DSM utilities: date, df, awk
22#
23# Optional:
24# - Git
25#
26# Nerd Font only needs to be installed on the SSH client/terminal.
27#
28# This file is intended to be installed as:
29#
30# /etc/synology-prompt.sh
31#
32# and sourced from the user's interactive Bash environment.
33#
34
35
36# ==============================================================================
37# GUARD
38# ==============================================================================
39
40# Interactive shell only.
41case "$-" in
42 *i*) ;;
43 *) return 0 2>/dev/null || exit 0 ;;
44esac
45
46# Bash is required.
47if [ -z "${BASH_VERSION:-}" ]; then
48 return 0 2>/dev/null || exit 0
49fi
50
51# Prevent loading more than once in the same shell.
52if [ "${SYNO_PROMPT_LOADED:-0}" = "1" ]; then
53 return 0
54fi
55
56SYNO_PROMPT_LOADED=1
57
58
59# ==============================================================================
60# CONFIGURATION
61# ==============================================================================
62
63# Maximum displayed PWD length.
64SYNO_MAX_PWD_LENGTH=35
65
66# Show filesystem usage segment.
67SYNO_SHOW_DISK=1
68
69# Show Git segment when inside a Git repository.
70SYNO_SHOW_GIT=1
71
72# Show previous command exit code when non-zero.
73SYNO_SHOW_EXIT_CODE=1
74
75
76# ==============================================================================
77# NERD FONT ICONS
78#
79# Fonts are rendered by the SSH client.
80# Nothing needs to be installed on DSM.
81# ==============================================================================
82
83SYNO_ICON_USER=$'\uf007' # 
84SYNO_ICON_HOST=$'\uf233' # 
85SYNO_ICON_FOLDER=$'\uf07b' # 
86SYNO_ICON_GIT=$'\ue725' # 
87SYNO_ICON_DISK=$'\uf0a0' # 
88SYNO_ICON_CALENDAR=$'\uf073' # 
89SYNO_ICON_CLOCK=$'\uf017' # 
90
91SYNO_SEPARATOR=$'\ue0b0' # 
92
93
94# ==============================================================================
95# INDONESIAN DATE
96#
97# Deliberately does not depend on id_ID locale being installed on DSM.
98# ==============================================================================
99
100_syno_day()
101{
102 case "$(date +%u)" in
103 1) printf 'Senin' ;;
104 2) printf 'Selasa' ;;
105 3) printf 'Rabu' ;;
106 4) printf 'Kamis' ;;
107 5) printf 'Jumat' ;;
108 6) printf 'Sabtu' ;;
109 7) printf 'Minggu' ;;
110 esac
111}
112
113
114_syno_month()
115{
116 case "$(date +%m)" in
117 01) printf 'Januari' ;;
118 02) printf 'Februari' ;;
119 03) printf 'Maret' ;;
120 04) printf 'April' ;;
121 05) printf 'Mei' ;;
122 06) printf 'Juni' ;;
123 07) printf 'Juli' ;;
124 08) printf 'Agustus' ;;
125 09) printf 'September' ;;
126 10) printf 'Oktober' ;;
127 11) printf 'November' ;;
128 12) printf 'Desember' ;;
129 esac
130}
131
132
133_syno_date()
134{
135 printf '%s, %s %s %s' \
136 "$(_syno_day)" \
137 "$(date +%d)" \
138 "$(_syno_month)" \
139 "$(date +%Y)"
140}
141
142
143# ==============================================================================
144# WORKING DIRECTORY
145# ==============================================================================
146
147_syno_pwd()
148{
149 local p="$PWD"
150
151 # Replace the current user's home directory with ~.
152 case "$p" in
153 "$HOME")
154 p="~"
155 ;;
156 "$HOME"/*)
157 p="~${p#$HOME}"
158 ;;
159 esac
160
161 # Shorten very long paths.
162 if [ "${#p}" -gt "$SYNO_MAX_PWD_LENGTH" ]; then
163 p="…${p: -$((SYNO_MAX_PWD_LENGTH - 1))}"
164 fi
165
166 printf '%s' "$p"
167}
168
169
170# ==============================================================================
171# GIT STATUS
172#
173# Symbols:
174#
175# clean no symbol
176# dirty ◔
177# ahead ▲
178# behind ▼
179# diverged ◆
180# dirty + ahead ◔ △
181# dirty + behind ◔ ▽
182# dirty + diverged ◔ ◇
183# stash N🗎
184#
185# The segment automatically disappears outside a Git repository.
186# ==============================================================================
187
188_syno_git()
189{
190 [ "$SYNO_SHOW_GIT" = "1" ] || return
191
192 command -v git >/dev/null 2>&1 || return
193
194 git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return
195
196 local branch
197 local status
198 local first_line
199
200 local dirty=0
201 local ahead=0
202 local behind=0
203 local stash=0
204
205 local symbol=""
206
207 branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null)"
208
209 # Detached HEAD fallback.
210 if [ -z "$branch" ]; then
211 branch="$(git rev-parse --short HEAD 2>/dev/null)"
212 fi
213
214 [ -n "$branch" ] || return
215
216 status="$(git status --porcelain -b 2>/dev/null)"
217 first_line="$(printf '%s\n' "$status" | head -n 1)"
218
219 # Lines after the ## branch line indicate working-tree/index changes.
220 if printf '%s\n' "$status" | tail -n +2 | grep -q .; then
221 dirty=1
222 fi
223
224 case "$first_line" in
225 *ahead*)
226 ahead=1
227 ;;
228 esac
229
230 case "$first_line" in
231 *behind*)
232 behind=1
233 ;;
234 esac
235
236 if [ "$dirty" -eq 1 ] &&
237 [ "$ahead" -eq 1 ] &&
238 [ "$behind" -eq 1 ]; then
239
240 symbol=" ◔ ◇"
241
242 elif [ "$dirty" -eq 1 ] &&
243 [ "$ahead" -eq 1 ]; then
244
245 symbol=" ◔ △"
246
247 elif [ "$dirty" -eq 1 ] &&
248 [ "$behind" -eq 1 ]; then
249
250 symbol=" ◔ ▽"
251
252 elif [ "$dirty" -eq 1 ]; then
253
254 symbol=" ◔"
255
256 elif [ "$ahead" -eq 1 ] &&
257 [ "$behind" -eq 1 ]; then
258
259 symbol=" ◆"
260
261 elif [ "$ahead" -eq 1 ]; then
262
263 symbol=" ▲"
264
265 elif [ "$behind" -eq 1 ]; then
266
267 symbol=" ▼"
268 fi
269
270 stash="$(git stash list 2>/dev/null | wc -l | tr -d ' ')"
271
272 printf '%s %s%s' \
273 "$SYNO_ICON_GIT" \
274 "$branch" \
275 "$symbol"
276
277 if [ "${stash:-0}" -gt 0 ] 2>/dev/null; then
278 printf ' %s🗎' "$stash"
279 fi
280}
281
282
283# ==============================================================================
284# FILESYSTEM USAGE
285#
286# Displays usage of the filesystem containing the current working directory.
287#
288# Example:
289#
290# /volume1/docker/... -> volume1 usage
291# /tmp/... -> filesystem containing /tmp
292# /root -> root filesystem usage
293# ==============================================================================
294
295_syno_disk_percent()
296{
297 [ "$SYNO_SHOW_DISK" = "1" ] || return
298
299 local usage
300
301 usage="$(
302 df -P "$PWD" 2>/dev/null |
303 awk 'NR==2 {gsub(/%/, "", $5); print $5}'
304 )"
305
306 [ -n "$usage" ] || return
307
308 printf '%s' "$usage"
309}
310
311
312# ==============================================================================
313# POWERLINE SEGMENT RENDERER
314#
315# Escape sequences are stored directly in PS1.
316#
317# \[ and \] tell Bash that ANSI escape sequences consume zero terminal columns.
318# This keeps cursor positioning and line editing correct.
319# ==============================================================================
320
321_syno_add_segment()
322{
323 local text="$1"
324 local fg="$2"
325 local bg="$3"
326 local next_bg="$4"
327
328 PS1+="\[\e[1;38;5;${fg};48;5;${bg}m\]"
329 PS1+=" ${text} "
330
331 # Arrow:
332 # foreground = current background
333 # background = next segment background
334 PS1+="\[\e[0;38;5;${bg};48;5;${next_bg}m\]"
335 PS1+="${SYNO_SEPARATOR}"
336}
337
338
339_syno_add_last_segment()
340{
341 local text="$1"
342 local fg="$2"
343 local bg="$3"
344
345 PS1+="\[\e[1;38;5;${fg};48;5;${bg}m\]"
346 PS1+=" ${text} "
347
348 # Final arrow into terminal default background.
349 PS1+="\[\e[0;38;5;${bg};49m\]"
350 PS1+="${SYNO_SEPARATOR}"
351
352 PS1+="\[\e[0m\]"
353}
354
355
356# ==============================================================================
357# PROMPT BUILDER
358# ==============================================================================
359
360_syno_prompt()
361{
362 # IMPORTANT:
363 # This must be the first command so $? still contains the exit status
364 # of the user's previous command.
365 local exit_code=$?
366
367 local user_text
368 local host_text
369 local pwd_text
370 local git_text=""
371 local disk_text=""
372 local disk_percent=""
373 local date_text
374 local clock_text
375
376 user_text="${SYNO_ICON_USER} ${USER:-$(id -un)}"
377 host_text="${SYNO_ICON_HOST} ${HOSTNAME:-$(hostname)}"
378 pwd_text="${SYNO_ICON_FOLDER} $(_syno_pwd)"
379
380 git_text="$(_syno_git)"
381
382 disk_percent="$(_syno_disk_percent)"
383
384 if [ -n "$disk_percent" ]; then
385 disk_text="${SYNO_ICON_DISK} ${disk_percent}%"
386 fi
387
388 date_text="${SYNO_ICON_CALENDAR} $(_syno_date)"
389 clock_text="${SYNO_ICON_CLOCK} $(date +%H:%M:%S)"
390
391
392 # ==========================================================================
393 # NORMAL USER PALETTE
394 # ==========================================================================
395
396 local user_fg=16
397 local user_bg=46
398
399 local host_fg=231
400 local host_bg=33
401
402 local pwd_fg=238
403 local pwd_bg=255
404
405 local git_fg=250
406 local git_bg=238
407
408 local disk_fg=231
409 local disk_bg=31
410
411 local date_fg=231
412 local date_bg=135
413
414 local clock_fg=231
415 local clock_bg=16
416
417 local input_fg=45
418
419
420 # ==========================================================================
421 # ROOT WARNING PALETTE
422 #
423 # Root is deliberately visually obvious.
424 # ==========================================================================
425
426 if [ "$(id -u)" -eq 0 ]; then
427
428 user_fg=231
429 user_bg=196
430
431 host_fg=231
432 host_bg=203
433
434 pwd_fg=16
435 pwd_bg=226
436
437 git_fg=231
438 git_bg=196
439
440 disk_fg=16
441 disk_bg=214
442
443 date_fg=231
444 date_bg=203
445
446 clock_fg=231
447 clock_bg=196
448
449 input_fg=203
450 fi
451
452
453 # ==========================================================================
454 # BUILD FIRST LINE
455 # ==========================================================================
456
457 PS1="\n╭"
458
459 _syno_add_segment \
460 "$user_text" \
461 "$user_fg" \
462 "$user_bg" \
463 "$host_bg"
464
465 _syno_add_segment \
466 "$host_text" \
467 "$host_fg" \
468 "$host_bg" \
469 "$pwd_bg"
470
471
472 # --------------------------------------------------------------------------
473 # PWD -> optional Git -> optional Disk -> Date -> Clock
474 #
475 # We need to determine the background of the segment following PWD.
476 # --------------------------------------------------------------------------
477
478 if [ -n "$git_text" ]; then
479
480 _syno_add_segment \
481 "$pwd_text" \
482 "$pwd_fg" \
483 "$pwd_bg" \
484 "$git_bg"
485
486 if [ -n "$disk_text" ]; then
487
488 _syno_add_segment \
489 "$git_text" \
490 "$git_fg" \
491 "$git_bg" \
492 "$disk_bg"
493
494 else
495
496 _syno_add_segment \
497 "$git_text" \
498 "$git_fg" \
499 "$git_bg" \
500 "$date_bg"
501 fi
502
503 else
504
505 if [ -n "$disk_text" ]; then
506
507 _syno_add_segment \
508 "$pwd_text" \
509 "$pwd_fg" \
510 "$pwd_bg" \
511 "$disk_bg"
512
513 else
514
515 _syno_add_segment \
516 "$pwd_text" \
517 "$pwd_fg" \
518 "$pwd_bg" \
519 "$date_bg"
520 fi
521 fi
522
523
524 if [ -n "$disk_text" ]; then
525
526 _syno_add_segment \
527 "$disk_text" \
528 "$disk_fg" \
529 "$disk_bg" \
530 "$date_bg"
531 fi
532
533
534 _syno_add_segment \
535 "$date_text" \
536 "$date_fg" \
537 "$date_bg" \
538 "$clock_bg"
539
540
541 _syno_add_last_segment \
542 "$clock_text" \
543 "$clock_fg" \
544 "$clock_bg"
545
546
547 # ==========================================================================
548 # BUILD SECOND LINE
549 # ==========================================================================
550
551 local symbol='$'
552
553 if [ "$(id -u)" -eq 0 ]; then
554 symbol='#'
555 fi
556
557 PS1+="\n╰> "
558
559
560 # Previous command failed.
561 if [ "$SYNO_SHOW_EXIT_CODE" = "1" ] &&
562 [ "$exit_code" -ne 0 ]; then
563
564 PS1+="\[\e[1;38;5;196m\]"
565 PS1+="✘ ${exit_code} "
566 PS1+="\[\e[0m\]"
567 fi
568
569
570 PS1+="\[\e[1;38;5;${input_fg}m\]"
571 PS1+="${symbol} "
572 PS1+="\[\e[0m\]"
573}
574
575
576# ==============================================================================
577# ACTIVATE PROMPT
578# ==============================================================================
579
580PROMPT_COMMAND=_syno_prompt
581
582
583# ==============================================================================
584# OPTIONAL CONVENIENCE ALIASES
585#
586# Only simple commands known to work on DSM are used.
587# ==============================================================================
588
589alias ll='ls -lah'
590alias la='ls -A'
591alias l='ls -CF'
592
593alias dfh='df -h'
594alias duh='du -h'
595
596if command -v git >/dev/null 2>&1; then
597 alias gs='git status'
598 alias gl='git log --oneline --decorate --graph -15'
599fi
600
601
602# ==============================================================================
603# CLEAN TERMINAL FORMATTING ON EXIT
604# ==============================================================================
605
606trap 'printf "\033[0m"' EXIT