on 89df9d9 help sign in
searx/utils/lib.sh
527 lines 13 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
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 no_eval=""
381 local CONF=""
383 if [[ "$1" == "--no-eval" ]]; then
384 no_eval=$1; shift
385 fi
387 CONF=$1
388 # shellcheck disable=SC2086
389 install_template $no_eval "${CONF}" root root 644
390 uWSGI_enable_app "$(basename "${CONF}")"
391 uWSGI_restart
392 info_msg "installed uWSGI app: $(basename "${CONF}")"
395uWSGI_remove_app() {
397 # usage: uWSGI_remove_app <path.ini>
399 local CONF=$1
400 uWSGI_disable_app "$(basename "${CONF}")"
401 uWSGI_restart
402 rm -f "$CONF"
403 info_msg "removed uWSGI app: $(basename "${CONF}")"
406# shellcheck disable=SC2164
407uWSGI_enable_app() {
409 # usage: uWSGI_enable_app <path.ini>
411 local CONF=$1
412 if [[ -z $CONF ]]; then
413 err_msg "uWSGI_enable_app missing arguments"
414 return 42
415 fi
416 pushd "${uWSGI_SETUP}/apps-enabled" >/dev/null
417 rm -f "$(basename "${CONF}")"
418 # shellcheck disable=SC2226
419 ln -s "../apps-available/$(basename "${CONF}")"
420 info_msg "enabled uWSGI app: $(basename "${CONF}") (restart uWSGI required)"
421 popd >/dev/null
424uWSGI_disable_app() {
426 # usage: uWSGI_disable_app <path.ini>
428 local CONF=$1
429 if [[ -z $CONF ]]; then
430 err_msg "uWSGI_enable_app missing arguments"
431 return 42
432 fi
434 rm -f "${uWSGI_SETUP}/apps-enabled/$CONF"
435 info_msg "disabled uWSGI app: $(basename "${CONF}") (restart uWSGI required)"
438# distro's package manager
439# ------------------------
441# FIXME: Arch Linux & RHEL should be added
444pkg_install() {
446 # usage: TITEL='install foobar' pkg_install foopkg barpkg
448 rst_title "${TITLE:-installation of packages}" section
449 echo -en "\npackage(s)::\n\n $*\n" | $FMT
451 if ! ask_yn "Should packages be installed?" Yn 30; then
452 return 42
453 fi
454 # shellcheck disable=SC2068
455 apt-get install -y $@
458pkg_remove() {
460 # usage: TITEL='remove foobar' pkg_remove foopkg barpkg
462 rst_title "${TITLE:-remove packages}" section
463 echo -en "\npackage(s)::\n\n $*\n" | $FMT
465 if ! ask_yn "Should packages be removed (purge)?" Yn 30; then
466 return 42
467 fi
468 apt-get purge --autoremove --ignore-missing -y "$@"
471pkg_is_installed() {
473 # usage: pkg_is_install foopkg || pkg_install foopkg
475 dpkg -l "$1" &> /dev/null
476 return $?
479# git tooling
480# -----------
482# shellcheck disable=SC2164
483git_clone() {
485 # usage:
486 #
487 # git_clone <url> <name> [<branch> [<user>]]
488 # git_clone <url> <path> [<branch> [<user>]]
489 #
490 # First form uses $CACHE/<name> as destination folder, second form clones
491 # into <path>. If repository is allready cloned, pull from <branch> and
492 # update working tree (if needed, the caller has to stash local changes).
493 #
494 # git clone https://github.com/asciimoo/searx searx-src origin/master searxlogin
495 #
497 local url="$1"
498 local dest="$2"
499 local branch="$3"
500 local user="$4"
501 local bash_cmd="bash"
502 local remote="origin"
504 if [[ ! "${dest:0:1}" = "/" ]]; then
505 dest="$CACHE/$dest"
506 fi
508 [[ -z $branch ]] && branch=master
509 [[ -z $user ]] && [[ ! -z "${SUDO_USER}" ]] && user="${SUDO_USER}"
510 [[ ! -z $user ]] && bash_cmd="sudo -H -u $user -i"
512 if [[ -d "${dest}" ]] ; then
513 info_msg "already cloned: $dest"
514 tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " |$user| "
515cd "${dest}"
516git checkout -m -B "$branch" --track "$remote/$branch"
517git pull --all
518EOF
519 else
520 info_msg "clone into: $dest"
521 tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " |$user| "
522mkdir -p "$(dirname "$dest")"
523cd "$(dirname "$dest")"
524git clone --branch "$branch" --origin "$remote" "$url" "$(basename "$dest")"
525EOF
526 fi