From 6544deb089b7c077be2798bc07a01981a24d2827 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 11:35:17 +0100 Subject: [PATCH 1/6] feat: add basic_auth roda plugin --- app.rb | 3 +++ roda_plugins/basic_auth.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 roda_plugins/basic_auth.rb diff --git a/app.rb b/app.rb index 126782c2..49c324ba 100644 --- a/app.rb +++ b/app.rb @@ -71,6 +71,9 @@ class App < Roda plugin :render, escape: true, layout: 'layout' plugin :typecast_params + require_relative 'roda_plugins/basic_auth' + plugin :basic_auth + route do |r| path = RequestPath.new(request) diff --git a/roda_plugins/basic_auth.rb b/roda_plugins/basic_auth.rb new file mode 100644 index 00000000..99475f5e --- /dev/null +++ b/roda_plugins/basic_auth.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Roda + module RodaPlugins + module BasicAuth + # def self.configure(app, opts = {}) + # plugin_opts = (app.opts[:http_auth] ||= {}) + # app.opts[:http_auth] = plugin_opts.merge(opts) + # app.opts[:http_auth].freeze + # end + + # TODO: secure compare + + ## + # ... authenticator + module RequestMethods + def basic_auth(&authenticator) + raise ArgumentError, 'must be used with a block' unless authenticator + + auth = Rack::Auth::Basic::Request.new(env) + + if auth.provided? && yield(*auth.credentials) + env['REMOTE_USER'] = auth.username + else + response.status = 401 + # request.block_result(instance_exec(request, &opts[:unauthorized])) + request.halt response.finish + end + end + end + end + register_plugin(:basic_auth, BasicAuth) + end +end From f224426e80eb4371696fc4db106aa1764fd37e50 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 12:27:21 +0100 Subject: [PATCH 2/6] make basic_auth work --- app.rb | 5 ++++- roda_plugins/basic_auth.rb | 42 +++++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app.rb b/app.rb index 49c324ba..06b8a98d 100644 --- a/app.rb +++ b/app.rb @@ -7,6 +7,8 @@ require_relative './app/local_config' require_relative './app/html2rss_facade' +require_relative 'roda_plugins/basic_auth' + module App ## # This app uses html2rss and serves the feeds via HTTP. @@ -71,7 +73,6 @@ class App < Roda plugin :render, escape: true, layout: 'layout' plugin :typecast_params - require_relative 'roda_plugins/basic_auth' plugin :basic_auth route do |r| @@ -84,6 +85,8 @@ class App < Roda r.public r.get 'health_check.txt' do |_| + basic_auth realm: 'health_check', username: 'foo', password: 'bar' + HttpCache.expires_now(response) HealthCheck.run diff --git a/roda_plugins/basic_auth.rb b/roda_plugins/basic_auth.rb index 99475f5e..366cf3a0 100644 --- a/roda_plugins/basic_auth.rb +++ b/roda_plugins/basic_auth.rb @@ -1,34 +1,42 @@ # frozen_string_literal: true +require 'openssl' + class Roda + ## + # Roda's plugin namespace module RodaPlugins + ## + # Basic Auth plugin's namespace module BasicAuth - # def self.configure(app, opts = {}) - # plugin_opts = (app.opts[:http_auth] ||= {}) - # app.opts[:http_auth] = plugin_opts.merge(opts) - # app.opts[:http_auth].freeze - # end + def self.authorize(username, password, auth) + given_user, given_password = auth.credentials + + secure_compare(username.to_s, given_user) & secure_compare(password.to_s, given_password) + end - # TODO: secure compare + def self.secure_compare(left, right) + left.bytesize == right.bytesize && OpenSSL.fixed_length_secure_compare(left, right) + end ## - # ... authenticator - module RequestMethods - def basic_auth(&authenticator) - raise ArgumentError, 'must be used with a block' unless authenticator + # Methods here become instance methods in the roda application. + module InstanceMethods + def basic_auth(realm:, username:, password:) + raise ArgumentError, 'realm must not be a blank string' if realm.to_s.strip == '' + + response.headers['WWW-Authenticate'] = "Basic realm=#{realm}" auth = Rack::Auth::Basic::Request.new(env) - if auth.provided? && yield(*auth.credentials) - env['REMOTE_USER'] = auth.username - else - response.status = 401 - # request.block_result(instance_exec(request, &opts[:unauthorized])) - request.halt response.finish - end + return if auth.provided? && Roda::RodaPlugins::BasicAuth.authorize(username, password, auth) + + response.status = 401 + request.halt response.finish end end end + register_plugin(:basic_auth, BasicAuth) end end From 515f4fe8716a1efd653e852ed937beb2a85b8dc2 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 12:56:39 +0100 Subject: [PATCH 3/6] generate username/password if not set --- README.md | 27 ++++++++++++++++++--------- app.rb | 7 ++++--- app/health_check.rb | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 332b681a..a3acfd35 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ services: read_only: true environment: - RACK_ENV=production + - HEALTH_CHECK_USERNAME=health + - HEALTH_CHECK_PASSWORD=please-set-YOUR-OWN-veeeeeery-l0ng-aNd-h4rd-to-gue55-Passw0rd! watchtower: image: containrrr/watchtower volumes: @@ -71,12 +73,12 @@ html2rss-web comes with many feed configs out of the box. [See file list of all To use a config from there, build the URL like this: -The _feed config_ you'd like to use: -`lib/html2rss/configs/domainname.tld/whatever.yml` +The _feed config_ you'd like to use: +`lib/html2rss/configs/domainname.tld/whatever.yml` `‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌^^^^^^^^^^^^^^^^^^^^^^^^^^^` -The corresponding URL: -`http://localhost:3000/domainname.tld/whatever.rss` +The corresponding URL: +`http://localhost:3000/domainname.tld/whatever.rss` `‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ^^^^^^^^^^^^^^^^^^^^^^^^^^^` ## How to build your RSS feeds @@ -119,17 +121,24 @@ If you're going to host a public instance, _please please please_: | `RACK_TIMEOUT_SERVICE_TIMEOUT` | default: 15 | | `WEB_CONCURRENCY` | default: 2 | | `WEB_MAX_THREADS` | default: 5 | +| `HEALTH_CHECK_USERNAME` | | +| `HEALTH_CHECK_PASSWORD` | | ### Runtime monitoring via `GET /health_check.txt` -It is recommended to setup a monitoring of the `/health_check.txt` endpoint. With that, you can be notified when one of _your own_ configs break. +It is recommended to setup a monitoring of the `/health_check.txt` endpoint. With that, you can find out when one of _your own_ configs break. -The `GET /health_check.txt` endpoint responds with: +First, set values for username and password with the environment variables: `HEALTH_CHECK_USERNAME` and `HEALTH_CHECK_PASSWORD`. html2rss-web will generate a random username and password on each start if these variables are not set. -- if the feeds are generatable: it will respond with `success` . -- otherwise: it states the broken config names. +An authenticated `GET /health_check.txt` request will be responded with: -[UptimeRobot's free plan](https://uptimerobot.com/) is sufficent for basic monitoring every 5 minutes. Create a monitor of type _Keyword_ with this information: +- if the feeds are generatable: `success`. +- otherwise: the names of the broken configs. + +To get notified when one of your configs breaks, setup a monitoring of this endpoint. + +[UptimeRobot's free plan](https://uptimerobot.com/) is sufficent for basic monitoring (every 5 minutes). +Create a monitor of type _Keyword_ with this information and add your username and password: ![A screenshot showing the Keyword Monitor: a name, the instance's URL to /health_check.txt and an interval.](docs/uptimerobot_monitor.jpg) diff --git a/app.rb b/app.rb index 06b8a98d..3cbc1409 100644 --- a/app.rb +++ b/app.rb @@ -72,7 +72,6 @@ class App < Roda plugin :public plugin :render, escape: true, layout: 'layout' plugin :typecast_params - plugin :basic_auth route do |r| @@ -85,10 +84,12 @@ class App < Roda r.public r.get 'health_check.txt' do |_| - basic_auth realm: 'health_check', username: 'foo', password: 'bar' - HttpCache.expires_now(response) + basic_auth(realm: HealthCheck, + username: HealthCheck::Auth.username, + password: HealthCheck::Auth.password) + HealthCheck.run end diff --git a/app/health_check.rb b/app/health_check.rb index 50f4909f..ce519ef4 100644 --- a/app/health_check.rb +++ b/app/health_check.rb @@ -1,11 +1,32 @@ # frozen_string_literal: true require_relative './local_config' +require 'singleton' module App ## # Checks if the local configs generate valid RSS feeds. module HealthCheck + ## + # Contains logic to obtain username and password to be used with HealthCheck endpoint. + class Auth + def self.username + @username ||= ENV.fetch('HEALTH_CHECK_USERNAME') do + SecureRandom.base64(32).tap do |string| + puts "HEALTH_CHECK_USERNAME env var. missing! Please set it. Using generated value instead: #{string}" + end + end + end + + def self.password + @password ||= ENV.fetch('HEALTH_CHECK_PASSWORD') do + SecureRandom.base64(32).tap do |string| + puts "HEALTH_CHECK_PASSWORD env var. missing! Please set it. Using generated value instead: #{string}" + end + end + end + end + module_function ## From 7cdff25ff5d2ff7f97a638e18b3fc128a282b026 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 14:19:11 +0100 Subject: [PATCH 4/6] add specs --- README.md | 4 +- Rakefile | 5 +- app.rb | 12 ++-- app/health_check.rb | 6 +- .../roda_plugins}/basic_auth.rb | 13 ++++- spec/app/health_check/auth_spec.rb | 24 ++++++++ spec/roda/roda_plugins/basic_auth_spec.rb | 55 +++++++++++++++++++ 7 files changed, 104 insertions(+), 15 deletions(-) rename {roda_plugins => roda/roda_plugins}/basic_auth.rb (70%) create mode 100644 spec/app/health_check/auth_spec.rb create mode 100644 spec/roda/roda_plugins/basic_auth_spec.rb diff --git a/README.md b/README.md index a3acfd35..2a9bad3b 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,9 @@ If you're going to host a public instance, _please please please_: ### Runtime monitoring via `GET /health_check.txt` -It is recommended to setup a monitoring of the `/health_check.txt` endpoint. With that, you can find out when one of _your own_ configs break. +It is recommended to setup a monitoring of the `/health_check.txt` endpoint. With that, you can find out when one of _your own_ configs break. The endpoint uses HTTP Basic authentication. -First, set values for username and password with the environment variables: `HEALTH_CHECK_USERNAME` and `HEALTH_CHECK_PASSWORD`. html2rss-web will generate a random username and password on each start if these variables are not set. +First, set values for username and password with the environment variables: `HEALTH_CHECK_USERNAME` and `HEALTH_CHECK_PASSWORD`. If these variables are not set, html2rss-web will generate a random username and password on each start. An authenticated `GET /health_check.txt` request will be responded with: diff --git a/Rakefile b/Rakefile index 0085d87e..cdd8e987 100644 --- a/Rakefile +++ b/Rakefile @@ -11,6 +11,8 @@ task :test do '-d', '-p 3000:3000', '--env PUMA_LOG_CONFIG=1', + '--env HEALTH_CHECK_USERNAME=username', + '--env HEALTH_CHECK_PASSWORD=password', "--mount type=bind,source=#{current_dir}/config,target=/app/config", '--name html2rss-web-test', 'gilcreator/html2rss-web'].join(' ') @@ -20,7 +22,8 @@ task :test do sh 'docker ps -a' sh 'curl -f http://127.0.0.1:3000/github.com/releases.rss\?username=nuxt\&repository=nuxt.js || exit 1' - sh 'curl -f http://127.0.0.1:3000/health_check.txt || exit 1' + sh 'curl -f http://127.0.0.1:3000/example.rss || exit 1' + sh 'curl -f http://username:password@127.0.0.1:3000/health_check.txt || exit 1' sh 'docker exec html2rss-web-test html2rss help' ensure sh 'docker logs --tail all html2rss-web-test' diff --git a/app.rb b/app.rb index 3cbc1409..0473a10e 100644 --- a/app.rb +++ b/app.rb @@ -7,7 +7,7 @@ require_relative './app/local_config' require_relative './app/html2rss_facade' -require_relative 'roda_plugins/basic_auth' +require_relative 'roda/roda_plugins/basic_auth' module App ## @@ -86,11 +86,11 @@ class App < Roda r.get 'health_check.txt' do |_| HttpCache.expires_now(response) - basic_auth(realm: HealthCheck, - username: HealthCheck::Auth.username, - password: HealthCheck::Auth.password) - - HealthCheck.run + with_basic_auth(realm: HealthCheck, + username: HealthCheck::Auth.username, + password: HealthCheck::Auth.password) do + HealthCheck.run + end end # Route for feeds from the local feeds.yml diff --git a/app/health_check.rb b/app/health_check.rb index ce519ef4..2a1df924 100644 --- a/app/health_check.rb +++ b/app/health_check.rb @@ -5,13 +5,13 @@ module App ## - # Checks if the local configs generate valid RSS feeds. + # Checks if the local configs are generatable. module HealthCheck ## # Contains logic to obtain username and password to be used with HealthCheck endpoint. class Auth def self.username - @username ||= ENV.fetch('HEALTH_CHECK_USERNAME') do + @username ||= ENV.delete('HEALTH_CHECK_USERNAME') do SecureRandom.base64(32).tap do |string| puts "HEALTH_CHECK_USERNAME env var. missing! Please set it. Using generated value instead: #{string}" end @@ -19,7 +19,7 @@ def self.username end def self.password - @password ||= ENV.fetch('HEALTH_CHECK_PASSWORD') do + @password ||= ENV.delete('HEALTH_CHECK_PASSWORD') do SecureRandom.base64(32).tap do |string| puts "HEALTH_CHECK_PASSWORD env var. missing! Please set it. Using generated value instead: #{string}" end diff --git a/roda_plugins/basic_auth.rb b/roda/roda_plugins/basic_auth.rb similarity index 70% rename from roda_plugins/basic_auth.rb rename to roda/roda_plugins/basic_auth.rb index 366cf3a0..66b203a9 100644 --- a/roda_plugins/basic_auth.rb +++ b/roda/roda_plugins/basic_auth.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'roda' require 'openssl' class Roda @@ -12,7 +13,7 @@ module BasicAuth def self.authorize(username, password, auth) given_user, given_password = auth.credentials - secure_compare(username.to_s, given_user) & secure_compare(password.to_s, given_password) + secure_compare(username, given_user) & secure_compare(password, given_password) end def self.secure_compare(left, right) @@ -22,15 +23,21 @@ def self.secure_compare(left, right) ## # Methods here become instance methods in the roda application. module InstanceMethods - def basic_auth(realm:, username:, password:) + def with_basic_auth(realm:, username:, password:) raise ArgumentError, 'realm must not be a blank string' if realm.to_s.strip == '' response.headers['WWW-Authenticate'] = "Basic realm=#{realm}" auth = Rack::Auth::Basic::Request.new(env) - return if auth.provided? && Roda::RodaPlugins::BasicAuth.authorize(username, password, auth) + if auth.provided? && Roda::RodaPlugins::BasicAuth.authorize(username, password, auth) + yield if block_given? + else + unauthorized + end + end + def unauthorized response.status = 401 request.halt response.finish end diff --git a/spec/app/health_check/auth_spec.rb b/spec/app/health_check/auth_spec.rb new file mode 100644 index 00000000..67fb8d60 --- /dev/null +++ b/spec/app/health_check/auth_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative '../../../app/health_check' + +RSpec.describe App::HealthCheck::Auth do + before do + allow(ENV).to receive(:delete).with(any_args).and_call_original + end + + describe '.username' do + it { + expect(described_class.username).to be_a String + expect(ENV).to have_received(:delete).with('HEALTH_CHECK_USERNAME').once + } + end + + describe '.password' do + it { + expect(described_class.password).to be_a String + expect(ENV).to have_received(:delete).with('HEALTH_CHECK_PASSWORD').once + } + end +end diff --git a/spec/roda/roda_plugins/basic_auth_spec.rb b/spec/roda/roda_plugins/basic_auth_spec.rb new file mode 100644 index 00000000..ce1f5108 --- /dev/null +++ b/spec/roda/roda_plugins/basic_auth_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rack' +require_relative '../../../roda/roda_plugins/basic_auth' + +RSpec.describe Roda::RodaPlugins::BasicAuth do + before do + allow(Roda::RodaPlugins).to receive(:register_plugin).with(:basic_auth, described_class) + end + + describe '.authorize(username, password, auth)' do + context 'with correct credentials' do + it { + username = 'foo' + password = 'bar' + auth = instance_double(Rack::Auth::Basic::Request, credentials: %w[foo bar]) + + expect(described_class.authorize(username, password, auth)).to be true + } + end + + context 'with wrong credentials' do + it { + username = '' + password = '' + auth = instance_double(Rack::Auth::Basic::Request, credentials: %w[foo bar]) + + expect(described_class.authorize(username, password, auth)).to be false + } + end + end + + describe '.secure_compare(left, right)' do + context 'with left being same as right' do + let(:left) { 'something-asdf' } + let(:right) { 'something-asdf' } + + it 'uses OpenSSL.fixed_length_secure_compare', :aggregate_failures do + allow(OpenSSL).to receive(:fixed_length_secure_compare).with(left, right).and_call_original + + expect(described_class.secure_compare(left, right)).to be true + + expect(OpenSSL).to have_received(:fixed_length_secure_compare).with(left, right) + end + end + + context 'with left being different from right' do + it 'returns false', :aggregate_failures do + expect(described_class.secure_compare('left', 'right')).to be false + expect(described_class.secure_compare('lefty', 'right')).to be false + end + end + end +end From ca0e8982fce7ad729756deea19ebc5dd6154c483 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 14:51:52 +0100 Subject: [PATCH 5/6] feat(docker): test health endpoint during docker-test --- .rubocop.yml | 2 ++ Dockerfile | 28 +++++++++---------- Rakefile | 44 +++++++++++++++++++++++++++--- app/health_check.rb | 1 - spec/app/health_check/auth_spec.rb | 8 +++--- 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a7c07e0e..ed0d2c13 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,5 +8,7 @@ AllCops: NewCops: enable Metrics/BlockLength: + Exclude: + - Rakefile ExcludedMethods: - route diff --git a/Dockerfile b/Dockerfile index 29258623..30f8f945 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV RACK_ENV=production ENV PATH="/app/bin:${PATH}" HEALTHCHECK --interval=30m --timeout=60s --start-period=5s \ - CMD curl -f http://localhost:3000/health_check.txt || exit 1 + CMD curl -f http://${HEALTH_CHECK_USERNAME}:${HEALTH_CHECK_PASSWORD}@localhost:3000/health_check.txt || exit 1 RUN apk add --no-cache \ 'git=~2' \ @@ -23,16 +23,16 @@ ARG UID=991 ARG GID=991 RUN mkdir /app \ - && addgroup --gid "$GID" "$USER" \ - && adduser \ - --disabled-password \ - --gecos "" \ - --home "/app" \ - --ingroup "$USER" \ - --no-create-home \ - --uid "$UID" \ - "$USER" \ - && chown "$USER":"$USER" -R /app + && addgroup --gid "$GID" "$USER" \ + && adduser \ + --disabled-password \ + --gecos "" \ + --home "/app" \ + --ingroup "$USER" \ + --no-create-home \ + --uid "$UID" \ + "$USER" \ + && chown "$USER":"$USER" -R /app WORKDIR /app @@ -40,9 +40,9 @@ USER html2rss COPY --chown=html2rss:html2rss Gemfile Gemfile.lock ./ RUN gem install bundler:'<3' \ - && bundle config set --local without 'development test' \ - && bundle install --retry=5 --jobs=7 \ - && bundle binstubs bundler html2rss + && bundle config set --local without 'development test' \ + && bundle install --retry=5 --jobs=7 \ + && bundle binstubs bundler html2rss COPY --chown=html2rss:html2rss . . diff --git a/Rakefile b/Rakefile index cdd8e987..2e4f7e81 100644 --- a/Rakefile +++ b/Rakefile @@ -1,11 +1,38 @@ # frozen_string_literal: true +## +# Helper methods used during :test run +module Output + module_function + + def describe(msg) + puts '' + puts '*' * 80 + puts "* #{msg}" + puts '*' * 80 + puts '' + end + + def wait(seconds, message:) + print message if message + + seconds.times do |i| + putc ' ' + putc (i + 1).to_s + sleep 1 + end + puts '. Time is up.' + end +end + task default: %w[test] desc 'Build and run docker image/container, and send requests to it' + task :test do current_dir = ENV.fetch('GITHUB_WORKSPACE', __dir__) + Output.describe 'Building and running' sh 'docker build -t gilcreator/html2rss-web -f Dockerfile .' sh ['docker run', '-d', @@ -17,15 +44,24 @@ task :test do '--name html2rss-web-test', 'gilcreator/html2rss-web'].join(' ') - # wait for container to run and accept connections - sleep 5 - sh 'docker ps -a' + Output.wait 5, message: 'Waiting for container to start:' + + Output.describe 'Listing docker containers matching html2rss-web-test filter' + sh 'docker ps -a --filter name=html2rss-web-test' + + Output.describe 'Generating feed from a html2rss-configs config' + sh 'curl -f http://127.0.0.1:3000/github.com/releases.rss\?username=html2rss\&repository=html2rss || exit 1' - sh 'curl -f http://127.0.0.1:3000/github.com/releases.rss\?username=nuxt\&repository=nuxt.js || exit 1' + Output.describe 'Generating example feed from feeds.yml' sh 'curl -f http://127.0.0.1:3000/example.rss || exit 1' + + Output.describe 'Authenticated request to GET /health_check.txt' sh 'curl -f http://username:password@127.0.0.1:3000/health_check.txt || exit 1' + + Output.describe 'Print output of `html2rss help`' sh 'docker exec html2rss-web-test html2rss help' ensure + Output.describe 'Cleaning up' sh 'docker logs --tail all html2rss-web-test' sh 'docker stop html2rss-web-test' sh 'docker rm html2rss-web-test' diff --git a/app/health_check.rb b/app/health_check.rb index 2a1df924..698092d5 100644 --- a/app/health_check.rb +++ b/app/health_check.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require_relative './local_config' -require 'singleton' module App ## diff --git a/spec/app/health_check/auth_spec.rb b/spec/app/health_check/auth_spec.rb index 67fb8d60..b9fed4ef 100644 --- a/spec/app/health_check/auth_spec.rb +++ b/spec/app/health_check/auth_spec.rb @@ -9,16 +9,16 @@ end describe '.username' do - it { + it 'deletes the ENV var', :aggregate_failures do expect(described_class.username).to be_a String expect(ENV).to have_received(:delete).with('HEALTH_CHECK_USERNAME').once - } + end end describe '.password' do - it { + it 'deletes the ENV var', :aggregate_failures do expect(described_class.password).to be_a String expect(ENV).to have_received(:delete).with('HEALTH_CHECK_PASSWORD').once - } + end end end From 20260553223d5bb0abfaffdc101d561f8a2555b3 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Sun, 8 Jan 2023 15:17:12 +0100 Subject: [PATCH 6/6] docs(readme): update readme --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2a9bad3b..bcd3e9b4 100644 --- a/README.md +++ b/README.md @@ -73,13 +73,14 @@ html2rss-web comes with many feed configs out of the box. [See file list of all To use a config from there, build the URL like this: -The _feed config_ you'd like to use: -`lib/html2rss/configs/domainname.tld/whatever.yml` -`‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌^^^^^^^^^^^^^^^^^^^^^^^^^^^` +Build the URL of the _feed config_ you'd like to use like this: -The corresponding URL: -`http://localhost:3000/domainname.tld/whatever.rss` -`‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ^^^^^^^^^^^^^^^^^^^^^^^^^^^` +| | | +| -----------------------: | :---------------------------- | +| `lib/html2rss/configs/` | `domainname.tld/whatever.yml` | +| Would becomes this URL: | | +| `http://localhost:3000/` | `domainname.tld/whatever.rss` | +| | `^^^^^^^^^^^^^^^^^^^^^^^^^^^` | ## How to build your RSS feeds @@ -114,21 +115,21 @@ If you're going to host a public instance, _please please please_: ### Supported ENV variables -| Name | Description | -| ------------------------------ | ---------------------- | -| `PORT` | default: 3000 | -| `RACK_ENV` | default: 'development' | -| `RACK_TIMEOUT_SERVICE_TIMEOUT` | default: 15 | -| `WEB_CONCURRENCY` | default: 2 | -| `WEB_MAX_THREADS` | default: 5 | -| `HEALTH_CHECK_USERNAME` | | -| `HEALTH_CHECK_PASSWORD` | | +| Name | Description | +| ------------------------------ | -------------------------------- | +| `PORT` | default: 3000 | +| `RACK_ENV` | default: 'development' | +| `RACK_TIMEOUT_SERVICE_TIMEOUT` | default: 15 | +| `WEB_CONCURRENCY` | default: 2 | +| `WEB_MAX_THREADS` | default: 5 | +| `HEALTH_CHECK_USERNAME` | default: auto generated on start | +| `HEALTH_CHECK_PASSWORD` | default: auto generated on start | ### Runtime monitoring via `GET /health_check.txt` It is recommended to setup a monitoring of the `/health_check.txt` endpoint. With that, you can find out when one of _your own_ configs break. The endpoint uses HTTP Basic authentication. -First, set values for username and password with the environment variables: `HEALTH_CHECK_USERNAME` and `HEALTH_CHECK_PASSWORD`. If these variables are not set, html2rss-web will generate a random username and password on each start. +First, set username and password via these environment variables: `HEALTH_CHECK_USERNAME` and `HEALTH_CHECK_PASSWORD`. If these are not set, html2rss-web will generate a new random username and password on _each_ start. An authenticated `GET /health_check.txt` request will be responded with: @@ -138,7 +139,7 @@ An authenticated `GET /health_check.txt` request will be responded with: To get notified when one of your configs breaks, setup a monitoring of this endpoint. [UptimeRobot's free plan](https://uptimerobot.com/) is sufficent for basic monitoring (every 5 minutes). -Create a monitor of type _Keyword_ with this information and add your username and password: +Create a monitor of type _Keyword_ with this information and make it aware of your username and password: ![A screenshot showing the Keyword Monitor: a name, the instance's URL to /health_check.txt and an interval.](docs/uptimerobot_monitor.jpg)