lib.sh 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. #!/usr/bin/env bash
  2. # -*- coding: utf-8; mode: sh indent-tabs-mode: nil -*-
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. # shellcheck disable=SC2059,SC1117
  5. # ubuntu, debian, arch, fedora ...
  6. DIST_ID=$(source /etc/os-release; echo "$ID");
  7. # shellcheck disable=SC2034
  8. DIST_VERS=$(source /etc/os-release; echo "$VERSION_ID");
  9. ADMIN_NAME="${ADMIN_NAME:-$(git config user.name)}"
  10. ADMIN_NAME="${ADMIN_NAME:-$USER}"
  11. ADMIN_EMAIL="${ADMIN_EMAIL:-$(git config user.email)}"
  12. ADMIN_EMAIL="${ADMIN_EMAIL:-$USER@$(hostname)}"
  13. if [[ -z "${REPO_ROOT}" ]]; then
  14. REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")
  15. while [ -h "${REPO_ROOT}" ] ; do
  16. REPO_ROOT=$(readlink "${REPO_ROOT}")
  17. done
  18. REPO_ROOT=$(cd "${REPO_ROOT}/.." && pwd -P )
  19. fi
  20. if [[ -z ${TEMPLATES} ]]; then
  21. TEMPLATES="${REPO_ROOT}/utils/templates"
  22. fi
  23. if [[ -z "$CACHE" ]]; then
  24. CACHE="${REPO_ROOT}/cache"
  25. fi
  26. if [[ -z ${DIFF_CMD} ]]; then
  27. DIFF_CMD="diff -u"
  28. if command -v colordiff >/dev/null; then
  29. DIFF_CMD="colordiff -u"
  30. fi
  31. fi
  32. DOT_CONFIG="${DOT_CONFIG:-${REPO_ROOT}/.config.sh}"
  33. source_dot_config() {
  34. if [[ ! -e "${DOT_CONFIG}" ]]; then
  35. err_msg "configuration does not extsts at: ${DOT_CONFIG}"
  36. return 42
  37. fi
  38. # shellcheck disable=SC1090
  39. source "${DOT_CONFIG}"
  40. }
  41. sudo_or_exit() {
  42. # usage: sudo_or_exit
  43. if [ ! "$(id -u)" -eq 0 ]; then
  44. err_msg "this command requires root (sudo) privilege!" >&2
  45. exit 42
  46. fi
  47. }
  48. required_commands() {
  49. # usage: required_commands [cmd1 ...]
  50. local exit_val=0
  51. while [ -n "$1" ]; do
  52. if ! command -v "$1" &>/dev/null; then
  53. err_msg "missing command $1"
  54. exit_val=42
  55. fi
  56. shift
  57. done
  58. return $exit_val
  59. }
  60. # colors
  61. # ------
  62. # shellcheck disable=SC2034
  63. set_terminal_colors() {
  64. _colors=8
  65. _creset='\e[0m' # reset all attributes
  66. _Black='\e[0;30m'
  67. _White='\e[1;37m'
  68. _Red='\e[0;31m'
  69. _Green='\e[0;32m'
  70. _Yellow='\e[0;33m'
  71. _Blue='\e[0;34m'
  72. _Violet='\e[0;35m'
  73. _Cyan='\e[0;36m'
  74. _BBlack='\e[1;30m'
  75. _BWhite='\e[1;37m'
  76. _BRed='\e[1;31m'
  77. _BGreen='\e[1;32m'
  78. _BYellow='\e[1;33m'
  79. _BBlue='\e[1;34m'
  80. _BPurple='\e[1;35m'
  81. _BCyan='\e[1;36m'
  82. }
  83. if [ ! -p /dev/stdout ]; then
  84. set_terminal_colors
  85. fi
  86. # reST
  87. # ----
  88. if command -v fmt >/dev/null; then
  89. export FMT="fmt -u"
  90. else
  91. export FMT="cat"
  92. fi
  93. rst_title() {
  94. # usage: rst_title <header-text> [part|chapter|section]
  95. case ${2-chapter} in
  96. part) printf "\n${_BGreen}${1//?/=}${_creset}\n${_BCyan}${1}${_creset}\n${_BGreen}${1//?/=}${_creset}\n";;
  97. chapter) printf "\n${_BCyan}${1}${_creset}\n${_BGreen}${1//?/=}${_creset}\n";;
  98. section) printf "\n${_BCyan}${1}${_creset}\n${_BGreen}${1//?/-}${_creset}\n";;
  99. *)
  100. err_msg "invalid argument '${2}' in line $(caller)"
  101. return 42
  102. ;;
  103. esac
  104. }
  105. rst_para() {
  106. # usage: RST_INDENT=1 rst_para "lorem ipsum ..."
  107. local prefix=''
  108. if [[ -n $RST_INDENT ]] && [[ $RST_INDENT -gt 0 ]]; then
  109. prefix="$(for i in $(seq 1 "$RST_INDENT"); do printf " "; done)"
  110. echo -en "\n$*\n" | $FMT | prefix_stdout "$prefix"
  111. else
  112. echo -en "\n$*\n" | $FMT
  113. fi
  114. }
  115. err_msg() { echo -e "${_BRed}ERROR:${_creset} $*" >&2; }
  116. warn_msg() { echo -e "${_BBlue}WARN:${_creset} $*" >&2; }
  117. info_msg() { echo -e "${_BYellow}INFO:${_creset} $*" >&2; }
  118. clean_stdin() {
  119. if [[ $(uname -s) != 'Darwin' ]]; then
  120. while read -r -n1 -t 0.1; do : ; done
  121. fi
  122. }
  123. wait_key(){
  124. # usage: waitKEY [<timeout in sec>]
  125. clean_stdin
  126. local _t=$1
  127. local msg="${MSG}"
  128. [[ -z "$msg" ]] && msg="${_Green}** press any [${_BCyan}KEY${_Green}] to continue **${_creset}"
  129. [[ -n $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
  130. [[ -n $_t ]] && _t="-t $_t"
  131. printf "$msg"
  132. # shellcheck disable=SC2086
  133. read -r -s -n1 $_t
  134. echo
  135. clean_stdin
  136. }
  137. ask_yn() {
  138. # usage: ask_yn <prompt-text> [Ny|Yn] [<timeout in sec>]
  139. local EXIT_YES=0 # exit status 0 --> successful
  140. local EXIT_NO=1 # exit status 1 --> error code
  141. local _t=$3
  142. [[ -n $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
  143. [[ -n $_t ]] && _t="-t $_t"
  144. case "${FORCE_SELECTION:-${2}}" in
  145. Y) return ${EXIT_YES} ;;
  146. N) return ${EXIT_NO} ;;
  147. Yn)
  148. local exit_val=${EXIT_YES}
  149. local choice="[${_BGreen}YES${_creset}/no]"
  150. local default="Yes"
  151. ;;
  152. *)
  153. local exit_val=${EXIT_NO}
  154. local choice="[${_BGreen}NO${_creset}/yes]"
  155. local default="No"
  156. ;;
  157. esac
  158. echo
  159. while true; do
  160. clean_stdin
  161. printf "$1 ${choice} "
  162. # shellcheck disable=SC2086
  163. read -r -n1 $_t
  164. if [[ -z $REPLY ]]; then
  165. printf "$default\n"; break
  166. elif [[ $REPLY =~ ^[Yy]$ ]]; then
  167. exit_val=${EXIT_YES}
  168. printf "\n"
  169. break
  170. elif [[ $REPLY =~ ^[Nn]$ ]]; then
  171. exit_val=${EXIT_NO}
  172. printf "\n"
  173. break
  174. fi
  175. _t=""
  176. err_msg "invalid choice"
  177. done
  178. clean_stdin
  179. return $exit_val
  180. }
  181. tee_stderr () {
  182. # usage::
  183. # tee_stderr 1 <<EOF | python -i
  184. # print("hello")
  185. # EOF
  186. # ...
  187. # >>> print("hello")
  188. # hello
  189. local _t="0";
  190. if [[ -n $1 ]] ; then _t="$1"; fi
  191. (while read -r line; do
  192. # shellcheck disable=SC2086
  193. sleep $_t
  194. echo -e "$line" >&2
  195. echo "$line"
  196. done)
  197. }
  198. prefix_stdout () {
  199. # usage: <cmd> | prefix_stdout [prefix]
  200. local prefix="${_BYellow}-->|${_creset}"
  201. if [[ -n $1 ]] ; then prefix="$1"; fi
  202. # shellcheck disable=SC2162
  203. (while IFS= read line; do
  204. echo -e "${prefix}$line"
  205. done)
  206. }
  207. append_line() {
  208. # usage: append_line <line> <file>
  209. #
  210. # Append line if not exists, create file if not exists. E.g::
  211. #
  212. # append_line 'source ~/.foo' ~/bashrc
  213. local LINE=$1
  214. local FILE=$2
  215. grep -qFs -- "$LINE" "$FILE" || echo "$LINE" >> "$FILE"
  216. }
  217. cache_download() {
  218. # usage: cache_download <url> <local-filename>
  219. local exit_value=0
  220. if [[ -n ${SUDO_USER} ]]; then
  221. sudo -u "${SUDO_USER}" mkdir -p "${CACHE}"
  222. else
  223. mkdir -p "${CACHE}"
  224. fi
  225. if [[ -f "${CACHE}/$2" ]] ; then
  226. info_msg "already cached: $1"
  227. info_msg " --> ${CACHE}/$2"
  228. fi
  229. if [[ ! -f "${CACHE}/$2" ]]; then
  230. info_msg "caching: $1"
  231. info_msg " --> ${CACHE}/$2"
  232. if [[ -n ${SUDO_USER} ]]; then
  233. sudo -u "${SUDO_USER}" wget --progress=bar -O "${CACHE}/$2" "$1" ; exit_value=$?
  234. else
  235. wget --progress=bar -O "${CACHE}/$2" "$1" ; exit_value=$?
  236. fi
  237. if [[ ! $exit_value = 0 ]]; then
  238. err_msg "failed to download: $1"
  239. fi
  240. fi
  241. }
  242. backup_file() {
  243. # usage: backup_file /path/to/file.foo
  244. local stamp
  245. stamp=$(date +"_%Y%m%d_%H%M%S")
  246. info_msg "create backup: ${1}${stamp}"
  247. cp -a "${1}" "${1}${stamp}"
  248. }
  249. choose_one() {
  250. # usage:
  251. #
  252. # DEFAULT_SELECT= 2 \
  253. # choose_one <name> "your selection?" "Coffee" "Coffee with milk"
  254. local default=${DEFAULT_SELECT-1}
  255. local REPLY
  256. local env_name=$1 && shift
  257. local choice=$1;
  258. local max="${#@}"
  259. local _t
  260. [[ -n $FORCE_TIMEOUT ]] && _t=$FORCE_TIMEOUT
  261. [[ -n $_t ]] && _t="-t $_t"
  262. list=("$@")
  263. echo -e "${_BGreen}Menu::${_creset}"
  264. for ((i=1; i<= $((max -1)); i++)); do
  265. if [[ "$i" == "$default" ]]; then
  266. echo -e " ${_BGreen}$i.${_creset}) ${list[$i]} [default]"
  267. else
  268. echo -e " $i.) ${list[$i]}"
  269. fi
  270. done
  271. while true; do
  272. clean_stdin
  273. printf "$1 [${_BGreen}$default${_creset}] "
  274. if (( 10 > max )); then
  275. # shellcheck disable=SC2086
  276. read -r -n1 $_t
  277. else
  278. # shellcheck disable=SC2086,SC2229
  279. read -r $_t
  280. fi
  281. # selection fits
  282. [[ $REPLY =~ ^-?[0-9]+$ ]] && (( REPLY > 0 )) && (( REPLY < max )) && break
  283. # take default
  284. [[ -z $REPLY ]] && REPLY=$default && break
  285. _t=""
  286. err_msg "invalid choice"
  287. done
  288. eval "$env_name"='${list[${REPLY}]}'
  289. echo
  290. clean_stdin
  291. }
  292. install_template() {
  293. # usage:
  294. #
  295. # install_template [--no-eval] [--variant=<name>] \
  296. # {file} [{owner} [{group} [{chmod}]]]
  297. #
  298. # E.g. the origin of variant 'raw' of /etc/updatedb.conf is::
  299. #
  300. # ${TEMPLATES}/etc/updatedb.conf:raw
  301. #
  302. # To install variant 'raw' of /etc/updatedb.conf without evaluated
  303. # replacements you can use::
  304. #
  305. # install_template --variant=raw --no-eval \
  306. # /etc/updatedb.conf root root 644
  307. local _reply=""
  308. local do_eval=1
  309. local variant=""
  310. local pos_args=("$0")
  311. for i in "$@"; do
  312. case $i in
  313. --no-eval) do_eval=0; shift ;;
  314. --variant=*) variant=":${i#*=}"; shift ;;
  315. *) pos_args+=("$i") ;;
  316. esac
  317. done
  318. local dst="${pos_args[1]}"
  319. local template_origin="${TEMPLATES}${dst}${variant}"
  320. local template_file="${TEMPLATES}${dst}"
  321. local owner="${pos_args[2]-$(id -un)}"
  322. local group="${pos_args[3]-$(id -gn)}"
  323. local chmod="${pos_args[4]-644}"
  324. info_msg "install (eval=$do_eval): ${dst}"
  325. [[ -n $variant ]] && info_msg "variant: ${variant}"
  326. if [[ ! -f "${template_origin}" ]] ; then
  327. err_msg "${template_origin} does not exists"
  328. err_msg "... can't install $dst"
  329. wait_key 30
  330. return 42
  331. fi
  332. if [[ "$do_eval" == "1" ]]; then
  333. template_file="${CACHE}${dst}${variant}"
  334. info_msg "BUILD template ${template_file}"
  335. if [[ -n ${SUDO_USER} ]]; then
  336. sudo -u "${SUDO_USER}" mkdir -p "$(dirname "${template_file}")"
  337. else
  338. mkdir -p "$(dirname "${template_file}")"
  339. fi
  340. # shellcheck disable=SC2086
  341. eval "echo \"$(cat ${template_origin})\"" > "${template_file}"
  342. if [[ -n ${SUDO_USER} ]]; then
  343. chown "${SUDO_USER}:${SUDO_USER}" "${template_file}"
  344. fi
  345. else
  346. template_file=$template_origin
  347. fi
  348. mkdir -p "$(dirname "${dst}")"
  349. if [[ ! -f "${dst}" ]]; then
  350. info_msg "install: ${template_file}"
  351. sudo -H install -v -o "${owner}" -g "${group}" -m "${chmod}" \
  352. "${template_file}" "${dst}" | prefix_stdout
  353. return $?
  354. fi
  355. if [[ -f "${dst}" ]] && cmp --silent "${template_file}" "${dst}" ; then
  356. info_msg "file ${dst} allready installed"
  357. return 0
  358. fi
  359. info_msg "diffrent file ${dst} allready exists on this host"
  360. while true; do
  361. choose_one _reply "choose next step with file $dst" \
  362. "replace file" \
  363. "leave file unchanged" \
  364. "interactiv shell" \
  365. "diff files"
  366. case $_reply in
  367. "replace file")
  368. info_msg "install: ${template_file}"
  369. sudo -H install -v -o "${owner}" -g "${group}" -m "${chmod}" \
  370. "${template_file}" "${dst}" | prefix_stdout
  371. break
  372. ;;
  373. "leave file unchanged")
  374. break
  375. ;;
  376. "interactiv shell")
  377. echo -e "// edit ${_Red}${dst}${_creset} to your needs"
  378. echo -e "// exit with [${_BCyan}CTRL-D${_creset}]"
  379. sudo -H -u "${owner}" -i
  380. $DIFF_CMD "${dst}" "${template_file}"
  381. echo
  382. echo -e "// ${_BBlack}did you edit file ...${_creset}"
  383. echo -en "// ${_Red}${dst}${_creset}"
  384. if ask_yn "//${_BBlack}... to your needs?${_creset}"; then
  385. break
  386. fi
  387. ;;
  388. "diff files")
  389. $DIFF_CMD "${dst}" "${template_file}" | prefix_stdout
  390. esac
  391. done
  392. }
  393. service_is_available() {
  394. # usage: service_is_available <URL>
  395. local URL="$1"
  396. if [[ -z $URL ]]; then
  397. err_msg "service_is_available: missing arguments"
  398. return 42
  399. fi
  400. http_code=$(curl -H 'Cache-Control: no-cache' \
  401. --silent -o /dev/null --head --write-out '%{http_code}' --insecure \
  402. "${URL}")
  403. exit_val=$?
  404. if [[ $exit_val = 0 ]]; then
  405. info_msg "got $http_code from ${URL}"
  406. fi
  407. case "$http_code" in
  408. 404|410|423) exit_val=$http_code;;
  409. esac
  410. return "$exit_val"
  411. }
  412. # golang
  413. # ------
  414. go_is_available() {
  415. # usage: go_is_available $SERVICE_USER && echo "go is installed!"
  416. sudo -i -u "${1}" which go &>/dev/null
  417. }
  418. install_go() {
  419. # usage: install_go "${GO_PKG_URL}" "${GO_TAR}" "${SERVICE_USER}"
  420. local _svcpr=" ${_Yellow}|${3}|${_creset} "
  421. rst_title "Install Go in user's HOME" section
  422. rst_para "download and install go binary .."
  423. cache_download "${1}" "${2}"
  424. tee_stderr 0.1 <<EOF | sudo -i -u "${3}" | prefix_stdout "$_svcpr"
  425. echo \$PATH
  426. echo \$GOPATH
  427. mkdir -p \$HOME/local
  428. rm -rf \$HOME/local/go
  429. tar -C \$HOME/local -xzf ${CACHE}/${2}
  430. EOF
  431. sudo -i -u "${3}" <<EOF | prefix_stdout
  432. ! which go >/dev/null && echo "ERROR - Go Installation not found in PATH!?!"
  433. which go >/dev/null && go version && echo "congratulations -- Go installation OK :)"
  434. EOF
  435. }
  436. # system accounts
  437. # ---------------
  438. service_account_is_available() {
  439. # usage: service_account_is_available "$SERVICE_USER" && echo "OK"
  440. sudo -i -u "$1" echo \$HOME &>/dev/null
  441. }
  442. drop_service_account() {
  443. # usage: drop_service_account "${SERVICE_USER}"
  444. rst_title "Drop ${1} HOME" section
  445. if ask_yn "Do you really want to drop ${1} home folder?"; then
  446. userdel -r -f "${1}" 2>&1 | prefix_stdout
  447. else
  448. rst_para "Leave HOME folder $(du -sh "${1}") unchanged."
  449. fi
  450. }
  451. interactive_shell(){
  452. # usage: interactive_shell "${SERVICE_USER}"
  453. echo -e "// exit with [${_BCyan}CTRL-D${_creset}]"
  454. sudo -H -u "${1}" -i
  455. }
  456. # systemd
  457. # -------
  458. SYSTEMD_UNITS="${SYSTEMD_UNITS:-/lib/systemd/system}"
  459. systemd_install_service() {
  460. # usage: systemd_install_service "${SERVICE_NAME}" "${SERVICE_SYSTEMD_UNIT}"
  461. rst_title "Install System-D Unit ${1}" section
  462. echo
  463. install_template "${2}" root root 644
  464. wait_key
  465. systemd_activate_service "${1}"
  466. }
  467. systemd_remove_service() {
  468. # usage: systemd_remove_service "${SERVICE_NAME}" "${SERVICE_SYSTEMD_UNIT}"
  469. if ! ask_yn "Do you really want to deinstall systemd unit ${1}?"; then
  470. return 42
  471. fi
  472. systemd_deactivate_service "${1}"
  473. rm "${2}" 2>&1 | prefix_stdout
  474. }
  475. systemd_activate_service() {
  476. # usage: systemd_activate_service "${SERVICE_NAME}"
  477. rst_title "Activate ${1} (service)" section
  478. echo
  479. tee_stderr <<EOF | bash 2>&1
  480. systemctl enable ${1}.service
  481. systemctl restart ${1}.service
  482. EOF
  483. tee_stderr <<EOF | bash 2>&1
  484. systemctl status --no-pager ${1}.service
  485. EOF
  486. }
  487. systemd_deactivate_service() {
  488. # usage: systemd_deactivate_service "${SERVICE_NAME}"
  489. rst_title "De-Activate ${1} (service)" section
  490. echo
  491. tee_stderr <<EOF | bash 2>&1 | prefix_stdout
  492. systemctl stop ${1}.service
  493. systemctl disable ${1}.service
  494. EOF
  495. }
  496. systemd_restart_service() {
  497. # usage: systemd_restart_service "${SERVICE_NAME}"
  498. rst_title "Restart ${1} (service)" section
  499. echo
  500. tee_stderr <<EOF | bash 2>&1
  501. systemctl restart ${1}.service
  502. EOF
  503. tee_stderr <<EOF | bash 2>&1
  504. systemctl status --no-pager ${1}.service
  505. EOF
  506. }
  507. # Apache
  508. # ------
  509. apache_distro_setup() {
  510. # shellcheck disable=SC2034
  511. case $DIST_ID-$DIST_VERS in
  512. ubuntu-*|debian-*)
  513. # debian uses the /etc/apache2 path, while other distros use
  514. # the apache default at /etc/httpd
  515. APACHE_SITES_AVAILABLE="/etc/apache2/sites-available"
  516. APACHE_SITES_ENABLED="/etc/apache2/sites-enabled"
  517. APACHE_MODULES="/usr/lib/apache2/modules"
  518. APACHE_PACKAGES="apache2"
  519. ;;
  520. arch-*)
  521. APACHE_SITES_AVAILABLE="/etc/httpd/sites-available"
  522. APACHE_SITES_ENABLED="/etc/httpd/sites-enabled"
  523. APACHE_MODULES="modules"
  524. APACHE_PACKAGES="apache"
  525. ;;
  526. fedora-*)
  527. APACHE_SITES_AVAILABLE="/etc/httpd/sites-available"
  528. APACHE_SITES_ENABLED="/etc/httpd/sites-enabled"
  529. APACHE_MODULES="modules"
  530. APACHE_PACKAGES="httpd"
  531. ;;
  532. *)
  533. err_msg "$DIST_ID-$DIST_VERS: apache not yet implemented"
  534. ;;
  535. esac
  536. }
  537. apache_distro_setup
  538. install_apache(){
  539. info_msg "installing apache ..."
  540. pkg_install "$APACHE_PACKAGES"
  541. case $DIST_ID-$DIST_VERS in
  542. arch-*|fedora-*)
  543. if ! grep "IncludeOptional sites-enabled" "/etc/httpd/conf/httpd.conf"; then
  544. echo "IncludeOptional sites-enabled/*.conf" >> "/etc/httpd/conf/httpd.conf"
  545. fi
  546. systemctl enable httpd
  547. systemctl start httpd
  548. ;;
  549. esac
  550. }
  551. apache_is_installed() {
  552. case $DIST_ID-$DIST_VERS in
  553. ubuntu-*|debian-*) (command -v apachectl) &>/dev/null;;
  554. arch-*) (command -v httpd) &>/dev/null;;
  555. fedora-*) (command -v httpd) &>/dev/null;;
  556. esac
  557. }
  558. apache_reload() {
  559. info_msg "reload apache .."
  560. echo
  561. case $DIST_ID-$DIST_VERS in
  562. ubuntu-*|debian-*)
  563. sudo -H apachectl configtest
  564. sudo -H systemctl force-reload apache2
  565. ;;
  566. arch-*| fedora-*)
  567. sudo -H httpd -t
  568. sudo -H systemctl force-reload httpd
  569. ;;
  570. esac
  571. }
  572. apache_install_site() {
  573. # usage: apache_install_site [<template option> ...] <mysite.conf>
  574. #
  575. # <template option>: see install_template
  576. local template_opts=()
  577. local pos_args=("$0")
  578. for i in "$@"; do
  579. case $i in
  580. -*) template_opts+=("$i");;
  581. *) pos_args+=("$i");;
  582. esac
  583. done
  584. install_template "${template_opts[@]}" \
  585. "${APACHE_SITES_AVAILABLE}/${pos_args[1]}" \
  586. root root 644
  587. apache_enable_site "${pos_args[1]}"
  588. info_msg "installed apache site: ${pos_args[1]}"
  589. }
  590. apache_remove_site() {
  591. # usage: apache_remove_site <mysite.conf>
  592. info_msg "remove apache site: $1"
  593. apache_dissable_site "$1"
  594. rm -f "${APACHE_SITES_AVAILABLE}/$1"
  595. }
  596. apache_enable_site() {
  597. # usage: apache_enable_site <mysite.conf>
  598. local CONF="$1"
  599. info_msg "enable apache site: ${CONF}"
  600. case $DIST_ID-$DIST_VERS in
  601. ubuntu-*|debian-*)
  602. sudo -H a2ensite -q "${CONF}"
  603. ;;
  604. arch-*)
  605. mkdir -p "${APACHE_SITES_ENABLED}"
  606. rm -f "${APACHE_SITES_ENABLED}/${CONF}"
  607. ln -s "${APACHE_SITES_AVAILABLE}/${CONF}" "${APACHE_SITES_ENABLED}/${CONF}"
  608. ;;
  609. fedora-*)
  610. mkdir -p "${APACHE_SITES_ENABLED}"
  611. rm -f "${APACHE_SITES_ENABLED}/${CONF}"
  612. ln -s "${APACHE_SITES_AVAILABLE}/${CONF}" "${APACHE_SITES_ENABLED}/${CONF}"
  613. ;;
  614. esac
  615. apache_reload
  616. }
  617. apache_dissable_site() {
  618. # usage: apache_disable_site <mysite.conf>
  619. local CONF="$1"
  620. info_msg "disable apache site: ${CONF}"
  621. case $DIST_ID-$DIST_VERS in
  622. ubuntu-*|debian-*)
  623. sudo -H a2dissite -q "${CONF}"
  624. ;;
  625. arch-*)
  626. mkdir -p "${APACHE_SITES_ENABLED}"
  627. rm -f "${APACHE_SITES_ENABLED}/${CONF}"
  628. ln -s "${APACHE_SITES_AVAILABLE}/${CONF}" "${APACHE_SITES_ENABLED}/${CONF}"
  629. ;;
  630. fedora-*)
  631. mkdir -p "${APACHE_SITES_ENABLED}"
  632. rm -f "${APACHE_SITES_ENABLED}/${CONF}"
  633. ln -s "${APACHE_SITES_AVAILABLE}/${CONF}" "${APACHE_SITES_ENABLED}/${CONF}"
  634. ;;
  635. esac
  636. }
  637. # uWSGI
  638. # -----
  639. uWSGI_SETUP="${uWSGI_SETUP:=/etc/uwsgi}"
  640. uWSGI_USER=
  641. uWSGI_GROUP=
  642. # How distros manage uWSGI apps is very different. From uWSGI POV read:
  643. # - https://uwsgi-docs.readthedocs.io/en/latest/Management.html
  644. uWSGI_distro_setup() {
  645. case $DIST_ID-$DIST_VERS in
  646. ubuntu-*|debian-*)
  647. # init.d --> /usr/share/doc/uwsgi/README.Debian.gz
  648. # For uWSGI debian uses the LSB init process, this might be changed
  649. # one day, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=833067
  650. uWSGI_APPS_AVAILABLE="${uWSGI_SETUP}/apps-available"
  651. uWSGI_APPS_ENABLED="${uWSGI_SETUP}/apps-enabled"
  652. ;;
  653. arch-*)
  654. # systemd --> /usr/lib/systemd/system/uwsgi@.service
  655. # For uWSGI archlinux uses systemd template units, see
  656. # - http://0pointer.de/blog/projects/instances.html
  657. # - https://uwsgi-docs.readthedocs.io/en/latest/Systemd.html#one-service-per-app-in-systemd
  658. uWSGI_APPS_AVAILABLE="${uWSGI_SETUP}/apps-archlinux"
  659. uWSGI_APPS_ENABLED="${uWSGI_SETUP}"
  660. ;;
  661. fedora-*)
  662. # systemd --> /usr/lib/systemd/system/uwsgi.service
  663. # The unit file starts uWSGI in emperor mode (/etc/uwsgi.ini), see
  664. # - https://uwsgi-docs.readthedocs.io/en/latest/Emperor.html
  665. uWSGI_APPS_AVAILABLE="${uWSGI_SETUP}/apps-available"
  666. uWSGI_APPS_ENABLED="${uWSGI_SETUP}.d"
  667. uWSGI_USER="uwsgi"
  668. uWSGI_GROUP="uwsgi"
  669. ;;
  670. *)
  671. err_msg "$DIST_ID-$DIST_VERS: uWSGI not yet implemented"
  672. ;;
  673. esac
  674. }
  675. uWSGI_distro_setup
  676. uWSGI_restart() {
  677. # usage: uWSGI_restart() <myapp.ini>
  678. local CONF="$1"
  679. if [[ -z $CONF ]]; then
  680. err_msg "uWSGI_restart: missing arguments"
  681. return 42
  682. fi
  683. info_msg "restart uWSGI service"
  684. case $DIST_ID-$DIST_VERS in
  685. ubuntu-*|debian-*)
  686. # the 'service' method seems broken in that way, that it (re-)starts
  687. # the whole uwsgi process.
  688. service uwsgi restart "${CONF%.*}"
  689. ;;
  690. arch-*)
  691. # restart systemd template instance
  692. if uWSGI_app_available "${CONF}"; then
  693. systemctl restart "uwsgi@${CONF%.*}"
  694. else
  695. info_msg "in systemd template mode: ${CONF} not installed (nothing to restart)"
  696. fi
  697. ;;
  698. fedora-*)
  699. # in emperor mode, just touch the file to restart
  700. if uWSGI_app_enabled "${CONF}"; then
  701. touch "${uWSGI_APPS_ENABLED}/${CONF}"
  702. else
  703. info_msg "in uWSGI emperor mode: ${CONF} not installed (nothing to restart)"
  704. fi
  705. ;;
  706. *)
  707. err_msg "$DIST_ID-$DIST_VERS: uWSGI not yet implemented"
  708. return 42
  709. ;;
  710. esac
  711. }
  712. uWSGI_app_available() {
  713. # usage: uWSGI_app_available <myapp.ini>
  714. local CONF="$1"
  715. if [[ -z $CONF ]]; then
  716. err_msg "uWSGI_app_available: missing arguments"
  717. return 42
  718. fi
  719. [[ -f "${uWSGI_APPS_AVAILABLE}/${CONF}" ]]
  720. }
  721. uWSGI_install_app() {
  722. # usage: uWSGI_install_app [<template option> ...] <myapp.ini>
  723. #
  724. # <template option>: see install_template
  725. local pos_args=("$0")
  726. for i in "$@"; do
  727. case $i in
  728. -*) template_opts+=("$i");;
  729. *) pos_args+=("$i");;
  730. esac
  731. done
  732. mkdir -p "${uWSGI_APPS_AVAILABLE}"
  733. install_template "${template_opts[@]}" \
  734. "${uWSGI_APPS_AVAILABLE}/${pos_args[1]}" \
  735. root root 644
  736. uWSGI_enable_app "${pos_args[1]}"
  737. uWSGI_restart "${pos_args[1]}"
  738. info_msg "uWSGI app: ${pos_args[1]} is installed"
  739. }
  740. uWSGI_remove_app() {
  741. # usage: uWSGI_remove_app <myapp.ini>
  742. local CONF="$1"
  743. info_msg "remove uWSGI app: ${CONF}"
  744. uWSGI_disable_app "${CONF}"
  745. uWSGI_restart "${CONF}"
  746. rm -f "${uWSGI_APPS_AVAILABLE}/${CONF}"
  747. }
  748. uWSGI_app_enabled() {
  749. # usage: uWSGI_app_enabled <myapp.ini>
  750. local CONF="$1"
  751. local exit_val=0
  752. if [[ -z $CONF ]]; then
  753. err_msg "uWSGI_app_enabled: missing arguments"
  754. return 42
  755. fi
  756. case $DIST_ID-$DIST_VERS in
  757. ubuntu-*|debian-*)
  758. [[ -f "${uWSGI_APPS_ENABLED}/${CONF}" ]]
  759. exit_val=$?
  760. ;;
  761. arch-*)
  762. systemctl -q is-enabled "uwsgi@${CONF%.*}"
  763. exit_val=$?
  764. ;;
  765. fedora-*)
  766. [[ -f "${uWSGI_APPS_ENABLED}/${CONF}" ]]
  767. exit_val=$?
  768. ;;
  769. *)
  770. # FIXME
  771. err_msg "$DIST_ID-$DIST_VERS: uWSGI not yet implemented"
  772. exit_val=1
  773. ;;
  774. esac
  775. return $exit_val
  776. }
  777. # shellcheck disable=SC2164
  778. uWSGI_enable_app() {
  779. # usage: uWSGI_enable_app <myapp.ini>
  780. local CONF="$1"
  781. if [[ -z $CONF ]]; then
  782. err_msg "uWSGI_enable_app: missing arguments"
  783. return 42
  784. fi
  785. case $DIST_ID-$DIST_VERS in
  786. ubuntu-*|debian-*)
  787. mkdir -p "${uWSGI_APPS_ENABLED}"
  788. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  789. ln -s "${uWSGI_APPS_AVAILABLE}/${CONF}" "${uWSGI_APPS_ENABLED}/${CONF}"
  790. info_msg "enabled uWSGI app: ${CONF} (restart required)"
  791. ;;
  792. arch-*)
  793. mkdir -p "${uWSGI_APPS_ENABLED}"
  794. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  795. ln -s "${uWSGI_APPS_AVAILABLE}/${CONF}" "${uWSGI_APPS_ENABLED}/${CONF}"
  796. systemctl enable "uwsgi@${CONF%.*}"
  797. info_msg "enabled uWSGI app: ${CONF} (restart required)"
  798. ;;
  799. fedora-*)
  800. mkdir -p "${uWSGI_APPS_ENABLED}"
  801. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  802. ln -s "${uWSGI_APPS_AVAILABLE}/${CONF}" "${uWSGI_APPS_ENABLED}/${CONF}"
  803. chown "${uWSGI_USER}:${uWSGI_GROUP}" "${uWSGI_APPS_ENABLED}/${CONF}"
  804. info_msg "enabled uWSGI app: ${CONF}"
  805. ;;
  806. *)
  807. # FIXME
  808. err_msg "$DIST_ID-$DIST_VERS: uWSGI not yet implemented"
  809. ;;
  810. esac
  811. }
  812. uWSGI_disable_app() {
  813. # usage: uWSGI_disable_app <myapp.ini>
  814. local CONF="$1"
  815. if [[ -z $CONF ]]; then
  816. err_msg "uWSGI_disable_app: missing arguments"
  817. return 42
  818. fi
  819. case $DIST_ID-$DIST_VERS in
  820. ubuntu-*|debian-*)
  821. service uwsgi stop "${CONF%.*}"
  822. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  823. info_msg "disabled uWSGI app: ${CONF} (restart uWSGI required)"
  824. ;;
  825. arch-*)
  826. systemctl stop "uwsgi@${CONF%.*}"
  827. systemctl disable "uwsgi@${CONF%.*}"
  828. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  829. ;;
  830. fedora-*)
  831. # in emperor mode, just remove the app.ini file
  832. rm -f "${uWSGI_APPS_ENABLED}/${CONF}"
  833. ;;
  834. *)
  835. # FIXME
  836. err_msg "$DIST_ID-$DIST_VERS: uWSGI not yet implemented"
  837. ;;
  838. esac
  839. }
  840. # distro's package manager
  841. # ------------------------
  842. pkg_install() {
  843. # usage: TITEL='install foobar' pkg_install foopkg barpkg
  844. rst_title "${TITLE:-installation of packages}" section
  845. echo -e "\npackage(s)::\n"
  846. # shellcheck disable=SC2068
  847. echo " " $@ | $FMT
  848. if ! ask_yn "Should packages be installed?" Yn 30; then
  849. return 42
  850. fi
  851. case $DIST_ID in
  852. ubuntu|debian)
  853. # shellcheck disable=SC2068
  854. apt-get install -m -y $@
  855. ;;
  856. arch)
  857. # shellcheck disable=SC2068
  858. pacman -Sy --noconfirm $@
  859. ;;
  860. fedora)
  861. # shellcheck disable=SC2068
  862. dnf install -y $@
  863. ;;
  864. esac
  865. }
  866. pkg_remove() {
  867. # usage: TITEL='remove foobar' pkg_remove foopkg barpkg
  868. rst_title "${TITLE:-remove packages}" section
  869. echo -e "\npackage(s)::\n"
  870. # shellcheck disable=SC2068
  871. echo " " $@ | $FMT
  872. if ! ask_yn "Should packages be removed (purge)?" Yn 30; then
  873. return 42
  874. fi
  875. case $DIST_ID in
  876. ubuntu|debian)
  877. # shellcheck disable=SC2068
  878. apt-get purge --autoremove --ignore-missing -y $@
  879. ;;
  880. arch)
  881. # shellcheck disable=SC2068
  882. pacman -R --noconfirm $@
  883. ;;
  884. fedora)
  885. # shellcheck disable=SC2068
  886. dnf remove -y $@
  887. ;;
  888. esac
  889. }
  890. pkg_is_installed() {
  891. # usage: pkg_is_install foopkg || pkg_install foopkg
  892. case $DIST_ID in
  893. ubuntu|debian)
  894. dpkg -l "$1" &> /dev/null
  895. return $?
  896. ;;
  897. arch)
  898. pacman -Qsq "$1" &> /dev/null
  899. return $?
  900. ;;
  901. fedora)
  902. dnf list -q --installed "$1" &> /dev/null
  903. return $?
  904. ;;
  905. esac
  906. }
  907. # git tooling
  908. # -----------
  909. # shellcheck disable=SC2164
  910. git_clone() {
  911. # usage:
  912. #
  913. # git_clone <url> <name> [<branch> [<user>]]
  914. # git_clone <url> <path> [<branch> [<user>]]
  915. #
  916. # First form uses $CACHE/<name> as destination folder, second form clones
  917. # into <path>. If repository is allready cloned, pull from <branch> and
  918. # update working tree (if needed, the caller has to stash local changes).
  919. #
  920. # git clone https://github.com/asciimoo/searx searx-src origin/master searxlogin
  921. #
  922. local url="$1"
  923. local dest="$2"
  924. local branch="$3"
  925. local user="$4"
  926. local bash_cmd="bash"
  927. local remote="origin"
  928. if [[ ! "${dest:0:1}" = "/" ]]; then
  929. dest="$CACHE/$dest"
  930. fi
  931. [[ -z $branch ]] && branch=master
  932. [[ -z $user ]] && [[ -n "${SUDO_USER}" ]] && user="${SUDO_USER}"
  933. [[ -n $user ]] && bash_cmd="sudo -H -u $user -i"
  934. if [[ -d "${dest}" ]] ; then
  935. info_msg "already cloned: $dest"
  936. tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " ${_Yellow}|$user|${_creset} "
  937. cd "${dest}"
  938. git checkout -m -B "$branch" --track "$remote/$branch"
  939. git pull --all
  940. EOF
  941. else
  942. info_msg "clone into: $dest"
  943. tee_stderr 0.1 <<EOF | $bash_cmd 2>&1 | prefix_stdout " ${_Yellow}|$user|${_creset} "
  944. mkdir -p "$(dirname "$dest")"
  945. cd "$(dirname "$dest")"
  946. git clone --branch "$branch" --origin "$remote" "$url" "$(basename "$dest")"
  947. EOF
  948. fi
  949. }
  950. # containers
  951. # ----------
  952. in_container() {
  953. # Test if shell runs in a container.
  954. #
  955. # usage: in_container && echo "process running inside a LXC container"
  956. # in_container || echo "process is not running inside a LXC container"
  957. #
  958. # sudo_or_exit
  959. # hint: Reads init process environment, therefore root access is required!
  960. # to be safe, take a look at the environment of process 1 (/sbin/init)
  961. # grep -qa 'container=lxc' /proc/1/environ
  962. # see lxc_init_container_env
  963. [[ -f /.lxcenv ]]
  964. }
  965. LXC_ENV_FOLDER=
  966. if in_container; then
  967. # shellcheck disable=SC2034
  968. LXC_ENV_FOLDER="lxc/$(hostname)/"
  969. fi
  970. lxc_init_container_env() {
  971. # usage: lxc_init_container_env <name>
  972. # Create a /.lxcenv file in the root folder. Call this once after the
  973. # container is inital started and before installing any boilerplate stuff.
  974. info_msg "create /.lxcenv in container $1"
  975. cat <<EOF | lxc exec "${1}" -- bash | prefix_stdout "[${_BBlue}${1}${_creset}] "
  976. touch "/.lxcenv"
  977. ls -l "/.lxcenv"
  978. EOF
  979. }
  980. # apt packages
  981. LXC_BASE_PACKAGES_debian="bash git build-essential python3 virtualenv"
  982. # pacman packages
  983. LXC_BASE_PACKAGES_arch="bash git base-devel python python-virtualenv"
  984. # dnf packages
  985. LXC_BASE_PACKAGES_fedora="bash git @development-tools python virtualenv"
  986. case $DIST_ID in
  987. ubuntu|debian) LXC_BASE_PACKAGES="${LXC_BASE_PACKAGES_debian}" ;;
  988. arch) LXC_BASE_PACKAGES="${LXC_BASE_PACKAGES_arch}" ;;
  989. fedora) LXC_BASE_PACKAGES="${LXC_BASE_PACKAGES_fedora}" ;;
  990. *) err_msg "$DIST_ID-$DIST_VERS: pkg_install LXC_BASE_PACKAGES not yet implemented" ;;
  991. esac
  992. lxc_install_base_packages() {
  993. info_msg "install LXC_BASE_PACKAGES in container $1"
  994. pkg_install "${LXC_BASE_PACKAGES}"
  995. }
  996. lxc_image_copy() {
  997. # usage: lxc_copy_image <remote image> <local image>
  998. #
  999. # lxc_copy_image "images:ubuntu/19.10" "ubu1910"
  1000. if lxc_image_exists "local:${LXC_SUITE[i+1]}"; then
  1001. info_msg "image ${LXC_SUITE[i]} already copied --> ${LXC_SUITE[i+1]}"
  1002. else
  1003. info_msg "copy image locally ${LXC_SUITE[i]} --> ${LXC_SUITE[i+1]}"
  1004. lxc image copy "${LXC_SUITE[i]}" local: \
  1005. --alias "${LXC_SUITE[i+1]}" | prefix_stdout
  1006. fi
  1007. }
  1008. lxc_init_container() {
  1009. # usage: lxc_init_container <image name> <container name>
  1010. local image_name="$1"
  1011. local container_name="$2"
  1012. if lxc info "${container_name}" &>/dev/null; then
  1013. info_msg "container '${container_name}' already exists"
  1014. else
  1015. info_msg "create container instance: ${container_name}"
  1016. lxc init "local:${image_name}" "${container_name}"
  1017. fi
  1018. }
  1019. lxc_exists(){
  1020. # usage: lxc_exists <name> || echo "container <name> does not exists"
  1021. lxc info "$1" &>/dev/null
  1022. }
  1023. lxc_image_exists(){
  1024. # usage: lxc_image_exists <alias> || echo "image <alias> does locally not exists"
  1025. lxc image info "local:$1" &>/dev/null
  1026. }
  1027. lxc_delete_container() {
  1028. # usage: lxc_delete_container <container-name>
  1029. if lxc info "$1" &>/dev/null; then
  1030. info_msg "stop & delete instance ${_BBlue}${1}${_creset}"
  1031. lxc stop "$1" &>/dev/null
  1032. lxc delete "$1" | prefix_stdout
  1033. else
  1034. warn_msg "instance '$1' does not exist / can't delete :o"
  1035. fi
  1036. }
  1037. lxc_delete_local_image() {
  1038. # usage: lxc_delete_local_image <container-name>
  1039. info_msg "delete image 'local:$i'"
  1040. lxc image delete "local:$i"
  1041. }
  1042. # IP
  1043. # --
  1044. global_IPs(){
  1045. # usage: global_IPS
  1046. #
  1047. # print list of host's SCOPE global addresses and adapters e.g::
  1048. #
  1049. # $ global_IPs
  1050. # enp4s0|192.168.1.127
  1051. # lxdbr0|10.246.86.1
  1052. # lxdbr0|fd42:8c58:2cd:b73f::1
  1053. ip -o addr show | sed -nr 's/[0-9]*:\s*([a-z0-9]*).*inet[6]?\s*([a-z0-9.:]*).*scope global.*/\1|\2/p'
  1054. }