Fusion RSS
Overview
Fusion is a lightweight, self-hosted RSS reader. It subscribes to RSS and Atom feeds, discovers feeds automatically from any site URL, and organises them into groups. Articles appear in a fast, responsive web interface with unread tracking, bookmarks, full-text search and Google Reader-style keyboard shortcuts.
A Fever-compatible API lets third-party clients such as Reeder, Unread and FeedMe sync against your own server. Fusion ships no AI features by design.
The SecureAnyCloud image delivers Fusion 1.2.0 fully installed and reverse-proxied with nginx. Fusion is a single self-contained Go binary with an embedded SQLite datastore, running under systemd, with nginx in front on port 80. The database lives on a dedicated Azure data disk mounted at /var/lib/fusion, separate from the OS disk and re-provisioned with every VM.
What you get:
- Fusion 1.2.0 (single Go binary) at
/opt/fusion/fusion, verified against the upstream release checksum. - An nginx reverse proxy on
:80in front of the Fusion server on:8080. - A dedicated Azure data disk at
/var/lib/fusionholding the SQLite database (fusion.db), separate from the OS disk and independently resizable. - Secure defaults: a unique 32-character administrator password is generated on first boot into a root-only file, so no shared or default credential ships in the image.
fusion.serviceandnginx.serviceas systemd units, enabled and active.- An Ubuntu 24.04 LTS base, patched at build time, with
unattended-upgradesenabled. - 24/7 SecureAnyCloud support.
Secure by default: a unique password on first boot
Fusion signs in with a password only. There is no username field. This image does not bake in a password.
On the very first boot, fusion-firstboot.service generates a unique 32-character password for your virtual machine. It writes the password into Fusion's runtime environment file and records it in /root/fusion-credentials.txt (mode 0600, readable only by root).
Two independent guards make it impossible for this image to serve an instance with a default credential:
fusion.servicedoes not start until first boot has provisioned that password.- Fusion itself refuses to start when no password is configured.
Prerequisites
- An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region.
- A virtual machine size of Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point. Scale up for very large feed counts or many concurrent readers.
- Network security group inbound rules that allow
22/tcpfrom your management network and80/tcpfrom wherever you browse Fusion.
Do not publish port 8080. Fusion's own listener binds to all interfaces, and nginx on port 80 is the intended single public entry point.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Fusion by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).
Review the dedicated data disk on the Disks tab. Then choose Review + create and Create.
Step 2 - Deploy from the Azure CLI
Run these commands from a workstation where you are signed in to the Azure CLI:
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:fusion-rss:default:latest
az vm create \
--resource-group <your-rg> \
--name fusion \
--image sac1647283583153:fusion-rss:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name fusion --port 80 --priority 1010Step 3 - Connect to your VM
Connect over SSH:
ssh azureuser@<vm-public-ip>The message of the day shows your Fusion URL and where to find your password.
Step 4 - Confirm the services are running
Both units report active, nginx listens on port 80, and the API answers. An unauthenticated request to the API correctly returns 401.
for s in fusion.service nginx.service; do printf '%-16s %s\n' "$s" "$(systemctl is-active "$s")" done printf 'first boot completed: %s\n' \
"$([ -f /var/lib/sac/data/fusion-firstboot.done ] && echo yes || echo no)" printf 'GET /api/oidc/enabled -> HTTP %s\n' \
"$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/api/oidc/enabled)" printf 'GET / -> HTTP %s\n' \
"$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)" printf 'GET /api/feeds (no auth) -> HTTP %s\n' \
"$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/api/feeds)"Step 5 - Retrieve your password
Your password was generated on first boot. It is stored in a root-only file, along with your Fusion URL and Fever API details.
sudo stat -c '%n %a %U:%G' /root/fusion-credentials.txt
sudo grep -E '^FUSION_URL=' /root/fusion-credentials.txt echo echo "Your password (keep it safe):"
sudo grep '^fusion.password=' /root/fusion-credentials.txt | cut -d= -f2-Confirm that the password actually authenticates, and that the example password from the upstream project's documentation is rejected:
PASS="$(sudo grep '^fusion.password=' /root/fusion-credentials.txt | cut -d= -f2-)" JAR="$(mktemp)" CODE="$(curl -s -o /dev/null -w '%{http_code}' -c "$JAR" \
-H 'Content-Type: application/json' \
--data-binary "$(jq -nc --arg p "$PASS" '{password:$p}')" \
http://127.0.0.1/api/sessions)" printf 'sign in with your per-VM password -> HTTP %s\n' "$CODE" [ "$CODE" = "200" ] || { echo "sign in did not succeed"; rm -f "$JAR"; exit 1; } printf 'authenticated GET /api/feeds -> HTTP %s\n' \
"$(curl -s -o /dev/null -w '%{http_code}' -b "$JAR" http://127.0.0.1/api/feeds)" echo '{"password":"fusion"}' > /tmp/fusion-example-pass.json printf 'sign in with the upstream example password -> HTTP %s (401 = rejected)\n' \
"$(curl -s -o /dev/null -w '%{http_code}' -H 'Content-Type: application/json' \
--data-binary @/tmp/fusion-example-pass.json http://127.0.0.1/api/sessions)" rm -f /tmp/fusion-example-pass.json "$JAR"Step 6 - Sign in to Fusion
Browse to http://<vm-public-ip>/. Enter the password from Step 5 and select Sign in. Fusion opens on your unread articles.
The left sidebar holds your saved views (Unread, Starred, All) and your feeds grouped into folders, each with an unread count. The centre pane lists articles. The All / Unread / Starred tabs filter it.
Step 7 - Add your first feed
In the web interface, choose Manage Feeds, then Add. Paste either a feed URL or an ordinary site URL. Fusion discovers the feed automatically. Give the feed a name, pick a group and save. Import and Export accept OPML, so you can migrate an existing reader in one step.
The same operations are available over the API. This example signs in, checks that a feed URL resolves, and lists your current feeds:
PASS="$(sudo grep '^fusion.password=' /root/fusion-credentials.txt | cut -d= -f2-)" JAR="$(mktemp)" curl -s -o /dev/null -c "$JAR" -H 'Content-Type: application/json' \
--data-binary "$(jq -nc --arg p "$PASS" '{password:$p}')" \
http://127.0.0.1/api/sessions echo "Feed discovery for https://blog.rust-lang.org/ :" curl -s -b "$JAR" -H 'Content-Type: application/json' \
--data-binary '{"url":"https://blog.rust-lang.org/"}' \
http://127.0.0.1/api/feeds/validate | jq -r '.data[]?.link' | head -5 echo echo "Your feeds:" curl -s -b "$JAR" http://127.0.0.1/api/feeds \
| jq -r '.data[] | " \(.name) -> \(.link)"' | head -20 rm -f "$JAR"Reading articles
Select any article to open the reading pane. Fusion renders the full article content supplied by the feed, with the source, publication date and a link to the original page. Mark as unread, Star and Original sit along the top. Previous and Next move through the list.
Fusion pulls each feed on a schedule (every 30 minutes by default). To refresh everything immediately, use Refresh All on the Manage Feeds screen, or run:
PASS="$(sudo grep '^fusion.password=' /root/fusion-credentials.txt | cut -d= -f2-)" JAR="$(mktemp)" curl -s -o /dev/null -c "$JAR" -H 'Content-Type: application/json' \
--data-binary "$(jq -nc --arg p "$PASS" '{password:$p}')" \
http://127.0.0.1/api/sessions curl -s -o /dev/null -b "$JAR" -X POST http://127.0.0.1/api/feeds/refresh sleep 5 printf 'articles currently stored: %s\n' \
"$(curl -s -b "$JAR" http://127.0.0.1/api/items | jq -r '.total')" rm -f "$JAR"Keyboard shortcuts
Fusion uses Google Reader-style shortcuts. Press ? in the web interface for the full list, and Cmd/Ctrl + K to open search.
Connecting a Fever API client
Fusion exposes a Fever-compatible endpoint, so mobile and desktop readers can sync against your server. In your client, choose Fever as the account type and enter:
- Endpoint:
http://<vm-public-ip>/fever - Username: fusion
- Password: the password from Step 5
Put the VM behind HTTPS before you use a Fever client over the public internet. See Enabling HTTPS with Let's Encrypt below.
The data disk
The SQLite database lives on a dedicated Azure data disk mounted at /var/lib/fusion, keyed by UUID in /etc/fstab with nofail. It is captured into the image and re-provisioned on every VM. You can resize it independently of the OS disk.
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /var/lib/fusion df -h /var/lib/fusion | tail -1 echo
sudo ls -l /var/lib/fusion/fusion.db printf 'feeds: %s\n' "$(sudo sqlite3 /var/lib/fusion/fusion.db 'SELECT COUNT(*) FROM feeds;')" printf 'articles: %s\n' "$(sudo sqlite3 /var/lib/fusion/fusion.db 'SELECT COUNT(*) FROM items;')" echo grep /var/lib/fusion /etc/fstabChanging the password
Fusion reads the password from its environment file at start-up. Edit the file and restart the service:
sudo sed -i 's/^FUSION_PASSWORD=.*/FUSION_PASSWORD=<new-password>/' /etc/fusion/fusion.env
sudo systemctl restart fusionUpdate /root/fusion-credentials.txt too, so the record stays accurate. Sign in again afterwards, because sessions are held in memory and the restart clears them.
Tuning feed pulling
Fusion reads its configuration from /etc/fusion/fusion.env. These settings are all optional:
FUSION_PULL_INTERVAL: seconds between pulls (default1800).FUSION_PULL_TIMEOUT: per-request timeout in seconds (default30).FUSION_PULL_CONCURRENCY: maximum concurrent pulls (default10).FUSION_LOGIN_RATE_LIMIT,FUSION_LOGIN_WINDOWandFUSION_LOGIN_BLOCK: login throttling.
Apply changes with sudo systemctl restart fusion.
Backing up and restoring
The whole application state is one SQLite file. Take a consistent copy with SQLite's online backup, which is safe to run while Fusion is running:
sudo install -d -m 0750 /var/backups/fusion
sudo sqlite3 /var/lib/fusion/fusion.db ".backup '/var/backups/fusion/fusion-backup.db'"
sudo sqlite3 /var/backups/fusion/fusion-backup.db 'PRAGMA integrity_check;'
sudo ls -l /var/backups/fusion/To restore, stop Fusion and copy the backup over /var/lib/fusion/fusion.db. Restore ownership to fusion:fusion, and start Fusion again.
Security updates
The base OS is fully patched at build time, and unattended-upgrades stays enabled, so security updates continue to apply on your VM. Run these checks:
echo "held packages (should be none):" H="$(apt-mark showhold)"; [ -z "$H" ] && echo " (none)" || echo "$H"
echo printf 'packages still upgradable: %s\n' \
"$(apt-get -s -o APT::Get::Always-Include-Phased-Updates=true dist-upgrade | grep -c '^Inst ' || true)"
echo printf 'unattended-upgrades: %s\n' "$(systemctl is-enabled unattended-upgrades.service)" grep -h Unattended /etc/apt/apt.conf.d/20auto-upgrades
echo
echo "swap (must be empty on an Azure image):" S="$(swapon --show)"; [ -z "$S" ] && echo " (empty)" || echo "$S"Enabling HTTPS with Let's Encrypt
Point a DNS A record at your VM's public IP and open 443/tcp in the network security group. Then issue a certificate. nginx is already the front end, so certbot can configure it in place:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> --agree-tos -m <your-email> --redirectCertbot installs a renewal timer automatically. Once TLS terminates at nginx, Fusion marks its session cookie Secure, because nginx forwards X-Forwarded-Proto.
Upgrading Fusion
Fusion is a single binary, so an upgrade is a download, a checksum check and a restart. Replace the version in the commands with the release you want from the project's releases page:
sudo systemctl stop fusion
sudo curl -fsSL -o /opt/fusion/fusion.new \
https://github.com/0x2E/fusion/releases/download/v<version>/fusion-linux-amd64
sudo sha256sum /opt/fusion/fusion.new
# compare with upstream checksums.txt
sudo install -m 0755 /opt/fusion/fusion.new /opt/fusion/fusion
sudo rm -f /opt/fusion/fusion.new
sudo systemctl start fusionBack up the database first. See Backing up and restoring.
Troubleshooting
Check the service log, the nginx configuration and the effective Fusion settings:
echo "--- recent fusion log ---" journalctl -u fusion.service --no-pager -n 15
echo
echo "--- nginx configuration test ---" nginx -t 2>&1
echo
echo "--- effective configuration (password not shown) ---" sudo grep -vE '^FUSION_PASSWORD=|^\s*#|^\s*$' /etc/fusion/fusion.envIf Fusion is not running, the usual cause is a missing password. Confirm that FUSION_PASSWORD is set in /etc/fusion/fusion.env, then run sudo systemctl restart fusion.
If the web interface is unreachable from outside the VM but curl http://127.0.0.1/ succeeds locally, check that the network security group allows 80/tcp.
Support
This image is published and supported by SecureAnyCloud. For assistance, contact sales@secureanycloud.com. Fusion is open source software distributed under the MIT licence. See the upstream project for product documentation.
Deploy on Azure
Launch Fusion on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need help?
Our support team is available 24/7.
sales@secureanycloud.com