on af2f58f help sign in
searx/utils/lib.sh
598 lines 15 kB history raw
1#!/usr/bin/env bash
2# -*- coding: utf-8; mode: sh indent-tabs-mode: nil -*-
3# shellcheck disable=SC2059,SC1117,SC2162,SC2004
5ADMIN_NAME="${ADMIN_NAME:-$(git config user.name)}"
6ADMIN_NAME="${ADMIN_NAME:-$USER}"
8ADMIN_EMAIL="${ADMIN_EMAIL:-$(git config user.email)}"
9ADMIN_EMAIL="${ADMIN_EMAIL:-$USER@$(hostname)}"
11if [[ -z "${REPO_ROOT}" ]]; then
12 REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")
13 while [ -h "${REPO_ROOT}" ] ; do
14 REPO_ROOT=$(readlink "${REPO_ROOT}")
15 done
16 REPO_ROOT=$(cd "${REPO_ROOT}/.." && pwd -P )
17fi
19if [[ -z ${TEMPLATES} ]]; then
20 TEMPLATES="${REPO_ROOT}/utils/templates"
21fi
23if [[ -z "$CACHE" ]]; then
24 CACHE="${REPO_ROOT}/cache"
25fi
27if [[ -z "$SYSTEMD_UNITS" ]]; then
28 SYSTEMD_UNITS="/lib/systemd/system"
29fi
31if [[ -z ${DIFF_CMD} ]]; then
32 DIFF_CMD="diff -u"
33 if command -v colordiff >/dev/null; then
34 DIFF_CMD="colordiff -u"
35 fi
36fi
38sudo_or_exit() {
39 # usage: sudo_or_exit
41 if [ ! "$(id -u)" -eq 0 ]; then
42 err_msg "this command requires root (sudo) privilege!" >&2
43 exit 42
44 fi
47rst_title() {
48 # usage: rst_title <header-text> [part|chapter|section]
50 case ${2-chapter} in
51 part) printf "\n${1//?/=}\n$1\n${1//?/=}\n";;
52 chapter) printf "\n${1}\n${1//?/=}\n";;
53 section) printf "\n${1}\n${1//?/-}\n";;
54 *)
55 err_msg "invalid argument '${2}' in line $(caller)"
56 return 42
57 ;;
58 esac
61if command -v fmt >/dev/null; then
62 export FMT="fmt -u"
63else
64 export FMT="cat"
65fi
67rst_para() {
68 # usage: RST_INDENT=1 rst_para "lorem ipsum ..."
69 local prefix=''
70 if ! [[ -z $RST_INDENT ]] && [[ $RST_INDENT -gt 0 ]]; then
71 prefix="$(for i in $(seq 1 "$RST_INDENT"); do printf " "; done)"
72 echo -en "\n$*\n" | $FMT | prefix_stdout "$prefix"
73 else
74 echo -en "\n$*\n" | $FMT
75 fi
78err_msg() { echo -e "ERROR: $*" >&2; }
79warn_msg() { echo -e "WARN: $*" >&2; }
80info_msg() { echo -e "INFO: $*"; }
82clean_stdin() {
83 if [[ $(uname -s) != 'Darwin' ]]; then
84 while read -n1 -t 0.1; do : ; done
85 fi
88wait_key(){
89 # usage: waitKEY [<timeout in sec>]
91 clean_stdin
92 local _t=$1
93 [[ ! -z $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
94 [[ ! -z $_t ]] && _t="-t $_t"
95 # shellcheck disable=SC2086
96 read -s -n1 $_t -p "** press any [KEY] to continue **"
97 echo
98 clean_stdin
101ask_yn() {
102 # usage: ask_yn <prompt-text> [Ny|Yn] [<timeout in sec>]
104 local EXIT_YES=0 # exit status 0 --> successful
105 local EXIT_NO=1 # exit status 1 --> error code
107 local _t=$3
108 [[ ! -z $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
109 [[ ! -z $_t ]] && _t="-t $_t"
110 case "${2}" in
111 Yn)
112 local exit_val=${EXIT_YES}
113 local choice="[YES/no]"
114 local default="Yes"
115 ;;
116 *)
117 local exit_val=${EXIT_NO}
118 local choice="[NO/yes]"
119 local default="No"
120 ;;
121 esac
122 echo
123 while true; do
124 clean_stdin
125 printf "$1 ${choice} "
126 # shellcheck disable=SC2086
127 read -n1 $_t
128 if [[ -z $REPLY ]]; then
129 printf "$default\n"; break
130 elif [[ $REPLY =~ ^[Yy]$ ]]; then
131 exit_val=${EXIT_YES}
132 printf "\n"
133 break
134 elif [[ $REPLY =~ ^[Nn]$ ]]; then
135 exit_val=${EXIT_NO}
136 printf "\n"
137 break
138 fi
139 _t=""
140 err_msg "invalid choice"
141 done
142 clean_stdin
143 return $exit_val
146tee_stderr () {
148 # usage::
149 # tee_stderr 1 <<EOF | python -i
150 # print("hello")
151 # EOF
152 # ...
153 # >>> print("hello")
154 # hello
156 local _t="0";
157 if [[ ! -z $1 ]] ; then _t="$1"; fi
159 (while read line; do
160 # shellcheck disable=SC2086
161 sleep $_t
162 echo -e "$line" >&2
163 echo "$line"
164 done)
167prefix_stdout () {
168 # usage: <cmd> | prefix_stdout [prefix]
170 local prefix=" | "
172 if [[ ! -z $1 ]] ; then prefix="$1"; fi
174 (while IFS= read line; do
175 echo -e "${prefix}$line"
176 done)
179append_line() {
181 # usage: append_line <line> <file>
182 #
183 # Append line if not exists, create file if not exists. E.g::
184 #
185 # append_line 'source ~/.foo' ~/bashrc
187 local LINE=$1
188 local FILE=$2
189 grep -qFs -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"
192cache_download() {
194 # usage: cache_download <url> <local-filename>
196 local exit_value=0
198 if [[ ! -z ${SUDO_USER} ]]; then
199 sudo -u "${SUDO_USER}" mkdir -p "${CACHE}"
200 else
201 mkdir -p "${CACHE}"
202 fi
204 if [[ -f "${CACHE}/$2" ]] ; then
205 info_msg "already cached: $1"
206 info_msg " --> ${CACHE}/$2"
207 fi
209 if [[ ! -f "${CACHE}/$2" ]]; then
210 info_msg "caching: $1"
211 info_msg " --> ${CACHE}/$2"
212 if [[ ! -z ${SUDO_USER} ]]; then
213 sudo -u "${SUDO_USER}" wget --progress=bar -O "${CACHE}/$2" "$1" ; exit_value=$?
214 else
215 wget --progress=bar -O "${CACHE}/$2" "$1" ; exit_value=$?
216 fi
217 if $exit_value; then
218 err_msg "failed to download: $1"
219 fi
220 fi
223choose_one() {
225 # usage:
226 #
227 # DEFAULT_SELECT= 2 \
228 # choose_one <name> "your selection?" "Coffee" "Coffee with milk"
230 local default=${DEFAULT_SELECT-1}
231 local REPLY
232 local env_name=$1 && shift
233 local choice=$1;
234 local max="${#@}"
235 local _t
236 [[ ! -z $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
237 [[ ! -z $_t ]] && _t="-t $_t"
239 list=("$@")
240 echo -e "Menu::"
241 for ((i=1; i<= $(($max -1)); i++)); do
242 if [[ "$i" == "$default" ]]; then
243 echo -e " $i.) ${list[$i]} [default]"
244 else
245 echo -e " $i.) ${list[$i]}"
246 fi
247 done
248 while true; do
249 clean_stdin
250 printf "$1 [$default] "
252 if (( 10 > $max )); then
253 # shellcheck disable=SC2086
254 read -n1 $_t
255 else
256 # shellcheck disable=SC2086,SC2229
257 read $_t
258 fi
259 # selection fits
260 [[ $REPLY =~ ^-?[0-9]+$ ]] && (( $REPLY > 0 )) && (( $REPLY < $max )) && break
262 # take default
263 [[ -z $REPLY ]] && REPLY=$default && break
265 _t=""
266 err_msg "invalid choice"
267 done
268 eval "$env_name"='${list[${REPLY}]}'
269 echo
270 clean_stdin
273install_template() {
275 # usage:
276 #
277 # install_template [--no-eval] {file} [{owner} [{group} [{chmod}]]]
278 #
279 # install_template --no-eval /etc/updatedb.conf root root 644
281 local do_eval=1
282 if [[ "$1" == "--no-eval" ]]; then
283 do_eval=0; shift
284 fi
285 local dst="${1}"
286 local owner=${2-$(id -un)}
287 local group=${3-$(id -gn)}
288 local chmod=${4-644}
289 local _reply=""
291 info_msg "install: ${dst}"
293 if [[ ! -f "${TEMPLATES}${dst}" ]] ; then
294 err_msg "${TEMPLATES}${dst} does not exists"
295 err_msg "... can't install $dst / exit installation with error 42"
296 wait_key 30
297 return 42
298 fi
300 local template_file="${TEMPLATES}${dst}"
301 if [[ "$do_eval" == "1" ]]; then
302 info_msg "BUILD template ${template_file}"
303 if [[ -f "${TEMPLATES}${dst}" ]] ; then
304 template_file="${CACHE}${dst}"
305 mkdir -p "$(dirname "${template_file}")"
306 # shellcheck disable=SC2086
307 eval "echo \"$(cat ${TEMPLATES}${dst})\"" > "${template_file}"
308 else
309 err_msg "failed ${template_file}"
310 return 42
311 fi
312 fi
314 mkdir -p "$(dirname "${dst}")"
316 if [[ ! -f "${dst}" ]]; then
317 info_msg "install: ${template_file}"
318 sudo -H install -v -o "${owner}" -g "${group}" -m "${chmod}" \
319 "${template_file}" "${dst}" | prefix_stdout
320 return $?
321 fi
323 if [[ -f "${dst}" ]] && cmp --silent "${template_file}" "${dst}" ; then
324 info_msg "file ${dst} allready installed"
325 return 0
326 fi
328 info_msg "file ${dst} allready exists on this host"
330 while true; do
331 choose_one _reply "choose next step with file $dst" \
332 "replace file" \
333 "leave file unchanged" \
334 "interactiv shell" \
335 "diff files"
337 case $_reply in
338 "replace file")
339 info_msg "install: ${template_file}"
340 sudo -H install -v -o "${owner}" -g "${group}" -m "${chmod}" \
341 "${template_file}" "${dst}" | prefix_stdout
342 break
343 ;;
344 "leave file unchanged")
345 break
346 ;;
347 "interactiv shell")
348 echo "// edit ${dst} to your needs"
349 echo "// exit with CTRL-D"
350 sudo -H -u "${owner}" -i
351 $DIFF_CMD "${dst}" "${template_file}"
352 if ask_yn "did you edit ${template_file} to your needs?"; then
353 break
354 fi
355 ;;
356 "diff files")
357 $DIFF_CMD "${dst}" "${template_file}" | prefix_stdout
358 esac
359 done
362# Apache
363# ------
365# FIXME: Arch Linux & RHEL should be added
367if [[ -z "${APACHE_SITES_AVAILABE}" ]]; then
368 APACHE_SITES_AVAILABE="/etc/apache2/sites-available"
369fi
371apache_is_installed() {
372 (command -v apachectl \
373 && command -v a2ensite \
374 && command -v a2dissite ) &>/dev/null
377apache_reload() {
379 info_msg "reload apache .."
380 echo
381 sudo -H apachectl configtest
382 sudo -H service apache2 force-reload
385apache_install_site() {
387 # usage: apache_install_site [--no-eval] <mysite.conf>
389 local no_eval=""
390 local CONF="$1"
392 if [[ "$1" == "--no-eval" ]]; then
393 no_eval=$1; shift
394 fi
396 # shellcheck disable=SC2086
397 install_template $no_eval "${APACHE_SITES_AVAILABE}/${CONF}" root root 644
399 apache_enable_site "${CONF}"
400 apache_reload
401 info_msg "installed apache site: ${CONF}"
404apache_enable_site() {
405 info_msg "enable apache site $1 .."
406 sudo -H a2ensite -q "$1"
407 apache_reload
410apache_dissable_site() {
411 info_msg "disable apache site $1 .."
412 sudo -H a2dissite -q "$1"
413 apache_reload
416# uWSGI
417# -----
419uWSGI_SETUP="${uWSGI_SETUP:=/etc/uwsgi}"
421uWSGI_restart() {
423 # usage: uWSGI_restart()
425 info_msg "restart uWSGI service"
426 sudo -H systemctl restart uwsgi
429uWSGI_app_available() {
430 # usage: uWSGI_app_available <myapp.ini>
431 local CONF="$1"
432 if [[ -z $CONF ]]; then
433 err_msg "uWSGI_app_available: missing arguments"
434 return 42
435 fi
436 [[ -f "${uWSGI_SETUP}/apps-available/${CONF}" ]]
439uWSGI_install_app() {
441 # usage: uWSGI_install_app [--no-eval] <myapp.ini>
443 local no_eval=""
444 local CONF="$1"
446 if [[ "$1" == "--no-eval" ]]; then
447 no_eval=$1; shift
448 fi
450 # shellcheck disable=SC2086
451 install_template $no_eval "${uWSGI_SETUP}/apps-available/${CONF}" root root 644
452 uWSGI_enable_app "${CONF}"
453 uWSGI_restart
454 info_msg "installed uWSGI app: ${CONF}"
457uWSGI_remove_app() {
459 # usage: uWSGI_remove_app <myapp.ini>
461 local CONF="$1"
462 uWSGI_disable_app "${CONF}"
463 uWSGI_restart
464 rm -f "${uWSGI_SETUP}/apps-available/${CONF}"
465 info_msg "removed uWSGI app: ${CONF}"
468uWSGI_app_enabled() {
469 # usage: uWSGI_app_enabled <myapp.ini>
470 local CONF="$1"
471 if [[ -z $CONF ]]; then
472 err_msg "uWSGI_app_enabled: missing arguments"
473 return 42
474 fi
475 [[ -f "${uWSGI_SETUP}/apps-enabled/${CONF}" ]]
478# shellcheck disable=SC2164
479uWSGI_enable_app() {
481 # usage: uWSGI_enable_app <myapp.ini>
483 local CONF="$1"
484 if [[ -z $CONF ]]; then
485 err_msg "uWSGI_enable_app: missing arguments"
486 return 42
487 fi
488 pushd "${uWSGI_SETUP}/apps-enabled" >/dev/null
489 rm -f "$CONF"
490 # shellcheck disable=SC2226
491 ln -s "../apps-available/${CONF}"
492 info_msg "enabled uWSGI app: ${CONF} (restart uWSGI required)"
493 popd >/dev/null
496uWSGI_disable_app() {
498 # usage: uWSGI_disable_app <myapp.ini>
500 local CONF="$1"
501 if [[ -z $CONF ]]; then
502 err_msg "uWSGI_enable_app: missing arguments"
503 return 42
504 fi
505 rm -f "${uWSGI_SETUP}/apps-enabled/${CONF}"
506 info_msg "disabled uWSGI app: ${CONF} (restart uWSGI required)"
509# distro's package manager
510# ------------------------
512# FIXME: Arch Linux & RHEL should be added
515pkg_install() {
517 # usage: TITEL='install foobar' pkg_install foopkg barpkg
519 rst_title "${TITLE:-installation of packages}" section
520 echo -en "\npackage(s)::\n\n $*\n" | $FMT
522 if ! ask_yn "Should packages be installed?" Yn 30; then
523 return 42
524 fi
525 # shellcheck disable=SC2068
526 apt-get install -y $@
529pkg_remove() {
531 # usage: TITEL='remove foobar' pkg_remove foopkg barpkg
533 rst_title "${TITLE:-remove packages}" section
534 echo -en "\npackage(s)::\n\n $*\n" | $FMT
536 if ! ask_yn "Should packages be removed (purge)?" Yn 30; then
537 return 42
538 fi
539 apt-get purge --autoremove --ignore-missing -y "$@"
542pkg_is_installed() {
544 # usage: pkg_is_install foopkg || pkg_install foopkg
546 dpkg -l "$1" &> /dev/null
547 return $?
550# git tooling
551# -----------
553# shellcheck disable=SC2164
554git_clone() {
556 # usage:
557 #
558 # git_clone <url> <name> [<branch> [<user>]]
559 # git_clone <url> <path> [<branch> [<user>]]
560 #
561 # First form uses $CACHE/<name> as destination folder, second form clones
562 # into <path>. If repository is allready cloned, pull from <branch> and
563 # update working tree (if needed, the caller has to stash local changes).
564 #
565 # git clone https://github.com/asciimoo/searx searx-src origin/master searxlogin
566 #
568 local url="$1"
569 local dest="$2"
570 local branch="$3"
571 local user="$4"
572 local bash_cmd="bash"
573 local remote="origin"
575 if [[ ! "${dest:0:1}" = "/" ]]; then
576 dest="$CACHE/$dest"
577 fi
579 [[ -z $branch ]] && branch=master
580 [[ -z $user ]] && [[ ! -z "${SUDO_USER}" ]] && user="${SUDO_USER}"
581 [[ ! -z $user ]] && bash_cmd="sudo -H -u $user -i"
583 if [[ -d "${dest}" ]] ; then
584 info_msg "already cloned: $dest"
585 tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " |$user| "
586cd "${dest}"
587git checkout -m -B "$branch" --track "$remote/$branch"
588git pull --all
589EOF
590 else
591 info_msg "clone into: $dest"
592 tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " |$user| "
593mkdir -p "$(dirname "$dest")"
594cd "$(dirname "$dest")"
595git clone --branch "$branch" --origin "$remote" "$url" "$(basename "$dest")"
596EOF
597 fi