Last active 1790666318

dartokloning's Avatar dartokloning revised this gist 1790666317. Go to revision

1 file changed, 606 insertions

synology-prompt.sh(file created)

@@ -0,0 +1,606 @@
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.
41 + case "$-" in
42 + *i*) ;;
43 + *) return 0 2>/dev/null || exit 0 ;;
44 + esac
45 +
46 + # Bash is required.
47 + if [ -z "${BASH_VERSION:-}" ]; then
48 + return 0 2>/dev/null || exit 0
49 + fi
50 +
51 + # Prevent loading more than once in the same shell.
52 + if [ "${SYNO_PROMPT_LOADED:-0}" = "1" ]; then
53 + return 0
54 + fi
55 +
56 + SYNO_PROMPT_LOADED=1
57 +
58 +
59 + # ==============================================================================
60 + # CONFIGURATION
61 + # ==============================================================================
62 +
63 + # Maximum displayed PWD length.
64 + SYNO_MAX_PWD_LENGTH=35
65 +
66 + # Show filesystem usage segment.
67 + SYNO_SHOW_DISK=1
68 +
69 + # Show Git segment when inside a Git repository.
70 + SYNO_SHOW_GIT=1
71 +
72 + # Show previous command exit code when non-zero.
73 + SYNO_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 +
83 + SYNO_ICON_USER=$'\uf007' # 
84 + SYNO_ICON_HOST=$'\uf233' # 
85 + SYNO_ICON_FOLDER=$'\uf07b' # 
86 + SYNO_ICON_GIT=$'\ue725' # 
87 + SYNO_ICON_DISK=$'\uf0a0' # 
88 + SYNO_ICON_CALENDAR=$'\uf073' # 
89 + SYNO_ICON_CLOCK=$'\uf017' # 
90 +
91 + SYNO_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 +
580 + PROMPT_COMMAND=_syno_prompt
581 +
582 +
583 + # ==============================================================================
584 + # OPTIONAL CONVENIENCE ALIASES
585 + #
586 + # Only simple commands known to work on DSM are used.
587 + # ==============================================================================
588 +
589 + alias ll='ls -lah'
590 + alias la='ls -A'
591 + alias l='ls -CF'
592 +
593 + alias dfh='df -h'
594 + alias duh='du -h'
595 +
596 + if command -v git >/dev/null 2>&1; then
597 + alias gs='git status'
598 + alias gl='git log --oneline --decorate --graph -15'
599 + fi
600 +
601 +
602 + # ==============================================================================
603 + # CLEAN TERMINAL FORMATTING ON EXIT
604 + # ==============================================================================
605 +
606 + trap 'printf "\033[0m"' EXIT
Newer Older