From b34e7f5b7374985c5ef0e66aa90fc5761a625329 Mon Sep 17 00:00:00 2001 From: Mike Wilson Date: Thu, 15 Jun 2023 12:46:06 -0400 Subject: [PATCH] Added script directory to Tautulli --- ansible/main.yml | 3 +- ansible/roles/authentik/handlers/main.yml | 5 + ansible/roles/authentik/tasks/main.yml | 30 + .../authentik/templates/docker-compose.yml | 38 + .../roles/authentik/templates/settings.yml | 1890 +++++++++++++++++ ansible/roles/authentik/vars/main.yml | 10 + ansible/roles/gitea/tasks/main.yml | 2 +- ansible/roles/tautulli/files/kill_stream.py | 639 ++++++ ansible/roles/tautulli/tasks/main.yml | 10 + 9 files changed, 2625 insertions(+), 2 deletions(-) create mode 100644 ansible/roles/authentik/handlers/main.yml create mode 100644 ansible/roles/authentik/tasks/main.yml create mode 100644 ansible/roles/authentik/templates/docker-compose.yml create mode 100644 ansible/roles/authentik/templates/settings.yml create mode 100644 ansible/roles/authentik/vars/main.yml create mode 100644 ansible/roles/tautulli/files/kill_stream.py diff --git a/ansible/main.yml b/ansible/main.yml index 7d66e50..89fa6ae 100644 --- a/ansible/main.yml +++ b/ansible/main.yml @@ -16,7 +16,8 @@ - overseerr - ntfy - nextcloud - - tautulli + - name: tautulli + tags: test - unifi-controller - navidrome - lidarr diff --git a/ansible/roles/authentik/handlers/main.yml b/ansible/roles/authentik/handlers/main.yml new file mode 100644 index 0000000..2a269a1 --- /dev/null +++ b/ansible/roles/authentik/handlers/main.yml @@ -0,0 +1,5 @@ +- name: restart searxng + community.docker.docker_compose: + project_src: "{{ install_directory }}/{{ role_name }}" + restarted: true + diff --git a/ansible/roles/authentik/tasks/main.yml b/ansible/roles/authentik/tasks/main.yml new file mode 100644 index 0000000..9f9040b --- /dev/null +++ b/ansible/roles/authentik/tasks/main.yml @@ -0,0 +1,30 @@ +- name: Create install directory + file: + path: "{{ install_directory }}/{{ role_name }}" + state: directory + owner: "{{ docker_user }}" + mode: "{{ docker_compose_directory_mask }}" + become: true + +- name: Copy docker-compose file to destination + template: + src: docker-compose.yml + dest: "{{ install_directory }}/{{ role_name }}/docker-compose.yml" + owner: "{{ docker_user }}" + mode: "{{ docker_compose_file_mask }}" + validate: docker-compose -f %s config + become: true + +- name: Copy settings file to destionation + template: + src: settings.yml + dest: "{{ data_dir }}/{{ role_name }}/settings.yml" + owner: "{{ docker_user }}" + become: true + notify: restart searxng + +- name: Start docker container + community.docker.docker_compose: + project_src: "{{ install_directory }}/{{ role_name }}" + pull: true + remove_orphans: yes diff --git a/ansible/roles/authentik/templates/docker-compose.yml b/ansible/roles/authentik/templates/docker-compose.yml new file mode 100644 index 0000000..458a3d0 --- /dev/null +++ b/ansible/roles/authentik/templates/docker-compose.yml @@ -0,0 +1,38 @@ +version: "{{ docker_compose_version }}" + +networks: + traefik: + external: true + +services: + searxng: + container_name: searxng + image: searxng/searxng + restart: unless-stopped + networks: + - traefik + volumes: + - "{{ data_dir }}/{{ role_name }}:/etc/searxng" + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID + labels: + traefik.enable: true + traefik.http.routers.searxng.rule: "Host(`search.{{ personal_domain }}`)" + traefik.http.routers.searxng.middlewares: lan-whitelist@file + + redis: + image: redis:alpine + restart: unless-stopped + command: redis-server --save "" --appendonly "no" + tmpfs: + - /var/lib/redis + cap_drop: + - ALL + cap_add: + - SETGID + - SETUID + - DAC_OVERRIDE diff --git a/ansible/roles/authentik/templates/settings.yml b/ansible/roles/authentik/templates/settings.yml new file mode 100644 index 0000000..224149c --- /dev/null +++ b/ansible/roles/authentik/templates/settings.yml @@ -0,0 +1,1890 @@ +general: + debug: false + instance_name: "DuckDuckNo" + privacypolicy_url: false + donation_url: false + contact_url: false + enable_metrics: true + +brand: + new_issue_url: https://github.com/searxng/searxng/issues/new + docs_url: https://docs.searxng.org/ + public_instances: https://searx.space + wiki_url: https://github.com/searxng/searxng/wiki + issue_url: https://github.com/searxng/searxng/issues + +search: + safe_search: 0 + autocomplete: "qwant" + autocomplete_min: 4 + default_lang: "auto" + ban_time_on_fail: 5 + max_ban_time_on_fail: 120 + suspended_times: + # Engine suspension time after error (in seconds; set to 0 to disable) + # For error "Access denied" and "HTTP error [402, 403]" + SearxEngineAccessDenied: 86400 + # For error "CAPTCHA" + SearxEngineCaptcha: 86400 + # For error "Too many request" and "HTTP error 429" + SearxEngineTooManyRequests: 3600 + # Cloudflare CAPTCHA + cf_SearxEngineCaptcha: 1296000 + cf_SearxEngineAccessDenied: 86400 + # ReCAPTCHA + recaptcha_SearxEngineCaptcha: 604800 + + # remove format to deny access, use lower case. + # formats: [html, csv, json, rss] + formats: + - html + +server: + # If you change port, bind_address or base_url don't forget to rebuild + # instance's environment (make buildenv). Is overwritten by ${SEARXNG_PORT} + # and ${SEARXNG_BIND_ADDRESS} + port: 8888 + bind_address: "127.0.0.1" + # public URL of the instance, to ensure correct inbound links. Is overwritten + # by ${SEARXNG_URL}. + base_url: "https://search.{{ personal_domain }}" # "http://example.com/location" + limiter: false # rate limit the number of request on the instance, block some bots + + # If your instance owns a /etc/searxng/settings.yml file, then set the following + # values there. + + secret_key: "{{ searxng_secret_key }}" # Is overwritten by ${SEARXNG_SECRET} + # Proxying image results through searx + image_proxy: true + # 1.0 and 1.1 are supported + http_protocol_version: "1.0" + # POST queries are more secure as they don't show up in history but may cause + # problems when using Firefox containers + method: "POST" + default_http_headers: + X-Content-Type-Options: nosniff + X-XSS-Protection: 1; mode=block + X-Download-Options: noopen + X-Robots-Tag: noindex, nofollow + Referrer-Policy: no-referrer + +redis: + # URL to connect redis database. Is overwritten by ${SEARXNG_REDIS_URL}. + # https://redis-py.readthedocs.io/en/stable/connections.html#redis.client.Redis.from_url + url: redis://redis:6379 + +ui: + # Custom static path - leave it blank if you didn't change + static_path: "" + static_use_hash: false + # Custom templates path - leave it blank if you didn't change + templates_path: "" + # query_in_title: When true, the result page's titles contains the query + # it decreases the privacy, since the browser can records the page titles. + query_in_title: false + # infinite_scroll: When true, automatically loads the next page when scrolling to bottom of the current page. + infinite_scroll: false + # ui theme + default_theme: simple + # center the results ? + center_alignment: false + # URL prefix of the internet archive, don't forgett trailing slash (if needed). + # cache_url: "https://webcache.googleusercontent.com/search?q=cache:" + # Default interface locale - leave blank to detect from browser information or + # use codes from the 'locales' config section + default_locale: "" + # Open result links in a new tab by default + # results_on_new_tab: false + theme_args: + # style of simple theme: auto, light, dark + simple_style: auto + +# Lock arbitrary settings on the preferences page. To find the ID of the user +# setting you want to lock, check the ID of the form on the page "preferences". +# +# preferences: +# lock: +# - language +# - autocomplete +# - method +# - query_in_title + +# searx supports result proxification using an external service: +# https://github.com/asciimoo/morty uncomment below section if you have running +# morty proxy the key is base64 encoded (keep the !!binary notation) +# Note: since commit af77ec3, morty accepts a base64 encoded key. +# +# result_proxy: +# url: http://127.0.0.1:3000/ +# # the key is a base64 encoded string, the YAML !!binary prefix is optional +# key: !!binary "your_morty_proxy_key" +# # [true|false] enable the "proxy" button next to each result +# proxify_results: true + +# communication with search engines +# +outgoing: + # default timeout in seconds, can be override by engine + request_timeout: 3.0 + # the maximum timeout in seconds + # max_request_timeout: 10.0 + # suffix of searx_useragent, could contain information like an email address + # to the administrator + useragent_suffix: "" + # The maximum number of concurrent connections that may be established. + pool_connections: 100 + # Allow the connection pool to maintain keep-alive connections below this + # point. + pool_maxsize: 20 + # See https://www.python-httpx.org/http2/ + enable_http2: true + # uncomment below section if you want to use a custom server certificate + # see https://www.python-httpx.org/advanced/#changing-the-verification-defaults + # and https://www.python-httpx.org/compatibility/#ssl-configuration + # verify: ~/.mitmproxy/mitmproxy-ca-cert.cer + # + # uncomment below section if you want to use a proxyq see: SOCKS proxies + # https://2.python-requests.org/en/latest/user/advanced/#proxies + # are also supported: see + # https://2.python-requests.org/en/latest/user/advanced/#socks + # + # proxies: + # all://: + # - http://proxy1:8080 + # - http://proxy2:8080 + # + # using_tor_proxy: true + # + # Extra seconds to add in order to account for the time taken by the proxy + # + # extra_proxy_timeout: 10.0 + # + # uncomment below section only if you have more than one network interface + # which can be the source of outgoing search requests + # + # source_ips: + # - 1.1.1.1 + # - 1.1.1.2 + # - fe80::/126 + +# External plugin configuration, for more details see +# https://docs.searxng.org/dev/plugins.html +# +# plugins: +# - plugin1 +# - plugin2 +# - ... + +# Comment or un-comment plugin to activate / deactivate by default. +# +# enabled_plugins: +# # these plugins are enabled if nothing is configured .. +# - 'Hash plugin' +# - 'Search on category select' +# - 'Self Information' +# - 'Tracker URL remover' +# - 'Ahmia blacklist' # activation depends on outgoing.using_tor_proxy +# # these plugins are disabled if nothing is configured .. +# - 'Hostname replace' # see hostname_replace configuration below +# - 'Open Access DOI rewrite' +# - 'Vim-like hotkeys' +# - 'Tor check plugin' +# # Read the docs before activate: auto-detection of the language could be +# # detrimental to users expectations / users can activate the plugin in the +# # preferences if they want. +# - 'Autodetect search language' + +# Configuration of the "Hostname replace" plugin: +# +# hostname_replace: +# '(.*\.)?youtube\.com$': 'invidious.example.com' +# '(.*\.)?youtu\.be$': 'invidious.example.com' +# '(.*\.)?youtube-noocookie\.com$': 'yotter.example.com' +# '(.*\.)?reddit\.com$': 'teddit.example.com' +# '(.*\.)?redd\.it$': 'teddit.example.com' +# '(www\.)?twitter\.com$': 'nitter.example.com' +# # to remove matching host names from result list, set value to false +# 'spam\.example\.com': false + +checker: + # disable checker when in debug mode + off_when_debug: true + + # use "scheduling: false" to disable scheduling + # scheduling: interval or int + + # to activate the scheduler: + # * uncomment "scheduling" section + # * add "cache2 = name=searxngcache,items=2000,blocks=2000,blocksize=4096,bitmap=1" + # to your uwsgi.ini + + # scheduling: + # start_after: [300, 1800] # delay to start the first run of the checker + # every: [86400, 90000] # how often the checker runs + + # additional tests: only for the YAML anchors (see the engines section) + # + additional_tests: + rosebud: &test_rosebud + matrix: + query: rosebud + lang: en + result_container: + - not_empty + - ['one_title_contains', 'citizen kane'] + test: + - unique_results + + android: &test_android + matrix: + query: ['android'] + lang: ['en', 'de', 'fr', 'zh-CN'] + result_container: + - not_empty + - ['one_title_contains', 'google'] + test: + - unique_results + + # tests: only for the YAML anchors (see the engines section) + tests: + infobox: &tests_infobox + infobox: + matrix: + query: ["linux", "new york", "bbc"] + result_container: + - has_infobox + +categories_as_tabs: + general: + images: + videos: + news: + map: + music: + it: + science: + files: + social media: + +engines: + - name: 9gag + engine: 9gag + shortcut: 9g + disabled: true + + - name: apk mirror + engine: apkmirror + timeout: 4.0 + shortcut: apkm + disabled: true + + - name: apple app store + engine: apple_app_store + shortcut: aps + disabled: true + + # Requires Tor + - name: ahmia + engine: ahmia + categories: onions + enable_http: true + shortcut: ah + + - name: arch linux wiki + engine: archlinux + shortcut: al + + - name: archive is + engine: xpath + search_url: https://archive.is/search/?q={query} + url_xpath: (//div[@class="TEXT-BLOCK"]/a)/@href + title_xpath: (//div[@class="TEXT-BLOCK"]/a) + content_xpath: //div[@class="TEXT-BLOCK"]/ul/li + categories: general + timeout: 7.0 + disabled: true + shortcut: ai + soft_max_redirects: 1 + about: + website: https://archive.is/ + wikidata_id: Q13515725 + official_api_documentation: https://mementoweb.org/depot/native/archiveis/ + use_official_api: false + require_api_key: false + results: HTML + + - name: artic + engine: artic + shortcut: arc + timeout: 4.0 + + - name: arxiv + engine: arxiv + shortcut: arx + timeout: 4.0 + + # tmp suspended: dh key too small + # - name: base + # engine: base + # shortcut: bs + + - name: bandcamp + engine: bandcamp + shortcut: bc + categories: music + + - name: wikipedia + engine: wikipedia + shortcut: wp + base_url: 'https://{language}.wikipedia.org/' + + - name: bing + engine: bing + shortcut: bi + disabled: true + + - name: bing images + engine: bing_images + shortcut: bii + + - name: bing news + engine: bing_news + shortcut: bin + + - name: bing videos + engine: bing_videos + shortcut: biv + + - name: bitbucket + engine: xpath + paging: true + search_url: https://bitbucket.org/repo/all/{pageno}?name={query} + url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href + title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"] + content_xpath: //article[@class="repo-summary"]/p + categories: [it, repos] + timeout: 4.0 + disabled: true + shortcut: bb + about: + website: https://bitbucket.org/ + wikidata_id: Q2493781 + official_api_documentation: https://developer.atlassian.com/bitbucket + use_official_api: false + require_api_key: false + results: HTML + + - name: btdigg + engine: btdigg + shortcut: bt + + - name: ccc-tv + engine: xpath + paging: false + search_url: https://media.ccc.de/search/?q={query} + url_xpath: //div[@class="caption"]/h3/a/@href + title_xpath: //div[@class="caption"]/h3/a/text() + content_xpath: //div[@class="caption"]/h4/@title + categories: videos + disabled: true + shortcut: c3tv + about: + website: https://media.ccc.de/ + wikidata_id: Q80729951 + official_api_documentation: https://github.com/voc/voctoweb + use_official_api: false + require_api_key: false + results: HTML + # We don't set language: de here because media.ccc.de is not just + # for a German audience. It contains many English videos and many + # German videos have English subtitles. + + - name: openverse + engine: openverse + categories: images + shortcut: opv + + # - name: core.ac.uk + # engine: core + # categories: science + # shortcut: cor + # # get your API key from: https://core.ac.uk/api-keys/register/ + # api_key: 'unset' + + - name: crossref + engine: crossref + shortcut: cr + timeout: 30 + disabled: true + + - name: yep + engine: json_engine + shortcut: yep + categories: general + disabled: true + paging: false + content_html_to_text: true + title_html_to_text: true + search_url: https://api.yep.com/fs/1/?type=web&q={query}&no_correct=false&limit=100 + results_query: 1/results + title_query: title + url_query: url + content_query: snippet + about: + website: https://yep.com + use_official_api: false + require_api_key: false + results: JSON + + - name: curlie + engine: xpath + shortcut: cl + categories: general + disabled: true + paging: true + lang_all: '' + search_url: https://curlie.org/search?q={query}&lang={lang}&start={pageno}&stime=92452189 + page_size: 20 + results_xpath: //div[@id="site-list-content"]/div[@class="site-item"] + url_xpath: ./div[@class="title-and-desc"]/a/@href + title_xpath: ./div[@class="title-and-desc"]/a/div + content_xpath: ./div[@class="title-and-desc"]/div[@class="site-descr"] + about: + website: https://curlie.org/ + wikidata_id: Q60715723 + use_official_api: false + require_api_key: false + results: HTML + + - name: currency + engine: currency_convert + categories: general + shortcut: cc + + - name: deezer + engine: deezer + shortcut: dz + disabled: true + + - name: deviantart + engine: deviantart + shortcut: da + timeout: 3.0 + + - name: ddg definitions + engine: duckduckgo_definitions + shortcut: ddd + weight: 2 + disabled: true + tests: *tests_infobox + + # cloudflare protected + # - name: digbt + # engine: digbt + # shortcut: dbt + # timeout: 6.0 + # disabled: true + + - name: docker hub + engine: docker_hub + shortcut: dh + categories: [it, packages] + + - name: erowid + engine: xpath + paging: true + first_page_num: 0 + page_size: 30 + search_url: https://www.erowid.org/search.php?q={query}&s={pageno} + url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href + title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text() + content_xpath: //dl[@class="results-list"]/dd[@class="result-details"] + categories: [] + shortcut: ew + disabled: true + about: + website: https://www.erowid.org/ + wikidata_id: Q1430691 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + # - name: elasticsearch + # shortcut: es + # engine: elasticsearch + # base_url: http://localhost:9200 + # username: elastic + # password: changeme + # index: my-index + # # available options: match, simple_query_string, term, terms, custom + # query_type: match + # # if query_type is set to custom, provide your query here + # #custom_query_json: {"query":{"match_all": {}}} + # #show_metadata: false + # disabled: true + + - name: wikidata + engine: wikidata + shortcut: wd + timeout: 3.0 + weight: 2 + tests: *tests_infobox + + - name: duckduckgo + engine: duckduckgo + shortcut: ddg + + - name: duckduckgo images + engine: duckduckgo_images + shortcut: ddi + timeout: 3.0 + disabled: true + + - name: duckduckgo weather + engine: duckduckgo_weather + shortcut: ddw + disabled: true + + - name: apple maps + engine: apple_maps + shortcut: apm + disabled: true + timeout: 5.0 + + - name: emojipedia + engine: emojipedia + timeout: 4.0 + shortcut: em + disabled: true + + - name: tineye + engine: tineye + shortcut: tin + timeout: 9.0 + disabled: true + + - name: etymonline + engine: xpath + paging: true + search_url: https://etymonline.com/search?page={pageno}&q={query} + url_xpath: //a[contains(@class, "word__name--")]/@href + title_xpath: //a[contains(@class, "word__name--")] + content_xpath: //section[contains(@class, "word__defination")] + first_page_num: 1 + shortcut: et + categories: [dictionaries] + disabled: false + about: + website: https://www.etymonline.com/ + wikidata_id: Q1188617 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + # - name: ebay + # engine: ebay + # shortcut: eb + # base_url: 'https://www.ebay.com' + # disabled: true + # timeout: 5 + + - name: 1x + engine: www1x + shortcut: 1x + timeout: 3.0 + disabled: true + + - name: fdroid + engine: fdroid + shortcut: fd + disabled: true + + - name: flickr + categories: images + shortcut: fl + # You can use the engine using the official stable API, but you need an API + # key, see: https://www.flickr.com/services/apps/create/ + # engine: flickr + # api_key: 'apikey' # required! + # Or you can use the html non-stable engine, activated by default + engine: flickr_noapi + + - name: free software directory + engine: mediawiki + shortcut: fsd + categories: [it, software wikis] + base_url: https://directory.fsf.org/ + number_of_results: 5 + # what part of a page matches the query string: title, text, nearmatch + # * title - query matches title + # * text - query matches the text of page + # * nearmatch - nearmatch in title + search_type: title + timeout: 5.0 + disabled: true + about: + website: https://directory.fsf.org/ + wikidata_id: Q2470288 + + # - name: freesound + # engine: freesound + # shortcut: fnd + # disabled: true + # timeout: 15.0 + # API key required, see: https://freesound.org/docs/api/overview.html + # api_key: MyAPIkey + + - name: frinkiac + engine: frinkiac + shortcut: frk + disabled: true + + - name: genius + engine: genius + shortcut: gen + + - name: gentoo + engine: gentoo + shortcut: ge + timeout: 10.0 + + - name: gitlab + engine: json_engine + paging: true + search_url: https://gitlab.com/api/v4/projects?search={query}&page={pageno} + url_query: web_url + title_query: name_with_namespace + content_query: description + page_size: 20 + categories: [it, repos] + shortcut: gl + timeout: 10.0 + disabled: true + about: + website: https://about.gitlab.com/ + wikidata_id: Q16639197 + official_api_documentation: https://docs.gitlab.com/ee/api/ + use_official_api: false + require_api_key: false + results: JSON + + - name: github + engine: github + shortcut: gh + + # This a Gitea service. If you would like to use a different instance, + # change codeberg.org to URL of the desired Gitea host. Or you can create a + # new engine by copying this and changing the name, shortcut and search_url. + + - name: codeberg + engine: json_engine + search_url: https://codeberg.org/api/v1/repos/search?q={query}&limit=10 + url_query: html_url + title_query: name + content_query: description + categories: [it, repos] + shortcut: cb + disabled: true + about: + website: https://codeberg.org/ + wikidata_id: + official_api_documentation: https://try.gitea.io/api/swagger + use_official_api: false + require_api_key: false + results: JSON + + - name: google + engine: google + shortcut: go + # additional_tests: + # android: *test_android + + - name: google images + engine: google_images + shortcut: goi + # additional_tests: + # android: *test_android + # dali: + # matrix: + # query: ['Dali Christ'] + # lang: ['en', 'de', 'fr', 'zh-CN'] + # result_container: + # - ['one_title_contains', 'Salvador'] + + - name: google news + engine: google_news + shortcut: gon + # additional_tests: + # android: *test_android + + - name: google videos + engine: google_videos + shortcut: gov + # additional_tests: + # android: *test_android + + - name: google scholar + engine: google_scholar + shortcut: gos + + - name: google play apps + engine: google_play + categories: [files, apps] + shortcut: gpa + play_categ: apps + disabled: true + + - name: google play movies + engine: google_play + categories: videos + shortcut: gpm + play_categ: movies + disabled: true + + - name: gpodder + engine: json_engine + shortcut: gpod + timeout: 4.0 + paging: false + search_url: https://gpodder.net/search.json?q={query} + url_query: url + title_query: title + content_query: description + page_size: 19 + categories: music + disabled: true + about: + website: https://gpodder.net + wikidata_id: Q3093354 + official_api_documentation: https://gpoddernet.readthedocs.io/en/latest/api/ + use_official_api: false + requires_api_key: false + results: JSON + + - name: habrahabr + engine: xpath + paging: true + search_url: https://habrahabr.ru/search/page{pageno}/?q={query} + url_xpath: //article[contains(@class, "post")]//a[@class="post__title_link"]/@href + title_xpath: //article[contains(@class, "post")]//a[@class="post__title_link"] + content_xpath: //article[contains(@class, "post")]//div[contains(@class, "post__text")] + categories: it + timeout: 4.0 + disabled: true + shortcut: habr + about: + website: https://habr.com/ + wikidata_id: Q4494434 + official_api_documentation: https://habr.com/en/docs/help/api/ + use_official_api: false + require_api_key: false + results: HTML + + - name: hoogle + engine: xpath + paging: true + search_url: https://hoogle.haskell.org/?hoogle={query}&start={pageno} + results_xpath: '//div[@class="result"]' + title_xpath: './/div[@class="ans"]//a' + url_xpath: './/div[@class="ans"]//a/@href' + content_xpath: './/div[@class="from"]' + page_size: 20 + categories: [it, packages] + shortcut: ho + about: + website: https://hoogle.haskell.org/ + wikidata_id: Q34010 + official_api_documentation: https://hackage.haskell.org/api + use_official_api: false + require_api_key: false + results: JSON + + - name: imdb + engine: imdb + shortcut: imdb + timeout: 6.0 + disabled: true + + - name: ina + engine: ina + shortcut: in + timeout: 6.0 + disabled: true + + - name: invidious + engine: invidious + # Instanes will be selected randomly, see https://api.invidious.io/ for + # instances that are stable (good uptime) and close to you. + base_url: + - https://invidious.snopyta.org + - https://vid.puffyan.us + # - https://invidious.kavin.rocks # Error 1020 // Access denied by Cloudflare + - https://invidio.xamh.de + - https://inv.riverside.rocks + shortcut: iv + timeout: 3.0 + disabled: true + + - name: jisho + engine: jisho + shortcut: js + timeout: 3.0 + disabled: true + + - name: kickass + engine: kickass + shortcut: kc + timeout: 4.0 + disabled: true + + - name: library genesis + engine: xpath + search_url: https://libgen.fun/search.php?req={query} + url_xpath: //a[contains(@href,"get.php?md5")]/@href + title_xpath: //a[contains(@href,"book/")]/text()[1] + content_xpath: //td/a[1][contains(@href,"=author")]/text() + categories: files + timeout: 7.0 + disabled: true + shortcut: lg + about: + website: https://libgen.fun/ + wikidata_id: Q22017206 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + # Disabling zlibrary due to z-lib.org domain seizure + # https://github.com/searxng/searxng/pull/1937 + # + # - name: z-library + # engine: zlibrary + # shortcut: zlib + # categories: files + # timeout: 3.0 + # # choose base_url, otherwise engine will do it at initialization time + # # base_url: https://b-ok.cc + # # base_url: https://de1lib.org + # # base_url: https://booksc.eu # does not have cover preview + # # base_url: https://booksc.org # does not have cover preview + + - name: library of congress + engine: loc + shortcut: loc + categories: images + + - name: lingva + engine: lingva + shortcut: lv + # set lingva instance in url, by default it will use the official instance + # url: https://lingva.ml + + - name: lobste.rs + engine: xpath + search_url: https://lobste.rs/search?utf8=%E2%9C%93&q={query}&what=stories&order=relevance + results_xpath: //li[contains(@class, "story")] + url_xpath: .//a[@class="u-url"]/@href + title_xpath: .//a[@class="u-url"] + content_xpath: .//a[@class="domain"] + categories: it + shortcut: lo + timeout: 5.0 + disabled: true + about: + website: https://lobste.rs/ + wikidata_id: Q60762874 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: azlyrics + shortcut: lyrics + engine: xpath + timeout: 4.0 + disabled: true + categories: [music, lyrics] + paging: true + search_url: https://search.azlyrics.com/search.php?q={query}&w=lyrics&p={pageno} + url_xpath: //td[@class="text-left visitedlyr"]/a/@href + title_xpath: //span/b/text() + content_xpath: //td[@class="text-left visitedlyr"]/a/small + about: + website: https://azlyrics.com + wikidata_id: Q66372542 + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: metacpan + engine: metacpan + shortcut: cpan + disabled: true + number_of_results: 20 + + # - name: meilisearch + # engine: meilisearch + # shortcut: mes + # enable_http: true + # base_url: http://localhost:7700 + # index: my-index + + - name: mixcloud + engine: mixcloud + shortcut: mc + + # MongoDB engine + # Required dependency: pymongo + # - name: mymongo + # engine: mongodb + # shortcut: md + # exact_match_only: false + # host: '127.0.0.1' + # port: 27017 + # enable_http: true + # results_per_page: 20 + # database: 'business' + # collection: 'reviews' # name of the db collection + # key: 'name' # key in the collection to search for + + - name: npm + engine: json_engine + paging: true + first_page_num: 0 + search_url: https://api.npms.io/v2/search?q={query}&size=25&from={pageno} + results_query: results + url_query: package/links/npm + title_query: package/name + content_query: package/description + page_size: 25 + categories: [it, packages] + disabled: true + timeout: 5.0 + shortcut: npm + about: + website: https://npms.io/ + wikidata_id: Q7067518 + official_api_documentation: https://api-docs.npms.io/ + use_official_api: false + require_api_key: false + results: JSON + + - name: nyaa + engine: nyaa + shortcut: nt + disabled: true + + - name: mankier + engine: json_engine + search_url: https://www.mankier.com/api/v2/mans/?q={query} + results_query: results + url_query: url + title_query: name + content_query: description + categories: it + shortcut: man + about: + website: https://www.mankier.com/ + official_api_documentation: https://www.mankier.com/api + use_official_api: true + require_api_key: false + results: JSON + + - name: openairedatasets + engine: json_engine + paging: true + search_url: https://api.openaire.eu/search/datasets?format=json&page={pageno}&size=10&title={query} + results_query: response/results/result + url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$ + title_query: metadata/oaf:entity/oaf:result/title/$ + content_query: metadata/oaf:entity/oaf:result/description/$ + content_html_to_text: true + categories: "science" + shortcut: oad + timeout: 5.0 + about: + website: https://www.openaire.eu/ + wikidata_id: Q25106053 + official_api_documentation: https://api.openaire.eu/ + use_official_api: false + require_api_key: false + results: JSON + + - name: openairepublications + engine: json_engine + paging: true + search_url: https://api.openaire.eu/search/publications?format=json&page={pageno}&size=10&title={query} + results_query: response/results/result + url_query: metadata/oaf:entity/oaf:result/children/instance/webresource/url/$ + title_query: metadata/oaf:entity/oaf:result/title/$ + content_query: metadata/oaf:entity/oaf:result/description/$ + content_html_to_text: true + categories: science + shortcut: oap + timeout: 5.0 + about: + website: https://www.openaire.eu/ + wikidata_id: Q25106053 + official_api_documentation: https://api.openaire.eu/ + use_official_api: false + require_api_key: false + results: JSON + + # - name: opensemanticsearch + # engine: opensemantic + # shortcut: oss + # base_url: 'http://localhost:8983/solr/opensemanticsearch/' + + - name: openstreetmap + engine: openstreetmap + shortcut: osm + + - name: openrepos + engine: xpath + paging: true + search_url: https://openrepos.net/search/node/{query}?page={pageno} + url_xpath: //li[@class="search-result"]//h3[@class="title"]/a/@href + title_xpath: //li[@class="search-result"]//h3[@class="title"]/a + content_xpath: //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"] + categories: files + timeout: 4.0 + disabled: true + shortcut: or + about: + website: https://openrepos.net/ + wikidata_id: + official_api_documentation: + use_official_api: false + require_api_key: false + results: HTML + + - name: packagist + engine: json_engine + paging: true + search_url: https://packagist.org/search.json?q={query}&page={pageno} + results_query: results + url_query: url + title_query: name + content_query: description + categories: [it, packages] + disabled: true + timeout: 5.0 + shortcut: pack + about: + website: https://packagist.org + wikidata_id: Q108311377 + official_api_documentation: https://packagist.org/apidoc + use_official_api: true + require_api_key: false + results: JSON + + - name: pdbe + engine: pdbe + shortcut: pdb + # Hide obsolete PDB entries. Default is not to hide obsolete structures + # hide_obsolete: false + + - name: photon + engine: photon + shortcut: ph + + - name: piratebay + engine: piratebay + shortcut: tpb + # You may need to change this URL to a proxy if piratebay is blocked in your + # country + url: https://thepiratebay.org/ + timeout: 3.0 + + # Required dependency: psychopg2 + # - name: postgresql + # engine: postgresql + # database: postgres + # username: postgres + # password: postgres + # limit: 10 + # query_str: 'SELECT * from my_table WHERE my_column = %(query)s' + # shortcut : psql + + - name: pub.dev + engine: xpath + shortcut: pd + search_url: https://pub.dev/packages?q={query}&page={pageno} + paging: true + results_xpath: /html/body/main/div/div[@class="search-results"]/div[@class="packages"]/div + url_xpath: ./div/h3/a/@href + title_xpath: ./div/h3/a + content_xpath: ./p[@class="packages-description"] + categories: [packages, it] + timeout: 3.0 + disabled: true + first_page_num: 1 + about: + website: https://pub.dev/ + official_api_documentation: https://pub.dev/help/api + use_official_api: false + require_api_key: false + results: HTML + + - name: pubmed + engine: pubmed + shortcut: pub + timeout: 3.0 + + - name: pypi + shortcut: pypi + engine: xpath + paging: true + search_url: https://pypi.org/search?q={query}&page={pageno} + results_xpath: /html/body/main/div/div/div/form/div/ul/li/a[@class="package-snippet"] + url_xpath: ./@href + title_xpath: ./h3/span[@class="package-snippet__name"] + content_xpath: ./p + suggestion_xpath: /html/body/main/div/div/div/form/div/div[@class="callout-block"]/p/span/a[@class="link"] + first_page_num: 1 + categories: [it, packages] + about: + website: https://pypi.org + wikidata_id: Q2984686 + official_api_documentation: https://warehouse.readthedocs.io/api-reference/index.html + use_official_api: false + require_api_key: false + results: HTML + + - name: qwant + qwant_categ: web + engine: qwant + shortcut: qw + categories: [general, web] + disabled: false + additional_tests: + rosebud: *test_rosebud + + - name: qwant news + qwant_categ: news + engine: qwant + shortcut: qwn + categories: news + disabled: false + network: qwant + + - name: qwant images + qwant_categ: images + engine: qwant + shortcut: qwi + categories: [images, web] + disabled: false + network: qwant + + - name: qwant videos + qwant_categ: videos + engine: qwant + shortcut: qwv + categories: [videos, web] + disabled: false + network: qwant + + # - name: library + # engine: recoll + # shortcut: lib + # base_url: 'https://recoll.example.org/' + # search_dir: '' + # mount_prefix: /export + # dl_prefix: 'https://download.example.org' + # timeout: 30.0 + # categories: files + # disabled: true + + # - name: recoll library reference + # engine: recoll + # base_url: 'https://recoll.example.org/' + # search_dir: reference + # mount_prefix: /export + # dl_prefix: 'https://download.example.org' + # shortcut: libr + # timeout: 30.0 + # categories: files + # disabled: true + + - name: reddit + engine: reddit + shortcut: re + page_size: 25 + + # Required dependency: redis + # - name: myredis + # shortcut : rds + # engine: redis_server + # exact_match_only: false + # host: '127.0.0.1' + # port: 6379 + # enable_http: true + # password: '' + # db: 0 + + # tmp suspended: bad certificate + # - name: scanr structures + # shortcut: scs + # engine: scanr_structures + # disabled: true + + - name: sepiasearch + engine: sepiasearch + shortcut: sep + + - name: soundcloud + engine: soundcloud + shortcut: sc + + - name: stackoverflow + engine: stackexchange + shortcut: st + api_site: 'stackoverflow' + categories: [it, q&a] + + - name: askubuntu + engine: stackexchange + shortcut: ubuntu + api_site: 'askubuntu' + categories: [it, q&a] + + - name: superuser + engine: stackexchange + shortcut: su + api_site: 'superuser' + categories: [it, q&a] + + - name: searchcode code + engine: searchcode_code + shortcut: scc + disabled: true + + - name: framalibre + engine: framalibre + shortcut: frl + disabled: true + + # - name: searx + # engine: searx_engine + # shortcut: se + # instance_urls : + # - http://127.0.0.1:8888/ + # - ... + # disabled: true + + - name: semantic scholar + engine: semantic_scholar + disabled: true + shortcut: se + + # Spotify needs API credentials + # - name: spotify + # engine: spotify + # shortcut: stf + # api_client_id: ******* + # api_client_secret: ******* + + # - name: solr + # engine: solr + # shortcut: slr + # base_url: http://localhost:8983 + # collection: collection_name + # sort: '' # sorting: asc or desc + # field_list: '' # comma separated list of field names to display on the UI + # default_fields: '' # default field to query + # query_fields: '' # query fields + # enable_http: true + + # - name: springer nature + # engine: springer + # # get your API key from: https://dev.springernature.com/signup + # # working API key, for test & debug: "a69685087d07eca9f13db62f65b8f601" + # api_key: 'unset' + # shortcut: springer + # timeout: 15.0 + + - name: startpage + engine: startpage + shortcut: sp + timeout: 6.0 + disabled: true + additional_tests: + rosebud: *test_rosebud + + - name: tokyotoshokan + engine: tokyotoshokan + shortcut: tt + timeout: 6.0 + disabled: true + + - name: solidtorrents + engine: solidtorrents + shortcut: solid + timeout: 4.0 + disabled: false + base_url: + - https://solidtorrents.net + - https://solidtorrents.eu + - https://solidtorrents.to + - https://bitsearch.to + + # For this demo of the sqlite engine download: + # https://liste.mediathekview.de/filmliste-v2.db.bz2 + # and unpack into searx/data/filmliste-v2.db + # Query to test: "!demo concert" + # + # - name: demo + # engine: sqlite + # shortcut: demo + # categories: general + # result_template: default.html + # database: searx/data/filmliste-v2.db + # query_str: >- + # SELECT title || ' (' || time(duration, 'unixepoch') || ')' AS title, + # COALESCE( NULLIF(url_video_hd,''), NULLIF(url_video_sd,''), url_video) AS url, + # description AS content + # FROM film + # WHERE title LIKE :wildcard OR description LIKE :wildcard + # ORDER BY duration DESC + # disabled: false + + # Requires Tor + - name: torch + engine: xpath + paging: true + search_url: + http://xmh57jrknzkhv6y3ls3ubitzfqnkrwxhopf5aygthi7d6rplyvk3noyd.onion/cgi-bin/omega/omega?P={query}&DEFAULTOP=and + results_xpath: //table//tr + url_xpath: ./td[2]/a + title_xpath: ./td[2]/b + content_xpath: ./td[2]/small + categories: onions + enable_http: true + shortcut: tch + + # torznab engine lets you query any torznab compatible indexer. Using this + # engine in combination with Jackett (https://github.com/Jackett/Jackett) + # opens the possibility to query a lot of public and private indexers directly + # from SearXNG. + # - name: torznab + # engine: torznab + # shortcut: trz + # base_url: http://localhost:9117/api/v2.0/indexers/all/results/torznab + # enable_http: true # if using localhost + # api_key: xxxxxxxxxxxxxxx + # # https://github.com/Jackett/Jackett/wiki/Jackett-Categories + # torznab_categories: # optional + # - 2000 + # - 5000 + + - name: twitter + shortcut: tw + engine: twitter + disabled: true + + # maybe in a fun category + # - name: uncyclopedia + # engine: mediawiki + # shortcut: unc + # base_url: https://uncyclopedia.wikia.com/ + # number_of_results: 5 + + # tmp suspended - too slow, too many errors + # - name: urbandictionary + # engine : xpath + # search_url : https://www.urbandictionary.com/define.php?term={query} + # url_xpath : //*[@class="word"]/@href + # title_xpath : //*[@class="def-header"] + # content_xpath: //*[@class="meaning"] + # shortcut: ud + + - name: unsplash + engine: unsplash + shortcut: us + + - name: yahoo + engine: yahoo + shortcut: yh + disabled: true + + - name: yahoo news + engine: yahoo_news + shortcut: yhn + + - name: youtube + shortcut: yt + # You can use the engine using the official stable API, but you need an API + # key See: https://console.developers.google.com/project + # + # engine: youtube_api + # api_key: 'apikey' # required! + # + # Or you can use the html non-stable engine, activated by default + engine: youtube_noapi + + - name: dailymotion + engine: dailymotion + shortcut: dm + + - name: vimeo + engine: vimeo + shortcut: vm + + - name: wiby + engine: json_engine + paging: true + search_url: https://wiby.me/json/?q={query}&p={pageno} + url_query: URL + title_query: Title + content_query: Snippet + categories: [general, web] + shortcut: wib + disabled: true + about: + website: https://wiby.me/ + + - name: alexandria + engine: json_engine + shortcut: alx + categories: general + paging: true + search_url: https://api.alexandria.org/?a=1&q={query}&p={pageno} + results_query: results + title_query: title + url_query: url + content_query: snippet + timeout: 1.5 + disabled: true + about: + website: https://alexandria.org/ + official_api_documentation: https://github.com/alexandria-org/alexandria-api/raw/master/README.md + use_official_api: true + require_api_key: false + results: JSON + + - name: wikibooks + engine: mediawiki + shortcut: wb + categories: general + base_url: "https://{language}.wikibooks.org/" + number_of_results: 5 + search_type: text + disabled: true + about: + website: https://www.wikibooks.org/ + wikidata_id: Q367 + + - name: wikinews + engine: mediawiki + shortcut: wn + categories: news + base_url: "https://{language}.wikinews.org/" + number_of_results: 5 + search_type: text + disabled: true + about: + website: https://www.wikinews.org/ + wikidata_id: Q964 + + - name: wikiquote + engine: mediawiki + shortcut: wq + categories: general + base_url: "https://{language}.wikiquote.org/" + number_of_results: 5 + search_type: text + disabled: true + additional_tests: + rosebud: *test_rosebud + about: + website: https://www.wikiquote.org/ + wikidata_id: Q369 + + - name: wikisource + engine: mediawiki + shortcut: ws + categories: general + base_url: "https://{language}.wikisource.org/" + number_of_results: 5 + search_type: text + disabled: true + about: + website: https://www.wikisource.org/ + wikidata_id: Q263 + + - name: wiktionary + engine: mediawiki + shortcut: wt + categories: [dictionaries] + base_url: "https://{language}.wiktionary.org/" + number_of_results: 5 + search_type: text + disabled: false + about: + website: https://www.wiktionary.org/ + wikidata_id: Q151 + + - name: wikiversity + engine: mediawiki + shortcut: wv + categories: general + base_url: "https://{language}.wikiversity.org/" + number_of_results: 5 + search_type: text + disabled: true + about: + website: https://www.wikiversity.org/ + wikidata_id: Q370 + + - name: wikivoyage + engine: mediawiki + shortcut: wy + categories: general + base_url: "https://{language}.wikivoyage.org/" + number_of_results: 5 + search_type: text + disabled: true + about: + website: https://www.wikivoyage.org/ + wikidata_id: Q373 + + - name: wolframalpha + shortcut: wa + # You can use the engine using the official stable API, but you need an API + # key. See: https://products.wolframalpha.com/api/ + # + # engine: wolframalpha_api + # api_key: '' + # + # Or you can use the html non-stable engine, activated by default + engine: wolframalpha_noapi + timeout: 6.0 + categories: [] + + - name: dictzone + engine: dictzone + shortcut: dc + + - name: mymemory translated + engine: translated + shortcut: tl + timeout: 5.0 + disabled: false + # You can use without an API key, but you are limited to 1000 words/day + # See: https://mymemory.translated.net/doc/usagelimits.php + # api_key: '' + + # Required dependency: mysql-connector-python + # - name: mysql + # engine: mysql_server + # database: mydatabase + # username: user + # password: pass + # limit: 10 + # query_str: 'SELECT * from mytable WHERE fieldname=%(query)s' + # shortcut: mysql + + - name: 1337x + engine: 1337x + shortcut: 1337x + disabled: true + + - name: duden + engine: duden + shortcut: du + disabled: true + + - name: seznam + shortcut: szn + engine: seznam + disabled: true + + # - name: deepl + # engine: deepl + # shortcut: dpl + # # You can use the engine using the official stable API, but you need an API key + # # See: https://www.deepl.com/pro-api?cta=header-pro-api + # api_key: '' # required! + # timeout: 5.0 + # disabled: true + + - name: mojeek + shortcut: mjk + engine: xpath + paging: true + categories: [general, web] + search_url: https://www.mojeek.com/search?q={query}&s={pageno}&lang={lang}&lb={lang} + results_xpath: //ul[@class="results-standard"]/li/a[@class="ob"] + url_xpath: ./@href + title_xpath: ../h2/a + content_xpath: ..//p[@class="s"] + suggestion_xpath: //div[@class="top-info"]/p[@class="top-info spell"]/em/a + first_page_num: 0 + page_size: 10 + disabled: true + about: + website: https://www.mojeek.com/ + wikidata_id: Q60747299 + official_api_documentation: https://www.mojeek.com/services/api.html/ + use_official_api: false + require_api_key: false + results: HTML + + - name: naver + shortcut: nvr + categories: [general, web] + engine: xpath + paging: true + search_url: https://search.naver.com/search.naver?where=webkr&sm=osp_hty&ie=UTF-8&query={query}&start={pageno} + url_xpath: //a[@class="link_tit"]/@href + title_xpath: //a[@class="link_tit"] + content_xpath: //a[@class="total_dsc"]/div + first_page_num: 1 + page_size: 10 + disabled: true + about: + website: https://www.naver.com/ + wikidata_id: Q485639 + official_api_documentation: https://developers.naver.com/docs/nmt/examples/ + use_official_api: false + require_api_key: false + results: HTML + language: ko + + - name: rubygems + shortcut: rbg + engine: xpath + paging: true + search_url: https://rubygems.org/search?page={pageno}&query={query} + results_xpath: /html/body/main/div/a[@class="gems__gem"] + url_xpath: ./@href + title_xpath: ./span/h2 + content_xpath: ./span/p + suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a + first_page_num: 1 + categories: [it, packages] + disabled: true + about: + website: https://rubygems.org/ + wikidata_id: Q1853420 + official_api_documentation: https://guides.rubygems.org/rubygems-org-api/ + use_official_api: false + require_api_key: false + results: HTML + + - name: peertube + engine: peertube + shortcut: ptb + paging: true + # alternatives see: https://instances.joinpeertube.org/instances + # base_url: https://tube.4aem.com + categories: videos + disabled: true + timeout: 6.0 + + - name: mediathekviewweb + engine: mediathekviewweb + shortcut: mvw + disabled: true + + # - name: yacy + # engine: yacy + # shortcut: ya + # base_url: http://localhost:8090 + # required if you aren't using HTTPS for your local yacy instance' + # enable_http: true + # number_of_results: 5 + # timeout: 3.0 + + - name: rumble + engine: rumble + shortcut: ru + base_url: https://rumble.com/ + paging: true + categories: videos + disabled: true + + - name: wordnik + engine: wordnik + shortcut: def + base_url: https://www.wordnik.com/ + categories: [dictionaries] + timeout: 5.0 + disabled: false + + - name: woxikon.de synonyme + engine: xpath + shortcut: woxi + categories: [dictionaries] + timeout: 5.0 + disabled: true + search_url: https://synonyme.woxikon.de/synonyme/{query}.php + url_xpath: //div[@class="upper-synonyms"]/a/@href + content_xpath: //div[@class="synonyms-list-group"] + title_xpath: //div[@class="upper-synonyms"]/a + no_result_for_http_status: [404] + about: + website: https://www.woxikon.de/ + wikidata_id: # No Wikidata ID + use_official_api: false + require_api_key: false + results: HTML + language: de + + - name: sjp.pwn + engine: sjp + shortcut: sjp + base_url: https://sjp.pwn.pl/ + timeout: 5.0 + disabled: true + + # wikimini: online encyclopedia for children + # The fulltext and title parameter is necessary for Wikimini because + # sometimes it will not show the results and redirect instead + - name: wikimini + engine: xpath + shortcut: wkmn + search_url: https://fr.wikimini.org/w/index.php?search={query}&title=Sp%C3%A9cial%3ASearch&fulltext=Search + url_xpath: //li/div[@class="mw-search-result-heading"]/a/@href + title_xpath: //li//div[@class="mw-search-result-heading"]/a + content_xpath: //li/div[@class="searchresult"] + categories: general + disabled: true + about: + website: https://wikimini.org/ + wikidata_id: Q3568032 + use_official_api: false + require_api_key: false + results: HTML + language: fr + + - name: wttr.in + engine: wttr + shortcut: wttr + timeout: 9.0 + + - name: brave + shortcut: brave + engine: xpath + paging: true + time_range_support: true + first_page_num: 0 + time_range_url: "&tf={time_range_val}" + search_url: https://search.brave.com/search?q={query}&offset={pageno}&spellcheck=1{time_range} + url_xpath: //a[@class="result-header"]/@href + title_xpath: //span[@class="snippet-title"] + content_xpath: //p[1][@class="snippet-description"] + suggestion_xpath: //div[@class="text-gray h6"]/a + time_range_map: + day: 'pd' + week: 'pw' + month: 'pm' + year: 'py' + categories: [general, web] + disabled: true + headers: + Accept-Encoding: gzip, deflate + about: + website: https://brave.com/search/ + wikidata_id: Q107355971 + use_official_api: false + require_api_key: false + results: HTML + + - name: petalsearch + shortcut: pts + engine: xpath + paging: true + search_url: https://petalsearch.com/search?query={query}&pn={pageno} + url_xpath: //div[@class='card-source'] + title_xpath: //div[@class='title-name'] + content_xpath: //div[@class='webpage-text'] + first_page_num: 1 + disabled: true + headers: + User-Agent: Mozilla/5.0 (Linux; Android 7.0;) \ + AppleWebKit/537.36 (KHTML, like Gecko) \ + Mobile Safari/537.36 (compatible; PetalBot;+https://webmaster.petalsearch.com/site/petalbot) + about: + website: https://petalsearch.com/ + wikidata_id: Q104399280 + use_official_api: false + require_api_key: false + results: HTML + + - name: petalsearch images + engine: petal_images + shortcut: ptsi + disabled: true + timeout: 3.0 + + - name: lib.rs + shortcut: lrs + engine: xpath + search_url: https://lib.rs/search?q={query} + results_xpath: /html/body/main/div/ol/li/a + url_xpath: ./@href + title_xpath: ./div[@class="h"]/h4 + content_xpath: ./div[@class="h"]/p + categories: [it, packages] + disabled: true + about: + website: https://lib.rs + wikidata_id: Q113486010 + use_official_api: false + require_api_key: false + results: HTML + + - name: sourcehut + shortcut: srht + engine: xpath + paging: true + search_url: https://sr.ht/projects?page={pageno}&search={query} + results_xpath: (//div[@class="event-list"])[1]/div[@class="event"] + url_xpath: ./h4/a[2]/@href + title_xpath: ./h4/a[2] + content_xpath: ./p + first_page_num: 1 + categories: [it, repos] + disabled: true + about: + website: https://sr.ht + wikidata_id: Q78514485 + official_api_documentation: https://man.sr.ht/ + use_official_api: false + require_api_key: false + results: HTML + + - name: goo + shortcut: goo + engine: xpath + paging: true + search_url: https://search.goo.ne.jp/web.jsp?MT={query}&FR={pageno}0 + url_xpath: //div[@class="result"]/p[@class='title fsL1']/a/@href + title_xpath: //div[@class="result"]/p[@class='title fsL1']/a + content_xpath: //p[contains(@class,'url fsM')]/following-sibling::p + first_page_num: 0 + categories: [general, web] + disabled: true + timeout: 4.0 + about: + website: https://search.goo.ne.jp + wikidata_id: Q249044 + use_official_api: false + require_api_key: false + results: HTML + language: ja + +doi_resolvers: + oadoi.org: 'https://oadoi.org/' + doi.org: 'https://doi.org/' + doai.io: 'https://dissem.in/' + sci-hub.se: 'https://sci-hub.se/' + sci-hub.st: 'https://sci-hub.st/' + sci-hub.ru: 'https://sci-hub.ru/' + +default_doi_resolver: 'oadoi.org' diff --git a/ansible/roles/authentik/vars/main.yml b/ansible/roles/authentik/vars/main.yml new file mode 100644 index 0000000..c9b5a2d --- /dev/null +++ b/ansible/roles/authentik/vars/main.yml @@ -0,0 +1,10 @@ +searxng_secret_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 33656138666464373665663339363665346566613637626131363335336535313131333265646539 + 3037373439643964343139383764386364623961383737610a313063613736316437366239663238 + 65333735633661316463336665353138623264396534383865363134613165636164303765356265 + 3865626366613966660a313738353339313133393765643136306361373061366132373130656531 + 61396230346333346636356562353733623332333662653164373630626339376433353663313862 + 61303230613135336662313531313836363466623162666233646231616333643536303233616231 + 62353866333465646162633738383866363338383932623335353038393130323932343363653233 + 62663465386661663262 diff --git a/ansible/roles/gitea/tasks/main.yml b/ansible/roles/gitea/tasks/main.yml index 8487b9c..2a46049 100644 --- a/ansible/roles/gitea/tasks/main.yml +++ b/ansible/roles/gitea/tasks/main.yml @@ -13,8 +13,8 @@ Match User git AuthorizedKeysCommandUser git AuthorizedKeysCommand /usr/bin/docker exec -i gitea /usr/local/bin/gitea keys -c /etc/gitea/app.ini -e git -u %u -t %t -k %k + validate: /usr/sbin/sshd -T -f %s become: true - validate: /usr/sbin/sshd -T -f %s notify: restart sshd ############################# diff --git a/ansible/roles/tautulli/files/kill_stream.py b/ansible/roles/tautulli/files/kill_stream.py new file mode 100644 index 0000000..4d3875c --- /dev/null +++ b/ansible/roles/tautulli/files/kill_stream.py @@ -0,0 +1,639 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Description: Use conditions to kill a stream +Author: Blacktwin, Arcanemagus, Samwiseg0, JonnyWong16, DirtyCajunRice + +Adding the script to Tautulli: +Tautulli > Settings > Notification Agents > Add a new notification agent > + Script + +Configuration: +Tautulli > Settings > Notification Agents > New Script > Configuration: + + Script Folder: /path/to/your/scripts + Script File: ./kill_stream.py (Should be selectable in a dropdown list) + Script Timeout: {timeout} + Description: Kill stream(s) + Save + +Triggers: +Tautulli > Settings > Notification Agents > New Script > Triggers: + + Check: Playback Start and/or Playback Pause + Save + +Conditions: +Tautulli > Settings > Notification Agents > New Script > Conditions: + + Set Conditions: [{condition} | {operator} | {value} ] + Save + +Script Arguments: +Tautulli > Settings > Notification Agents > New Script > Script Arguments: + + Select: Playback Start, Playback Pause + Arguments: --jbop SELECTOR --userId {user_id} --username {username} + --sessionId {session_id} --notify notifierID + --interval 30 --limit 1200 + --richMessage RICH_TYPE --serverName {server_name} + --plexUrl {plex_url} --posterUrl {poster_url} + --richColor '#E5A00D' + --killMessage 'Your message here.' + + Save + Close +""" +from __future__ import print_function +from __future__ import unicode_literals + + +from builtins import object +from builtins import str +import os +import sys +import json +import time +import argparse +from datetime import datetime +from requests import Session +from requests.adapters import HTTPAdapter +from requests.exceptions import RequestException + + +TAUTULLI_URL = '' +TAUTULLI_APIKEY = '' +TAUTULLI_PUBLIC_URL = '' +TAUTULLI_URL = os.getenv('TAUTULLI_URL', TAUTULLI_URL) +TAUTULLI_PUBLIC_URL = os.getenv('TAUTULLI_PUBLIC_URL', TAUTULLI_PUBLIC_URL) +TAUTULLI_APIKEY = os.getenv('TAUTULLI_APIKEY', TAUTULLI_APIKEY) +TAUTULLI_ENCODING = os.getenv('TAUTULLI_ENCODING', 'UTF-8') +VERIFY_SSL = False + +if TAUTULLI_PUBLIC_URL != '/': + # Check to see if there is a public URL set in Tautulli + TAUTULLI_LINK = TAUTULLI_PUBLIC_URL +else: + TAUTULLI_LINK = TAUTULLI_URL + +SUBJECT_TEXT = "Tautulli has killed a stream." +BODY_TEXT = "Killed session ID '{id}'. Reason: {message}" +BODY_TEXT_USER = "Killed {user}'s stream. Reason: {message}." + + +SELECTOR = ['stream', 'allStreams', 'paused'] + +RICH_TYPE = ['discord', 'slack'] + +TAUTULLI_ICON = 'https://github.com/Tautulli/Tautulli/raw/master/data/interfaces/default/images/logo-circle.png' + + +def utc_now_iso(): + """Get current time in ISO format""" + utcnow = datetime.utcnow() + + return utcnow.isoformat() + + +def hex_to_int(value): + """Convert hex value to integer""" + try: + return int(value, 16) + except (ValueError, TypeError): + return 0 + + +def arg_decoding(arg): + """Decode args, encode UTF-8""" + if sys.version_info[0] < 3: + return arg.decode(TAUTULLI_ENCODING).encode('UTF-8') + else: + return arg + + +def debug_dump_vars(): + """Dump parameters for debug""" + print('Tautulli URL - ' + TAUTULLI_URL) + print('Tautulli Public URL - ' + TAUTULLI_PUBLIC_URL) + print('Verify SSL - ' + str(VERIFY_SSL)) + print('Tautulli API key - ' + TAUTULLI_APIKEY[-4:] + .rjust(len(TAUTULLI_APIKEY), "x")) + + +def get_all_streams(tautulli, user_id=None): + """Get a list of all current streams. + + Parameters + ---------- + user_id : int + The ID of the user to grab sessions for. + tautulli : obj + Tautulli object. + Returns + ------- + objects + The of stream objects. + """ + sessions = tautulli.get_activity()['sessions'] + + if user_id: + streams = [Stream(session=s) for s in sessions if s['user_id'] == user_id] + else: + streams = [Stream(session=s) for s in sessions] + + return streams + + +def notify(all_opts, message, kill_type=None, stream=None, tautulli=None): + """Decides which notifier type to use""" + if all_opts.notify and all_opts.richMessage: + rich_notify(all_opts.notify, all_opts.richMessage, all_opts.richColor, kill_type, + all_opts.serverName, all_opts.plexUrl, all_opts.posterUrl, message, stream, tautulli) + elif all_opts.notify: + basic_notify(all_opts.notify, all_opts.sessionId, all_opts.username, message, stream, tautulli) + + +def rich_notify(notifier_id, rich_type, color=None, kill_type=None, server_name=None, + plex_url=None, poster_url=None, message=None, stream=None, tautulli=None): + """Decides which rich notifier type to use. Set default values for empty variables + + Parameters + ---------- + notifier_id : int + The ID of the user to grab sessions for. + rich_type : str + Contains 'discord' or 'slack'. + color : Union[int, str] + Hex string or integer representation of color. + kill_type : str + The kill type used. + server_name : str + The name of the plex server. + plex_url : str + Plex media URL. + poster_url : str + The media poster URL. + message : str + Message sent to the client. + stream : obj + Stream object. + tautulli : obj + Tautulli object. + """ + notification = Notification(notifier_id, None, None, tautulli, stream) + # Initialize Variables + title = '' + footer = '' + # Set a default server_name if none is provided + if server_name is None: + server_name = 'Plex Server' + + # Set a default color if none is provided + if color is None: + color = '#E5A00D' + + # Set a default plexUrl if none is provided + if plex_url is None: + plex_url = 'https://app.plex.tv' + + # Set a default posterUrl if none is provided + if poster_url is None: + poster_url = TAUTULLI_ICON + + # Set a default message if none is provided + if message is None: + message = 'The server owner has ended the stream.' + + if kill_type == 'Stream': + title = "Killed {}'s stream.".format(stream.friendly_name) + footer = '{} | Kill {}'.format(server_name, kill_type) + + elif kill_type == 'Paused': + title = "Killed {}'s paused stream.".format(stream.friendly_name) + footer = '{} | Kill {}'.format(server_name, kill_type) + + elif kill_type == 'All Streams': + title = "Killed {}'s stream.".format(stream.friendly_name) + footer = '{} | Kill {}'.format(server_name, kill_type) + poster_url = TAUTULLI_ICON + plex_url = 'https://app.plex.tv' + + if rich_type == 'discord': + color = hex_to_int(color.lstrip('#')) + notification.send_discord(title, color, poster_url, plex_url, message, footer) + + elif rich_type == 'slack': + notification.send_slack(title, color, poster_url, plex_url, message, footer) + + +def basic_notify(notifier_id, session_id, username=None, message=None, stream=None, tautulli=None): + """Basic notifier""" + notification = Notification(notifier_id, SUBJECT_TEXT, BODY_TEXT, tautulli, stream) + + if username: + body = BODY_TEXT_USER.format(user=username, + message=message) + else: + body = BODY_TEXT.format(id=session_id, message=message) + notification.send(SUBJECT_TEXT, body) + + +class Tautulli(object): + def __init__(self, url, apikey, verify_ssl=False, debug=None): + self.url = url + self.apikey = apikey + self.debug = debug + + self.session = Session() + self.adapters = HTTPAdapter(max_retries=3, + pool_connections=1, + pool_maxsize=1, + pool_block=True) + self.session.mount('http://', self.adapters) + self.session.mount('https://', self.adapters) + + # Ignore verifying the SSL certificate + if verify_ssl is False: + self.session.verify = False + # Disable the warning that the request is insecure, we know that... + import urllib3 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + def _call_api(self, cmd, payload, method='GET'): + payload['cmd'] = cmd + payload['apikey'] = self.apikey + + try: + response = self.session.request(method, self.url + '/api/v2', params=payload) + except RequestException as e: + print("Tautulli request failed for cmd '{}'. Invalid Tautulli URL? Error: {}".format(cmd, e)) + if self.debug: + traceback.print_exc() + return + + try: + response_json = response.json() + except ValueError: + print( + "Failed to parse json response for Tautulli API cmd '{}': {}" + .format(cmd, response.content)) + return + + if response_json['response']['result'] == 'success': + if self.debug: + print("Successfully called Tautulli API cmd '{}'".format(cmd)) + return response_json['response']['data'] + else: + error_msg = response_json['response']['message'] + print("Tautulli API cmd '{}' failed: {}".format(cmd, error_msg)) + return + + def get_activity(self, session_key=None, session_id=None): + """Call Tautulli's get_activity api endpoint""" + payload = {} + + if session_key: + payload['session_key'] = session_key + elif session_id: + payload['session_id'] = session_id + + return self._call_api('get_activity', payload) + + def notify(self, notifier_id, subject, body): + """Call Tautulli's notify api endpoint""" + payload = {'notifier_id': notifier_id, + 'subject': subject, + 'body': body} + + return self._call_api('notify', payload) + + def terminate_session(self, session_key=None, session_id=None, message=''): + """Call Tautulli's terminate_session api endpoint""" + payload = {} + + if session_key: + payload['session_key'] = session_key + elif session_id: + payload['session_id'] = session_id + + if message: + payload['message'] = message + + return self._call_api('terminate_session', payload) + + +class Stream(object): + def __init__(self, session_id=None, user_id=None, username=None, tautulli=None, session=None): + self.state = None + self.ip_address = None + self.session_id = session_id + self.user_id = user_id + self.username = username + self.session_exists = False + self.tautulli = tautulli + + if session is not None: + self._set_stream_attributes(session) + + def _set_stream_attributes(self, session): + for k, v in session.items(): + setattr(self, k, v) + + def get_all_stream_info(self): + """Get all stream info from Tautulli.""" + session = self.tautulli.get_activity(session_id=self.session_id) + if session: + self._set_stream_attributes(session) + self.session_exists = True + else: + self.session_exists = False + + def terminate(self, message=''): + """Calls Tautulli to terminate the session. + + Parameters + ---------- + message : str + The message to use if the stream is terminated. + """ + self.tautulli.terminate_session(session_id=self.session_id, message=message) + + def terminate_long_pause(self, message, limit, interval): + """Kills the session if it is paused for longer than seconds. + + Parameters + ---------- + message : str + The message to use if the stream is terminated. + limit : int + The number of seconds the session is allowed to remain paused before it + is terminated. + interval : int + The amount of time to wait between checks of the session state. + """ + start = datetime.now() + checked_time = 0 + # Continue checking 2 intervals past the allowed limit in order to + # account for system variances. + check_limit = limit + (interval * 2) + + while checked_time < check_limit: + self.get_all_stream_info() + + if self.session_exists is False: + sys.stdout.write( + "Session '{}' from user '{}' is no longer active " + .format(self.session_id, self.username) + + "on the server, stopping monitoring.\n") + return False + + now = datetime.now() + checked_time = (now - start).total_seconds() + + if self.state == 'paused': + if checked_time >= limit: + self.terminate(message) + sys.stdout.write( + "Session '{}' from user '{}' has been killed.\n" + .format(self.session_id, self.username)) + return True + else: + time.sleep(interval) + + elif self.state == 'playing' or self.state == 'buffering': + sys.stdout.write( + "Session '{}' from user '{}' has been resumed, " + .format(self.session_id, self.username) + + "stopping monitoring.\n") + return False + + +class Notification(object): + def __init__(self, notifier_id, subject, body, tautulli, stream): + self.notifier_id = notifier_id + self.subject = subject + self.body = body + + self.tautulli = tautulli + self.stream = stream + + def send(self, subject='', body=''): + """Send to Tautulli notifier. + + Parameters + ---------- + subject : str + Subject of the message. + body : str + Body of the message. + """ + subject = subject or self.subject + body = body or self.body + self.tautulli.notify(notifier_id=self.notifier_id, subject=subject, body=body) + + def send_discord(self, title, color, poster_url, plex_url, message, footer): + """Build the Discord message. + + Parameters + ---------- + title : str + The title of the message. + color : int + The color of the message + poster_url : str + The media poster URL. + plex_url : str + Plex media URL. + message : str + Message sent to the player. + footer : str + Footer of the message. + """ + discord_message = { + "embeds": [ + { + "author": { + "icon_url": TAUTULLI_ICON, + "name": "Tautulli", + "url": TAUTULLI_LINK.rstrip('/') + }, + "color": color, + "fields": [ + { + "inline": True, + "name": "User", + "value": self.stream.friendly_name + }, + { + "inline": True, + "name": "Session Key", + "value": self.stream.session_key + }, + { + "inline": True, + "name": "Watching", + "value": self.stream.full_title + }, + { + "inline": False, + "name": "Message Sent", + "value": message + } + ], + "thumbnail": { + "url": poster_url + }, + "title": title, + "timestamp": utc_now_iso(), + "url": plex_url, + "footer": { + "text": footer + } + + } + + ], + } + + discord_message = json.dumps(discord_message, sort_keys=True, + separators=(',', ': ')) + self.send(body=discord_message) + + def send_slack(self, title, color, poster_url, plex_url, message, footer): + """Build the Slack message. + + Parameters + ---------- + title : str + The title of the message. + color : int + The color of the message + poster_url : str + The media poster URL. + plex_url : str + Plex media URL. + message : str + Message sent to the player. + footer : str + Footer of the message. + """ + slack_message = { + "attachments": [ + { + "title": title, + "title_link": plex_url, + "author_icon": TAUTULLI_ICON, + "author_name": "Tautulli", + "author_link": TAUTULLI_LINK.rstrip('/'), + "color": color, + "fields": [ + { + "title": "User", + "value": self.stream.friendly_name, + "short": True + }, + { + "title": "Session Key", + "value": self.stream.session_key, + "short": True + }, + { + "title": "Watching", + "value": self.stream.full_title, + "short": True + }, + { + "title": "Message Sent", + "value": message, + "short": False + } + ], + "thumb_url": poster_url, + "footer": footer, + "ts": time.time() + } + + ], + } + + slack_message = json.dumps(slack_message, sort_keys=True, + separators=(',', ': ')) + self.send(body=slack_message) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Killing Plex streams from Tautulli.") + parser.add_argument('--jbop', required=True, choices=SELECTOR, + help='Kill selector.\nChoices: (%(choices)s)') + parser.add_argument('--userId', type=int, + help='The unique identifier for the user.') + parser.add_argument('--username', type=arg_decoding, + help='The username of the person streaming.') + parser.add_argument('--sessionId', + help='The unique identifier for the stream.') + parser.add_argument('--notify', type=int, + help='Notification Agent ID number to Agent to ' + + 'send notification.') + parser.add_argument('--limit', type=int, default=(20 * 60), # 20 minutes + help='The time session is allowed to remain paused.') + parser.add_argument('--interval', type=int, default=30, + help='The seconds between paused session checks.') + parser.add_argument('--killMessage', nargs='+', type=arg_decoding, + help='Message to send to user whose stream is killed.') + parser.add_argument('--richMessage', type=arg_decoding, choices=RICH_TYPE, + help='Rich message type selector.\nChoices: (%(choices)s)') + parser.add_argument('--serverName', type=arg_decoding, + help='Plex Server Name') + parser.add_argument('--plexUrl', type=arg_decoding, + help='URL to plex media') + parser.add_argument('--posterUrl', type=arg_decoding, + help='Poster URL of the media') + parser.add_argument('--richColor', type=arg_decoding, + help='Color of the rich message') + parser.add_argument("--debug", action='store_true', + help='Enable debug messages.') + + opts = parser.parse_args() + + if not opts.sessionId and opts.jbop != 'allStreams': + sys.stderr.write("No sessionId provided! Is this synced content?\n") + sys.exit(1) + + if opts.debug: + # Import traceback to get more detailed information + import traceback + # Dump the ENVs passed from tautulli + debug_dump_vars() + + # Create a Tautulli instance + tautulli_server = Tautulli(TAUTULLI_URL.rstrip('/'), TAUTULLI_APIKEY, VERIFY_SSL, opts.debug) + + # Create initial Stream object with basic info + tautulli_stream = Stream(opts.sessionId, opts.userId, opts.username, tautulli_server) + + # Only pull all stream info if using richMessage + if opts.notify and opts.richMessage: + tautulli_stream.get_all_stream_info() + + # Set a default message if none is provided + if opts.killMessage: + kill_message = ' '.join(opts.killMessage) + else: + kill_message = 'The server owner has ended the stream.' + + if opts.jbop == 'stream': + tautulli_stream.terminate(kill_message) + notify(opts, kill_message, 'Stream', tautulli_stream, tautulli_server) + + elif opts.jbop == 'allStreams': + all_streams = get_all_streams(tautulli_server, opts.userId) + for a_stream in all_streams: + tautulli_server.terminate_session(session_id=a_stream.session_id, message=kill_message) + notify(opts, kill_message, 'All Streams', a_stream, tautulli_server) + + elif opts.jbop == 'paused': + killed_stream = tautulli_stream.terminate_long_pause(kill_message, opts.limit, opts.interval) + if killed_stream: + notify(opts, kill_message, 'Paused', tautulli_stream, tautulli_server) diff --git a/ansible/roles/tautulli/tasks/main.yml b/ansible/roles/tautulli/tasks/main.yml index 5c0fdfc..e15c86a 100644 --- a/ansible/roles/tautulli/tasks/main.yml +++ b/ansible/roles/tautulli/tasks/main.yml @@ -22,6 +22,16 @@ validate: docker-compose -f %s config become: true +- name: Install script files + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ data_dir }}/tautulli/scripts" + mode: "{{ docker_compose_file_mask }}" + owner: "{{ primary_user }}" + with_fileglob: + - "files/*" + become: true + - name: Start docker container community.docker.docker_compose: project_src: "{{ install_directory }}/{{ role_name }}"