Technical Documentation: stringer-rss/stringer
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
Stringer
A self-hosted, anti-social RSS reader.
Stringer has no external dependencies, no social recommendations/sharing, and no fancy machine learning algorithms.
But it does have keyboard shortcuts and was made with love!
Installation
Stringer is a Ruby app based on Rails, PostgreSQL, Hotwire (Turbo and Stimulus) and GoodJob.
Stringer will run just fine on the Eco/Basic Heroku plans.
Instructions are provided for deploying to Heroku manually, to any Ruby
compatible Linux-based VPS, to Docker and to OpenShift.
Niceties
Keyboard Shortcuts
You can access the keyboard shortcuts when using the app by hitting ?.
Using your own domain with Heroku
You can run Stringer at http://reader.yourdomain.com using a CNAME.
If you are on Heroku:
heroku domains:add reader.yourdomain.comGo to your registrar and add a CNAME:
Record: CNAME
Name: reader
Target: your-heroku-instance.herokuapp.comWait a few minutes for changes to propagate.
Fever API
Stringer implements a clone of Fever's API so it can be used with any mobile client that supports Fever.
Use the following settings:
Server: {path-to-stringer}/fever (e.g. http://reader.example.com/fever)
Email: stringer (case-sensitive)
Password: {your-stringer-password}Translations
Stringer has been translated to several other languages. Your language can be set with the LOCALE environment variable.
To set your locale on Heroku, run heroku config:set LOCALE=en.
If you would like to translate Stringer to your preferred language, please use LocaleApp.
Clean up old read stories on Heroku
You can clean up old stories by running: rake cleanup_old_stories
By default, this removes read stories that are more than 30 days old (that
are not starred). You can either run this manually or add it as a scheduled
task.
Development
Getting Started
You'll need the toolchain versions pinned in .tool-versions (Ruby, Node,
PostgreSQL, and pnpm). With those installed, run:
bin/setupThis installs dependencies, prepares the database, and launches the development
server. The application will be running on port 3000.
On subsequent runs you can start the server directly with bin/dev, which boots
Puma alongside the esbuild watchers for JavaScript and CSS.
You can launch an interactive console (a la rails c) using rake console.
Tests and linting
Run the Ruby tests with bundle exec rspec. Run a single file or example withbundle exec rspec spec/path/to/file_spec.rb:LINE.
Run the JavaScript tests with pnpm test (Vitest). This also type-checks and
runs ESLint first.
Other checks: bundle exec rubocop (Ruby lint), pnpm eslint (JS lint),pnpm stylelint (CSS lint), pnpm tscheck (type check), andbundle exec brakeman (security scan).
Acknowledgments
Most of the heavy-lifting is done by feedjira and feedbag.
General sexiness courtesy of Twitter Bootstrap and Flat UI.
ReenieBeanie Font Copyright © 2010 Typeco ([email protected]). Licensed under SIL Open Font License, 1.1.
Lato Font Copyright © 2010-2011 by tyPoland Lukasz Dziedzic ([email protected]). Licensed under SIL Open Font License, 1.1.
Contact
If you have a question, feature idea, or are running into problems, our preferred method of contact is to open an issue on GitHub. This allows multiple people to weigh in, and we can keep everything in one place. Thanks!
Maintainers
Robert Fletcher boon.gl
Alumni
Matt Swanson (creator), mdswanson.com, @_swanson
Victor Koronen, victor.koronen.se, @victorkoronen
Docker (`docs/Docker.md`)
Stringer on Docker
Production ready setup using docker-compose
Create a local environment file named .env, e.g. via touch .env.
Download docker-compose.yml and run:
touch .env && docker compose up -dGive it a second and visit localhost!
Production ready manual setup
The following steps can be used to setup Stringer on Docker, using a Postgres database also running on Docker.
- Setup a Docker network so the two containers we're going to create can communicate:
docker network create --driver bridge stringer-network- Setup a Postgres Docker container:
docker run --detach \
--name stringer-postgres \
--restart always \
--volume /srv/stringer/data:/var/lib/postgresql \
--net stringer-network \
-e POSTGRES_PASSWORD=myPassword \
-e POSTGRES_DB=stringer \
postgres:18-alpine- Run the Stringer Docker image:
docker run --detach \
--name stringer \
--net stringer-network \
--restart always \
-e PORT=8080 \
-e DATABASE_URL=postgres://postgres:myPassword@stringer-postgres/stringer \
-e SECRET_KEY_BASE=$(openssl rand -hex 64) \
-e ENCRYPTION_PRIMARY_KEY=$(openssl rand -hex 64) \
-e ENCRYPTION_DETERMINISTIC_KEY=$(openssl rand -hex 64) \
-e ENCRYPTION_KEY_DERIVATION_SALT=$(openssl rand -hex 64) \
-e FETCH_FEEDS_CRON="*/5 * * * *" \ # optional
-e CLEANUP_CRON="0 0 * * *" \ # optional
-p 127.0.0.1:8080:8080 \
stringerrss/stringer:latestThat's it! You now have a fully working Stringer instance up and running!
Upgrading Postgres
The setup above moved from Postgres 16 to Postgres 18. Two things changed:
- A Postgres major version cannot read data files written by an older one.
- The official 18+ image expects a single mount at
/var/lib/postgresql(not/var/lib/postgresql/data) and stores the database in a version-named subdirectory inside it.
If you start the 18 image against an existing 16 data directory it refuses to start and prints an explanation, so nothing is lost. But you do need to migrate by hand. Stringer databases are small, so the simplest route is to dump and restore.
- With the old container still running, dump everything:
docker compose exec stringer-postgres sh -c 'pg_dumpall -U "$POSTGRES_USER"' > stringer-pg16.sql- Stop the stack and move the old data directory aside:
docker compose down
sudo mv /srv/stringer/data /srv/stringer/data-pg16Update
docker-compose.yml(or re-download it): change the image topostgres:18-alpineand the volume to/srv/stringer/data:/var/lib/postgresql.Start only the database and wait for it to accept connections:
docker compose up -d stringer-postgres
until docker compose exec stringer-postgres pg_isready -h localhost; do sleep 1; done- Restore the dump. Errors saying a role or database "already exists" are expected and harmless, because the fresh container already created them:
docker compose exec -T stringer-postgres sh -c 'psql -U "$POSTGRES_USER" -d postgres' < stringer-pg16.sql- Start the rest of the stack and check that your feeds and stories are there:
docker compose up -d- Once you are happy, delete
/srv/stringer/data-pg16andstringer-pg16.sql.
If you used the manual setup instead of docker-compose, run the same commands with docker exec stringer-postgres in place of docker compose exec stringer-postgres, and recreate the Postgres container with the new image and volume path in step 3.
For production use it's recommended to put a reverse proxy in front of Stringer.
Caddy (https://caddyserver.com/):
stringer.example.org {
proxy / localhost:8080 {
transparent
}
}Nginx (https://nginx.org/):
/* Detailed source-code truncated for AI context efficiency. */Heroku (`docs/Heroku.md`)
git clone [email protected]:stringer-rss/stringer.git
cd stringer
heroku create
heroku config:set SECRET_KEY_BASE=`openssl rand -hex 64`
heroku config:set ENCRYPTION_PRIMARY_KEY=`openssl rand -hex 64`
heroku config:set ENCRYPTION_DETERMINISTIC_KEY=`openssl rand -hex 64`
heroku config:set ENCRYPTION_KEY_DERIVATION_SALT=`openssl rand -hex 64`
git push heroku main
heroku config:set APP_URL=`heroku apps:info --shell | grep web_url | cut -d= -f2`
heroku run rake db:migrate
heroku restart
heroku addons:create scheduler
heroku addons:open schedulerAdd an hourly task that runs rake lazy_fetch (if you are not on Heroku you may want rake fetch_feeds instead).
Load the app and follow the instructions to import your feeds and start using the app.
See the "Niceties" section of the README for a few more tips and tricks for getting the most out of Stringer on Heroku.
Updating the app
From the app's directory:
git pull
git push heroku main
heroku run rake db:migrate
heroku restartOpenShift (`docs/OpenShift.md`)
Stringer on OpenShift
Deploying into OpenShift
- Creating new OpenShift Ruby 2.0 application with the Postgresql cartridge (command-line).
rhc app create feeds ruby-2.0 postgresql-9.2- Pull the code from the Stringer Github repository.
cd feeds
git remote add upstream [email protected]:stringer-rss/stringer.git
git pull -s recursive -X theirs upstream main- To enable migrations for the application, a new action_hook is required. Add the file, .openshift/action_hooks/deploy, with the below 3 lines into it.
pushd ${OPENSHIFT_REPO_DIR} > /dev/null
bundle exec rake db:migrate RAILS_ENV="production"
popd > /dev/null- Make sure that the file created above is executable on Unix-like systems.
chmod +x .openshift/action_hooks/deploy- Set the environment variables by generating them with the commands below.
rhc env set SECRET_KEY_BASE="`openssl rand -hex 64`"
rhc env set ENCRYPTION_PRIMARY_KEY="`openssl rand -hex 64`"
rhc env set ENCRYPTION_DETERMINISTIC_KEY="`openssl rand -hex 64`"
rhc env set ENCRYPTION_KEY_DERIVATION_SALT="`openssl rand -hex 64`"- Configuration of the database server is next. Open the file config/database.yml and add in the configuration for Production as shown below. OpenShift is able to use environment variables to push the information into the application.
production:
adapter: postgresql
database: <%= ENV["OPENSHIFT_APP_NAME"] %>
host: <%= ENV["OPENSHIFT_POSTGRESQL_DB_HOST"] %>
port: <%= ENV["OPENSHIFT_POSTGRESQL_DB_PORT"] %>
username: <%= ENV["OPENSHIFT_POSTGRESQL_DB_USERNAME"] %>
password: <%= ENV["OPENSHIFT_POSTGRESQL_DB_PASSWORD"] %>Due to an older version of bundler being used in OpenShift (1.3.5), some changes need to be made in the Gemfile.
Remove the Ruby version specification from the Gemfile below (error reporting wrong Ruby version when deploying to OpenShift).
ruby '2.0.0'Then change the two gem dependencies below to use the hash rocket syntax for the "require" option.gem "coveralls", "~> 0.7", require: false
gem "rubocop", "~> 0.35.1", require: false
gem "sinatra-assetpack", "~> 0.3.1", require: "sinatra/assetpack"togem "coveralls", "~> 0.7", :require => false
gem "rubocop", "~> 0.35.1", :require => false
gem "sinatra-assetpack", "~> 0.3.1", :require => "sinatra/assetpack"- Finally, once completed, all changes should be committed and pushed to OpenShift. Note that it might take a while when pushing to OpenShift.
git add .
git commit -m "Deployment of Stringer"
git push origin- Check that you are able to access the website at the URL given, i.e. feeds-username.rhcloud.com. Then set your password, import your feeds and all good to go!
Adding Cronjob to Fetch Feeds
After importing feeds, a cron job is needed on OpenShift to fetch feeds.
- Add a new cron cartridge for the cron job.
rhc cartridge add cron -a feeds- Add a new executable file, .openshift/cron/hourly/fetch_feeds and put the below 4 lines into it.
./usr/bin/rhcsh
pushd ${OPENSHIFT_REPO_DIR} > /dev/null
bundle exec rake fetch_feeds RAILS_ENV="production"
popd > /dev/null- Make the file executable.
chmod +x .openshift/cron/hourly/fetch_feeds- Push all changes to OpenShift.
git add .
git commit -m "Added Cronjob"
git push origin- Done! The cron job should fetch feeds every hour.
VPS (`docs/VPS.md`)
Install some dependencies
The first step is installing some essential dependencies from your VPS's package manager.
Ubuntu/Debian
sudo apt-get install git libxml2-dev libxslt-dev libcurl4-openssl-dev libpq-dev build-essential postgresql libreadline-devCentOS/Fedora
sudo yum install git libxml2-devel libxslt-devel curl-devel postgresql-devel make automake gcc gcc-c++ postgresql-server readline-devel openssl-develOn CentOS after installing Postgres, I needed to run these commands, Fedora likely the same.
service postgresql initdb && service postgresql startArch Linux
pacman -S git postgresql base-devel libxml2 libxslt curl readline postgresql-libsHere are some Arch specific instructions for setting up postgres
systemd-tmpfiles --create postgresql.conf
chown -c -R postgres:postgres /var/lib/postgres
sudo su - postgres -c "initdb --locale en_US.UTF-8 -E UTF8 -D '/var/lib/postgres/data'"
systemctl start postgresql
systemctl enable postgresqlSet up the database
Create a postgresql user to own the database Stringer will use, you will need to create a password too, make a note of it.
sudo -u postgres createuser -D -A -P stringerNow create the database Stringer will use
sudo -u postgres createdb -O stringer stringer_liveCreate your stringer user
We will run stringer as it's own user for security, also we will be installing a specific version of ruby to stringer user's home directory, this saves us worrying whether the version of ruby and some dependencies provided by your distro are compatible with Stringer.
sudo useradd stringer -m -s /bin/bash
sudo su -l stringerAlways use -l switch when you switch user to your stringer user, without it your modified .bash_profile or .profile file will be ignored.
Install Ruby for your stringer user
We are going to use Rbenv to manage the version of Ruby you use.
cd
git clone git://github.com/sstephenson/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> $HOME/.bash_profile
echo 'eval "$(rbenv init -)"' >> $HOME/.bash_profile
git clone git://github.com/sstephenson/ruby-build.git $HOME/.rbenv/plugins/ruby-build
source ~/.bash_profile
rbenv install 2.7.5
rbenv local 2.7.5
rbenv rehashWe also need to install bundler which will handle Stringer's dependencies
gem install bundler
rbenv rehashWe will also need foreman to run our app
gem install foremanInstall Stringer and set it up
Grab Stringer from github
git clone [email protected]:stringer-rss/stringer.git
cd stringerUse bundler to grab and build Stringer's dependencies
bundle install
rbenv rehashStringer uses environment variables to configure the application. Edit these values to reflect your settings.
echo 'export DATABASE_URL="postgres://stringer:EDIT_ME@localhost/stringer_live"' >> $HOME/.bash_profile
echo 'export RACK_ENV="production"' >> $HOME/.bash_profile
echo 'export RAILS_ENV="production"' >> $HOME/.bash_profile
echo "export SECRET_KEY_BASE=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_PRIMARY_KEY=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_DETERMINISTIC_KEY=`openssl rand -hex 64`" >> $HOME/.bash_profile
echo "export ENCRYPTION_KEY_DERIVATION_SALT=`openssl rand -hex 64`" >> $HOME/.bash_profile
source ~/.bash_profileTell stringer to run the database in production mode, using the postgres database you created earlier.
cd $HOME/stringer
rake db:migrate RAILS_ENV=productionRun the application:
foreman startSet up a cron job to parse the rss feeds.
crontab -eadd the lines
SHELL=/bin/bash
PATH=/home/stringer/.rbenv/bin:/bin/:/usr/bin:/usr/local/bin/:/usr/local/sbin
*/10 * * * * source $HOME/.bash_profile; cd $HOME/stringer/; bundle exec rake fetch_feeds;Manage Stringer With Systemd
You may want to manage Stringer as a systemd service on distributions come with systemd.
As stringer user, export app service files with foreman:
cd ~/stringer
mkdir systemd-services
foreman export systemd systemd-services -a stringer -u stringerLogout stringer user, install systemd services:
sudo cp -a ~stringer/stringer/systemd-services/* /etc/systemd/systemAs stringer user, close existing Stringer instance:
exit # exit appStart app as a systemd service and make app run at startup
sudo systemctl start stringer.target
sudo systemctl enable stringer.targetReverse Proxy With Nginx
You may want to use nginx as reverse proxy server to add SSL/TLS for security
reason. Here is a sample configuration:
server {
listen 80;
# listen 443 ssl;
# ssl_certificate ssl/fullchain.pem;
# ssl_certificate_key ssl/privatekey.pem;
# you can try to use Mozilla SSL Configuration Generator
# to harden your TLS configuration
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
server_name example.com;
location / {
proxy_pass http://127.0.0.1:1337/;
proxy_set_header Host $host;
}
}Deploy Stringer With Passenger And Apache
You may want to run Stringer with the existing Apache server. We need to install
mod_passenger and edit few files.
The installation of mod_passenger depends on VPS's system distribution release.
Offical installation guide is available at Passenger Library
After validating the mod_passenger install, we will fetch dependencies again
to meet Passenger's default GEM_HOME set. As stringer user:
cd ~/stringer
bundle install --path vendor/bundleEdit database.yml with correct database url:
cd ~/stringer
sed -i "s|url: .*|url: $DATABASE_URL|" config/database.ymlAdd VirtualHost to your Apache installation, here's a sample configuration:
<VirtualHost *:80>
ServerName example.com
DocumentRoot /home/stringer/stringer/public
PassengerEnabled On
PassengerAppRoot /home/stringer/stringer
PassengerRuby /home/stringer/.rbenv/shims/ruby
# PassengerLogFile /dev/null # don't flow logs to apache error.log
<Directory /home/stringer/stringer/public>
Options FollowSymLinks
Require all granted
AllowOverride All
</Directory>
# you can harden your connection with https, don't forget
# change to <VirtualHost *:443>
# SSLCertificateFile /etc/path/to/example.com/fullchain.pem
# SSLCertificateKeyFile /etc/path/to/example.com/privkey.pem
# Include /etc/path/to/options-ssl-apache.conf
</VirtualHost>--- METRICS ---
- Files Extracted: 5
- Estimated Token Budget: ~5004 tokens
- Canonical Reference: https://codewiki.google/github.com/stringer-rss/stringer