filtron.sh 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. #!/usr/bin/env bash
  2. # SPDX-License-Identifier: AGPL-3.0-or-later
  3. # shellcheck disable=SC2001
  4. # shellcheck source=utils/lib.sh
  5. source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
  6. # shellcheck source=utils/lib_install.sh
  7. source "${REPO_ROOT}/utils/lib_install.sh"
  8. # ----------------------------------------------------------------------------
  9. # config
  10. # ----------------------------------------------------------------------------
  11. PUBLIC_HOST="${PUBLIC_HOST:-$(echo "$PUBLIC_URL" | sed -e 's/[^/]*\/\/\([^@]*@\)\?\([^:/]*\).*/\2/')}"
  12. FILTRON_URL_PATH="${FILTRON_URL_PATH:-$(echo "${PUBLIC_URL}" \
  13. | sed -e 's,^.*://[^/]*\(/.*\),\1,g')}"
  14. [[ "${FILTRON_URL_PATH}" == "${PUBLIC_URL}" ]] && FILTRON_URL_PATH=/
  15. FILTRON_ETC="/etc/filtron"
  16. FILTRON_RULES="$FILTRON_ETC/rules.json"
  17. FILTRON_RULES_TEMPLATE="${FILTRON_RULES_TEMPLATE:-${REPO_ROOT}/utils/templates/etc/filtron/rules.json}"
  18. FILTRON_API="${FILTRON_API:-127.0.0.1:4005}"
  19. FILTRON_LISTEN="${FILTRON_LISTEN:-127.0.0.1:4004}"
  20. # The filtron target is the SearXNG installation, listenning on server.port at
  21. # server.bind_address. The default of FILTRON_TARGET is taken from the YAML
  22. # configuration, do not change this value without reinstalling the entire
  23. # SearXNG suite including filtron & morty.
  24. FILTRON_TARGET="${SEARXNG_BIND_ADDRESS}:${SEARXNG_PORT}"
  25. SERVICE_NAME="filtron"
  26. SERVICE_USER="${SERVICE_USER:-${SERVICE_NAME}}"
  27. SERVICE_HOME_BASE="${SERVICE_HOME_BASE:-/usr/local}"
  28. SERVICE_HOME="${SERVICE_HOME_BASE}/${SERVICE_USER}"
  29. SERVICE_SYSTEMD_UNIT="${SYSTEMD_UNITS}/${SERVICE_NAME}.service"
  30. # shellcheck disable=SC2034
  31. SERVICE_GROUP="${SERVICE_USER}"
  32. # shellcheck disable=SC2034
  33. SERVICE_GROUP="${SERVICE_USER}"
  34. GO_ENV="${SERVICE_HOME}/.go_env"
  35. GO_VERSION="go1.17.2"
  36. GO_PKG_URL="https://golang.org/dl/${GO_VERSION}.linux-amd64.tar.gz"
  37. GO_TAR=$(basename "$GO_PKG_URL")
  38. APACHE_FILTRON_SITE="searxng.conf"
  39. NGINX_FILTRON_SITE="searxng.conf"
  40. # shellcheck disable=SC2034
  41. CONFIG_FILES=(
  42. "${FILTRON_RULES}"
  43. "${SERVICE_SYSTEMD_UNIT}"
  44. )
  45. # ----------------------------------------------------------------------------
  46. usage() {
  47. # ----------------------------------------------------------------------------
  48. # shellcheck disable=SC1117
  49. cat <<EOF
  50. usage::
  51. $(basename "$0") shell
  52. $(basename "$0") install [all|user|rules]
  53. $(basename "$0") update [filtron]
  54. $(basename "$0") remove [all]
  55. $(basename "$0") activate [service]
  56. $(basename "$0") deactivate [service]
  57. $(basename "$0") inspect [service]
  58. $(basename "$0") option [debug-on|debug-off]
  59. $(basename "$0") apache [install|remove]
  60. $(basename "$0") nginx [install|remove]
  61. shell
  62. start interactive shell from user ${SERVICE_USER}
  63. install / remove
  64. :all: complete setup of filtron service
  65. :check: check the filtron installation
  66. :user: add/remove service user '$SERVICE_USER' ($SERVICE_HOME)
  67. :rules: reinstall filtron rules $FILTRON_RULES
  68. update filtron
  69. Update filtron installation ($SERVICE_HOME)
  70. activate service
  71. activate and start service daemon (systemd unit)
  72. deactivate service
  73. stop and deactivate service daemon (systemd unit)
  74. inspect service
  75. show service status and log
  76. option
  77. set one of the available options
  78. apache (${PUBLIC_URL})
  79. :install: apache site with a reverse proxy (ProxyPass)
  80. :remove: apache site ${APACHE_FILTRON_SITE}
  81. nginx (${PUBLIC_URL})
  82. :install: nginx site with a reverse proxy (ProxyPass)
  83. :remove: nginx site ${NGINX_FILTRON_SITE}
  84. filtron rules: ${FILTRON_RULES_TEMPLATE}
  85. ---- sourced ${DOT_CONFIG} :
  86. SERVICE_USER : ${SERVICE_USER}
  87. SERVICE_HOME : ${SERVICE_HOME}
  88. FILTRON_TARGET : ${FILTRON_TARGET}
  89. FILTRON_API : ${FILTRON_API}
  90. FILTRON_LISTEN : ${FILTRON_LISTEN}
  91. FILTRON_URL_PATH : ${FILTRON_URL_PATH}
  92. EOF
  93. install_log_searx_instance
  94. [[ -n ${1} ]] && err_msg "$1"
  95. }
  96. main() {
  97. required_commands \
  98. sudo install git wget curl \
  99. || exit
  100. local _usage="unknown or missing $1 command $2"
  101. case $1 in
  102. --getenv) var="$2"; echo "${!var}"; exit 0;;
  103. -h|--help) usage; exit 0;;
  104. shell)
  105. sudo_or_exit
  106. interactive_shell "${SERVICE_USER}"
  107. ;;
  108. inspect)
  109. case $2 in
  110. service)
  111. sudo_or_exit
  112. inspect_service
  113. ;;
  114. *) usage "$_usage"; exit 42;;
  115. esac ;;
  116. install)
  117. rst_title "$SERVICE_NAME" part
  118. sudo_or_exit
  119. case $2 in
  120. check)
  121. rst_title "Check filtron installation" part
  122. install_check
  123. ;;
  124. all) install_all ;;
  125. user) assert_user ;;
  126. rules)
  127. install_rules
  128. systemd_restart_service "${SERVICE_NAME}"
  129. ;;
  130. *) usage "$_usage"; exit 42;;
  131. esac ;;
  132. update)
  133. sudo_or_exit
  134. case $2 in
  135. filtron) update_filtron ;;
  136. *) usage "$_usage"; exit 42;;
  137. esac ;;
  138. remove)
  139. sudo_or_exit
  140. case $2 in
  141. all) remove_all;;
  142. user) drop_service_account "${SERVICE_USER}" ;;
  143. *) usage "$_usage"; exit 42;;
  144. esac ;;
  145. activate)
  146. sudo_or_exit
  147. case $2 in
  148. service) systemd_activate_service "${SERVICE_NAME}" ;;
  149. *) usage "$_usage"; exit 42;;
  150. esac ;;
  151. deactivate)
  152. sudo_or_exit
  153. case $2 in
  154. service) systemd_deactivate_service "${SERVICE_NAME}" ;;
  155. *) usage "$_usage"; exit 42;;
  156. esac ;;
  157. apache)
  158. sudo_or_exit
  159. case $2 in
  160. install) install_apache_site ;;
  161. remove) remove_apache_site ;;
  162. *) usage "$_usage"; exit 42;;
  163. esac ;;
  164. nginx)
  165. sudo_or_exit
  166. case $2 in
  167. install) install_nginx_site ;;
  168. remove) remove_nginx_site ;;
  169. *) usage "$_usage"; exit 42;;
  170. esac ;;
  171. option)
  172. sudo_or_exit
  173. case $2 in
  174. debug-on) echo; enable_debug ;;
  175. debug-off) echo; disable_debug ;;
  176. *) usage "$_usage"; exit 42;;
  177. esac ;;
  178. doc) rst-doc ;;
  179. *) usage "unknown or missing command $1"; exit 42;;
  180. esac
  181. }
  182. install_all() {
  183. rst_title "Install $SERVICE_NAME (service)"
  184. assert_user
  185. wait_key
  186. install_go "${GO_PKG_URL}" "${GO_TAR}" "${SERVICE_USER}"
  187. wait_key
  188. install_filtron
  189. install_rules
  190. wait_key
  191. systemd_install_service "${SERVICE_NAME}" "${SERVICE_SYSTEMD_UNIT}"
  192. wait_key
  193. echo
  194. if ! service_is_available "http://${FILTRON_LISTEN}" ; then
  195. err_msg "Filtron is not listening on: http://${FILTRON_LISTEN}"
  196. fi
  197. if apache_is_installed; then
  198. info_msg "Apache is installed on this host."
  199. if ask_yn "Do you want to install a reverse proxy (ProxyPass)" Yn; then
  200. install_apache_site
  201. fi
  202. elif nginx_is_installed; then
  203. info_msg "nginx is installed on this host."
  204. if ask_yn "Do you want to install a reverse proxy (ProxyPass)" Yn; then
  205. install_nginx_site
  206. fi
  207. fi
  208. if ask_yn "Do you want to inspect the installation?" Ny; then
  209. inspect_service
  210. fi
  211. }
  212. install_check() {
  213. if service_account_is_available "$SERVICE_USER"; then
  214. info_msg "service account $SERVICE_USER available."
  215. else
  216. err_msg "service account $SERVICE_USER not available!"
  217. fi
  218. if go_is_available "$SERVICE_USER"; then
  219. info_msg "~$SERVICE_USER: go is installed"
  220. else
  221. err_msg "~$SERVICE_USER: go is not installed"
  222. fi
  223. if filtron_is_installed; then
  224. info_msg "~$SERVICE_USER: filtron app is installed"
  225. else
  226. err_msg "~$SERVICE_USER: filtron app is not installed!"
  227. fi
  228. if ! service_is_available "http://${FILTRON_API}"; then
  229. err_msg "API not available at: http://${FILTRON_API}"
  230. fi
  231. if ! service_is_available "http://${FILTRON_LISTEN}" ; then
  232. err_msg "Filtron is not listening on: http://${FILTRON_LISTEN}"
  233. fi
  234. if service_is_available "http://${FILTRON_TARGET}" ; then
  235. info_msg "Filtron's target is available at: http://${FILTRON_TARGET}"
  236. fi
  237. if ! service_is_available "${PUBLIC_URL}"; then
  238. warn_msg "Public service at ${PUBLIC_URL} is not available!"
  239. if ! in_container; then
  240. warn_msg "Check if public name is correct and routed or use the public IP from above."
  241. fi
  242. fi
  243. if [[ "${GO_VERSION}" > "$(go_version)" ]]; then
  244. warn_msg "golang ($(go_version)) needs to be $GO_VERSION at least"
  245. else
  246. info_msg "golang $(go_version) is installed (min needed is: $GO_VERSION)"
  247. fi
  248. if [ -f "${APACHE_SITES_AVAILABLE}/searx.conf" ]; then
  249. warn_msg "old searx.conf apache site exists"
  250. fi
  251. if [ -f "${NGINX_APPS_AVAILABLE}/searx.conf" ]; then
  252. warn_msg "old searx.conf nginx site exists"
  253. fi
  254. }
  255. go_version(){
  256. sudo -i -u "$SERVICE_USER" <<EOF
  257. go version | cut -d' ' -f 3
  258. EOF
  259. }
  260. remove_all() {
  261. rst_title "De-Install $SERVICE_NAME (service)"
  262. rst_para "\
  263. It goes without saying that this script can only be used to remove
  264. installations that were installed with this script."
  265. if ! systemd_remove_service "${SERVICE_NAME}" "${SERVICE_SYSTEMD_UNIT}"; then
  266. return 42
  267. fi
  268. drop_service_account "${SERVICE_USER}"
  269. rm -r "$FILTRON_ETC" 2>&1 | prefix_stdout
  270. if service_is_available "${PUBLIC_URL}"; then
  271. MSG="** Don't forget to remove your public site! (${PUBLIC_URL}) **" wait_key 10
  272. fi
  273. }
  274. assert_user() {
  275. rst_title "user $SERVICE_USER" section
  276. echo
  277. tee_stderr 1 <<EOF | bash | prefix_stdout
  278. useradd --shell /bin/bash --system \
  279. --home-dir "$SERVICE_HOME" \
  280. --comment 'Reverse HTTP proxy to filter requests' $SERVICE_USER
  281. mkdir "$SERVICE_HOME"
  282. chown -R "$SERVICE_GROUP:$SERVICE_GROUP" "$SERVICE_HOME"
  283. groups $SERVICE_USER
  284. EOF
  285. SERVICE_HOME="$(sudo -i -u "$SERVICE_USER" echo \$HOME)"
  286. export SERVICE_HOME
  287. echo "export SERVICE_HOME=$SERVICE_HOME"
  288. cat > "$GO_ENV" <<EOF
  289. export GOPATH=\$HOME/go-apps
  290. export PATH=\$PATH:\$HOME/local/go/bin:\$GOPATH/bin
  291. EOF
  292. echo "Environment $GO_ENV has been setup."
  293. tee_stderr <<EOF | sudo -i -u "$SERVICE_USER"
  294. grep -qFs -- 'source $GO_ENV' ~/.profile || echo 'source $GO_ENV' >> ~/.profile
  295. EOF
  296. }
  297. filtron_is_installed() {
  298. [[ -f $SERVICE_HOME/go-apps/bin/filtron ]]
  299. }
  300. _svcpr=" ${_Yellow}|${SERVICE_USER}|${_creset} "
  301. install_filtron() {
  302. rst_title "Install filtron in user's ~/go-apps" section
  303. echo
  304. tee_stderr <<EOF | sudo -i -u "$SERVICE_USER" 2>&1 | prefix_stdout "$_svcpr"
  305. go get -v -u github.com/asciimoo/filtron
  306. EOF
  307. }
  308. update_filtron() {
  309. rst_title "Update filtron" section
  310. echo
  311. tee_stderr <<EOF | sudo -i -u "$SERVICE_USER" 2>&1 | prefix_stdout "$_svcpr"
  312. go get -v -u github.com/asciimoo/filtron
  313. EOF
  314. }
  315. install_rules() {
  316. rst_title "Install filtron rules"
  317. echo
  318. if [[ ! -f "${FILTRON_RULES}" ]]; then
  319. info_msg "install rules ${FILTRON_RULES_TEMPLATE}"
  320. info_msg " --> ${FILTRON_RULES}"
  321. mkdir -p "$(dirname "${FILTRON_RULES}")"
  322. cp "${FILTRON_RULES_TEMPLATE}" "${FILTRON_RULES}"
  323. return
  324. fi
  325. if cmp --silent "${FILTRON_RULES}" "${FILTRON_RULES_TEMPLATE}"; then
  326. info_msg "${FILTRON_RULES} is up to date with"
  327. info_msg "${FILTRON_RULES_TEMPLATE}"
  328. return
  329. fi
  330. rst_para "Diff between origin's rules file (+) and current (-):"
  331. echo "${FILTRON_RULES}" "${FILTRON_RULES_TEMPLATE}"
  332. $DIFF_CMD "${FILTRON_RULES}" "${FILTRON_RULES_TEMPLATE}"
  333. local action
  334. choose_one action "What should happen to the rules file? " \
  335. "keep configuration unchanged" \
  336. "use origin rules" \
  337. "start interactive shell"
  338. case $action in
  339. "keep configuration unchanged")
  340. info_msg "leave rules file unchanged"
  341. ;;
  342. "use origin rules")
  343. backup_file "${FILTRON_RULES}"
  344. info_msg "install origin rules"
  345. cp "${FILTRON_RULES_TEMPLATE}" "${FILTRON_RULES}"
  346. ;;
  347. "start interactive shell")
  348. backup_file "${FILTRON_RULES}"
  349. echo -e "// exit with [${_BCyan}CTRL-D${_creset}]"
  350. sudo -H -i
  351. rst_para 'Diff between new rules file (-) and current (+):'
  352. echo
  353. $DIFF_CMD "${FILTRON_RULES_TEMPLATE}" "${FILTRON_RULES}"
  354. wait_key
  355. ;;
  356. esac
  357. }
  358. inspect_service() {
  359. rst_title "service status & log"
  360. cat <<EOF
  361. sourced ${DOT_CONFIG} :
  362. SERVICE_USER : ${SERVICE_USER}
  363. SERVICE_HOME : ${SERVICE_HOME}
  364. FILTRON_TARGET : ${FILTRON_TARGET}
  365. FILTRON_API : ${FILTRON_API}
  366. FILTRON_LISTEN : ${FILTRON_LISTEN}
  367. FILTRON_URL_PATH : ${FILTRON_URL_PATH}
  368. EOF
  369. install_log_searx_instance
  370. install_check
  371. if in_container; then
  372. lxc_suite_info
  373. else
  374. info_msg "public URL --> ${PUBLIC_URL}"
  375. info_msg "internal URL --> http://${FILTRON_LISTEN}"
  376. fi
  377. local _debug_on
  378. if ask_yn "Enable filtron debug mode?"; then
  379. enable_debug
  380. _debug_on=1
  381. fi
  382. echo
  383. systemctl --no-pager -l status "${SERVICE_NAME}"
  384. echo
  385. info_msg "public URL --> ${PUBLIC_URL}"
  386. # shellcheck disable=SC2059
  387. printf "// use ${_BCyan}CTRL-C${_creset} to stop monitoring the log"
  388. read -r -s -n1 -t 5
  389. echo
  390. while true; do
  391. trap break 2
  392. journalctl -f -u "${SERVICE_NAME}"
  393. done
  394. if [[ $_debug_on == 1 ]]; then
  395. disable_debug
  396. fi
  397. return 0
  398. }
  399. enable_debug() {
  400. info_msg "try to enable debug mode ..."
  401. python <<EOF
  402. import sys, json
  403. debug = {
  404. u'name': u'debug request'
  405. , u'filters': []
  406. , u'interval': 0
  407. , u'limit': 0
  408. , u'actions': [{u'name': u'log'}]
  409. }
  410. with open('$FILTRON_RULES') as rules:
  411. j = json.load(rules)
  412. pos = None
  413. for i in range(len(j)):
  414. if j[i].get('name') == 'debug request':
  415. pos = i
  416. break
  417. if pos is not None:
  418. j[pos] = debug
  419. else:
  420. j.append(debug)
  421. with open('$FILTRON_RULES', 'w') as rules:
  422. json.dump(j, rules, indent=2, sort_keys=True)
  423. EOF
  424. systemctl restart "${SERVICE_NAME}.service"
  425. }
  426. disable_debug() {
  427. info_msg "try to disable debug mode ..."
  428. python <<EOF
  429. import sys, json
  430. with open('$FILTRON_RULES') as rules:
  431. j = json.load(rules)
  432. pos = None
  433. for i in range(len(j)):
  434. if j[i].get('name') == 'debug request':
  435. pos = i
  436. break
  437. if pos is not None:
  438. del j[pos]
  439. with open('$FILTRON_RULES', 'w') as rules:
  440. json.dump(j, rules, indent=2, sort_keys=True)
  441. EOF
  442. systemctl restart "${SERVICE_NAME}.service"
  443. }
  444. install_apache_site() {
  445. rst_title "Install Apache site $APACHE_FILTRON_SITE"
  446. rst_para "\
  447. This installs a reverse proxy (ProxyPass) into apache site (${APACHE_FILTRON_SITE})"
  448. ! apache_is_installed && info_msg "Apache is not installed."
  449. if ! ask_yn "Do you really want to continue?" Yn; then
  450. return
  451. else
  452. install_apache
  453. fi
  454. "${REPO_ROOT}/utils/searx.sh" install uwsgi
  455. apache_install_site --variant=filtron "${APACHE_FILTRON_SITE}"
  456. info_msg "testing public url .."
  457. if ! service_is_available "${PUBLIC_URL}"; then
  458. err_msg "Public service at ${PUBLIC_URL} is not available!"
  459. fi
  460. }
  461. remove_apache_site() {
  462. rst_title "Remove Apache site $APACHE_FILTRON_SITE"
  463. rst_para "\
  464. This removes apache site ${APACHE_FILTRON_SITE}."
  465. ! apache_is_installed && err_msg "Apache is not installed."
  466. if ! ask_yn "Do you really want to continue?" Yn; then
  467. return
  468. fi
  469. apache_remove_site "$APACHE_FILTRON_SITE"
  470. }
  471. install_nginx_site() {
  472. rst_title "Install nginx site $NGINX_FILTRON_SITE"
  473. rst_para "\
  474. This installs a reverse proxy (ProxyPass) into nginx site (${NGINX_FILTRON_SITE})"
  475. ! nginx_is_installed && info_msg "nginx is not installed."
  476. if ! ask_yn "Do you really want to continue?" Yn; then
  477. return
  478. else
  479. install_nginx
  480. fi
  481. "${REPO_ROOT}/utils/searx.sh" install uwsgi
  482. # shellcheck disable=SC2034
  483. SEARX_SRC=$("${REPO_ROOT}/utils/searx.sh" --getenv SEARX_SRC)
  484. # shellcheck disable=SC2034
  485. SEARXNG_URL_PATH=$("${REPO_ROOT}/utils/searx.sh" --getenv SEARXNG_URL_PATH)
  486. nginx_install_app --variant=filtron "${NGINX_FILTRON_SITE}"
  487. info_msg "testing public url .."
  488. if ! service_is_available "${PUBLIC_URL}"; then
  489. err_msg "Public service at ${PUBLIC_URL} is not available!"
  490. fi
  491. }
  492. remove_nginx_site() {
  493. rst_title "Remove nginx site $NGINX_FILTRON_SITE"
  494. rst_para "\
  495. This removes nginx site ${NGINX_FILTRON_SITE}."
  496. ! nginx_is_installed && err_msg "nginx is not installed."
  497. if ! ask_yn "Do you really want to continue?" Yn; then
  498. return
  499. fi
  500. nginx_remove_site "$FILTRON_FILTRON_SITE"
  501. }
  502. rst-doc() {
  503. eval "echo \"$(< "${REPO_ROOT}/docs/build-templates/filtron.rst")\""
  504. echo -e "\n.. START install systemd unit"
  505. cat <<EOF
  506. .. tabs::
  507. .. group-tab:: systemd
  508. .. code:: bash
  509. EOF
  510. eval "echo \"$(< "${TEMPLATES}/${SERVICE_SYSTEMD_UNIT}")\"" | prefix_stdout " "
  511. echo -e "\n.. END install systemd unit"
  512. # for DIST_NAME in ubuntu-20.04 arch fedora centos; do
  513. # (
  514. # DIST_ID=${DIST_NAME%-*}
  515. # DIST_VERS=${DIST_NAME#*-}
  516. # [[ $DIST_VERS =~ $DIST_ID ]] && DIST_VERS=
  517. # # ...
  518. # )
  519. # done
  520. }
  521. # ----------------------------------------------------------------------------
  522. main "$@"
  523. # ----------------------------------------------------------------------------