on 3cf3152 help sign in
searx/utils/lib.sh
528 lines 13 kB history raw
1#!/usr/bin/env bash
2# -*- coding: utf-8; mode: sh -*-
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 echo
269 clean_stdin
270 eval "$env_name"='${list[${REPLY}]}'
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
363# uWSGI
364# -----
366uWSGI_SETUP="${uWSGI_SETUP:=/etc/uwsgi}"
368uWSGI_restart() {
370 # usage: uWSGI_restart()
372 info_msg "restart uWSGI service"
373 sudo -H systemctl restart uwsgi
376uWSGI_install_app() {
378 # usage: uWSGI_install_app [--no-eval] /etc/uwsgi/apps-available/myapp.ini ...
380 local do_eval=""
381 local CONF
383 if [[ "$1" == "--no-eval" ]]; then
384 no_eval=$1; shift
385 fi
387 for CONF in "$@"; do
388 install_template "$no_eval" "${CONF}" root root 644
389 uWSGI_enable_app "$(basename "${CONF}")"
390 info_msg "enabled uWSGI app: $(basename "${CONF}")"
391 done
392 uWSGI_restart
395uWSGI_remove_app() {
397 # usage: uWSGI_remove_app <path.ini> ...
399 local CONF
400 for CONF in "$@"; do
401 uWSGI_disable_app "$(basename "${CONF}")"
402 rm -f "$CONF"
403 info_msg "removed uWSGI app: $(basename "${CONF}")"
404 done
405 uWSGI_restart
408# shellcheck disable=SC2164
409uWSGI_enable_app() {
411 # usage: uWSGI_enable_app <path.ini>
413 local CONF=$1
414 if [[ -z $CONF ]]; then
415 err_msg "uWSGI_enable_app missing arguments"
416 return 42
417 fi
418 pushd "${uWSGI_SETUP}/apps-enabled" >/dev/null
419 # shellcheck disable=SC2226
420 ln -s "../apps-available/$(basename "${CONF}")"
421 info_msg "enabled uWSGI app: $(basename "${CONF}") (restart uWSGI required)"
422 popd >/dev/null
425uWSGI_disable_app() {
427 # usage: uWSGI_disable_app <path.ini>
429 local CONF=$1
430 if [[ -z $CONF ]]; then
431 err_msg "uWSGI_enable_app missing arguments"
432 return 42
433 fi
435 rm -f "${uWSGI_SETUP}/apps-enabled/$CONF"
436 info_msg "disabled uWSGI app: $(basename "${CONF}") (restart uWSGI required)"
439# distro's package manager
440# ------------------------
442# FIXME: Arch Linux & RHEL should be added
445pkg_install() {
447 # usage: TITEL='install foobar' pkg_install foopkg barpkg
449 rst_title "${TITLE:-installation of packages}" section
450 echo -en "\npackage(s)::\n\n $*\n" | $FMT
452 if ! ask_yn "Should packages be installed?" Yn 30; then
453 return 42
454 fi
455 # shellcheck disable=SC2068
456 apt-get install -y $@
457 wait_key 30
460pkg_remove() {
462 # usage: TITEL='remove foobar' pkg_remove foopkg barpkg
464 rst_title "${TITLE:-remove packages}" section
465 echo -en "\npackage(s)::\n\n $*\n" | $FMT
467 if ! ask_yn "Should packages be removed (purge)?" Yn 30; then
468 return 42
469 fi
470 apt-get purge --autoremove --ignore-missing -y "$@"
471 wait_key 30
474pkg_is_installed() {
476 # usage: pkg_is_install foopkg || pkg_install foopkg
478 dpkg -l "$1" &> /dev/null
479 return $?
482# git tooling
483# -----------
485# shellcheck disable=SC2164
486git_clone() {
488 # usage:
489 #
490 # git_clone <url> <name> [<branch> [<user>]]
491 # git_clone <url> <path> [<branch> [<user>]]
492 #
493 # First form uses $CACHE/<name> as destination folder, second form clones
494 # into <path>. If repository is allready cloned, merge from origin and
495 # update working tree (if needed, the caller has to stash local changes).
496 #
497 # git clone https://github.com/asciimoo/searx searx-src origin/master searxlogin
498 #
500 local url="$1"
501 local dest="$2"
502 local branch="$3"
503 local user="$4"
504 local prefix=""
506 if [[ ! "${dest:0:1}" = "/" ]]; then
507 dest="$CACHE/$dest"
508 fi
510 [[ -z $branch ]] && branch=master
511 [[ -z $user ]] && [[ ! -z "${SUDO_USER}" ]] && user="${SUDO_USER}"
512 [[ -z $user ]] && prefix="sudo -H -u $user"
514 if [[ -d "${dest}" ]] ; then
515 info_msg "already cloned: $dest"
516 pushd "${dest}" > /dev/null
517 $prefix git checkout -b "$(basename "$branch")" --track "$branch"
518 $prefix git pull --all
519 popd > /dev/null
521 else
522 info_msg "clone into: $dest"
523 $prefix mkdir -p "$(dirname "$dest")"
524 pushd "${dest}" > /dev/null
525 git clone "$url" "$(basename "$dest")"
526 popd > /dev/null
527 fi