Deployment guide

Warracker

Overview

Warracker is a self-hosted warranty tracker: a single searchable record of everything you own that came with a warranty, what you paid for it, when the cover runs out, and where the paperwork is. This SecureAnyCloud image runs the free and open-source, AGPL-3.0 licensed Warracker 1.0.2 natively on Ubuntu 24.04 on Azure, deployed from the Azure Marketplace.

Each product becomes one entry with its purchase date and price, vendor, serial and model numbers, warranty length or an exact expiry date, notes and tags. Receipts, invoices, manuals and product photos are uploaded and stay attached to that entry for as long as you keep it. The dashboard sorts and filters by expiry so the things about to run out surface on their own, and a status view charts what is active, what is expiring soon and what has lapsed.

Warracker can also tell you before cover lapses rather than after. Point it at your own mail server for expiry email, or at Discord, Slack, Telegram, Teams, ntfy, Pushover or a generic webhook through Apprise. It supports multiple users with an administrator role, optional single sign-on through any OpenID Connect provider, optional integration with a Paperless-ngx document archive, light and dark themes, and a translated interface in more than a dozen languages.

nginx serves the interface on port 80 and reverse-proxies the API to a gunicorn worker on the loopback address. The worker is backed by PostgreSQL 16, which also listens only on loopback.

Warracker is an independent open-source project. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by the Warracker project. It ships the free and open-source AGPL-3.0 licensed software, unmodified.

What you get:

  • Warracker 1.0.2, the AGPL-3.0 licensed upstream release, pinned by tag and verified against a SHA-256 checksum and the release commit at build time
  • A version check: the version the code declares is asserted against the pinned version at build time and again after deployment, so a mislabelled upstream tag fails the build rather than shipping
  • nginx on port 80 serving the interface and reverse-proxying /api/, installed as a host package so unattended security upgrades keep the internet-facing component patched
  • gunicorn bound to 127.0.0.1:5000 and PostgreSQL 16 bound to loopback, so the only thing exposed to your network is the web interface
  • Exactly one administrator, seeded on first boot with a password unique to your VM, and public registration closed in the same step
  • A Flask session signing key, a PostgreSQL application role password and a PostgreSQL privileged role password, all generated per VM on first boot and written to a root-only file
  • Fail-closed guards: both the application service and nginx refuse to start if any secret is empty, unrotated, or a published upstream default
  • SMTP, OIDC and Apprise credentials shipped empty, so no mail account is ever baked into the image
  • Uploaded documents stored with mode 0750 under a 0750 data directory, never served directly by nginx, and reachable only through an authenticated route
  • The complete corresponding source, its checksum, the AGPL licence text and a written offer at /usr/share/warracker/source
  • An Ubuntu 24.04 LTS base with the latest security patches applied at build time and unattended security upgrades enabled
  • The Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair for administrative access to the VM
  • A network security group that allows inbound TCP 80 from the networks you trust, and TCP 22 from your administrative network only
  • A Standard_B2s VM (2 vCPU, 4 GiB RAM), which is sufficient: Warracker, gunicorn and PostgreSQL all run comfortably in that footprint
1

Deploy from the Azure Portal

  • In the Azure Portal, search the Marketplace for Warracker on Ubuntu 24.04 LTS by SecureAnyCloud and select Create.
  • Choose your subscription, resource group and region.
  • Set the VM size to Standard_B2s or larger.
  • Under Administrator account, select SSH public key and paste your public key. The administrative user is azureuser.
  • Under Inbound port rules, allow HTTP (80) and SSH (22). Restrict both to your own address ranges wherever you can.
  • On the Disks tab, use Standard SSD for the OS disk unless you need something else.
  • Select Review + create, then Create.

First boot takes about a minute. The appliance generates its own secrets, applies the database schema, seeds the administrator and closes registration before the web interface accepts a single request.

2

Deploy from the Azure CLI

Create the resource group and network security group, then the VM. Replace the values in angle brackets with your own.

# Placeholders: substitute your own resource group, region and address range.
az group create --name my-resource-group --location eastus
az network nsg create --resource-group my-resource-group --name warracker-nsg
az network nsg rule create --resource-group my-resource-group --nsg-name warracker-nsg \
  --name allow-ssh --priority 1000 --destination-port-ranges 22 \
  --source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp
az network nsg rule create --resource-group my-resource-group --nsg-name warracker-nsg \
  --name allow-http --priority 1010 --destination-port-ranges 80 \
  --source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp
# Accept the image terms once per subscription, before the first deployment.
az vm image terms accept --publisher sac --offer warracker-ubuntu-24-04 --plan default
az vm create \
  --resource-group my-resource-group \
  --name warracker-01 \
  --image sac1647283583153:warracker-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --nsg warracker-nsg \
  --public-ip-sku Standard
3

Connect to your VM

Connect over SSH as the azureuser administrator.

ssh azureuser@<vm-ip>
4

Confirm the appliance is running

Four services make up the appliance. warracker-firstboot is a one-shot unit that provisions this machine's own secrets. It stays active after it completes because it is a RemainAfterExit unit.

systemctl is-active postgresql warracker-firstboot warracker nginx

Expected output:

active active active active

Confirm the version that is actually deployed, rather than trusting a label.

grep -o "currentVersion = '[^']*'" /var/www/warracker/version-checker.js | head -1

Expected output:

currentVersion = '1.0.2'

Check what is listening. Only :22 and :80 are reachable from outside the VM. The application server and the database are both bound to the loopback address.

ss -tlnH | awk '{print $4}' | sort -u

Expected output:

0.0.0.0:22 0.0.0.0:80 127.0.0.1:5000 127.0.0.1:5432 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:80

Confirm the interface answers.

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/

Expected output:

HTTP 200
5

Read your per-instance credentials

Every secret on this appliance is generated on this machine during first boot. Nothing is baked into the image, so no two deployments share a credential and SecureAnyCloud does not know yours. Print the credentials file.

sudo cat /root/warracker-credentials.txt

The file is readable only by root. Confirm its mode and owner.

stat -c '%a %U:%G' /root/warracker-credentials.txt

Expected output:

600 root:root

The file contains the sign-in details plus the database credentials:

  • WARRACKER_URL: the address this VM resolved for itself at first boot
  • WARRACKER_ADMIN_USERNAME: the administrator sign-in name, admin
  • WARRACKER_ADMIN_EMAIL: the administrator email address, used for password reset if you configure mail
  • WARRACKER_ADMIN_PASSWORD: the administrator password, unique to this VM
  • WARRACKER_DB_USER / WARRACKER_DB_PASSWORD: the PostgreSQL role the application uses
  • WARRACKER_DB_ADMIN_USER / WARRACKER_DB_ADMIN_PASSWORD: the privileged PostgreSQL role
  • WARRACKER_SECRET_KEY: the key that signs every session token

Change the administrator password after your first sign-in, from the account menu in the top right of the interface.

6

Sign in

Open http://<vm-ip>/ in a browser. You are sent to the sign-in page, because this appliance has no anonymous access at all.

Sign in with the WARRACKER_ADMIN_USERNAME and WARRACKER_ADMIN_PASSWORD values from the credentials file.

Note the line under the sign-in button: new account registration is currently disabled. This is deliberate, and it is the most important difference between this image and a stock Warracker deployment. See Step 7.

7

Why nobody else can claim your instance

In Warracker's own behaviour, the first person to register becomes the administrator and the owner of the instance. It accepts that first registration on an empty database regardless of the registration setting. On a public address, a stock deployment is therefore a race between you and whoever finds the port first.

This image never enters that state. First boot seeds exactly one administrator with the password in your credentials file. It closes public registration in the same step, before the web interface accepts any request at all.

Verify it yourself. A registration attempt is refused:

curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST \
  -H 'Content-Type: application/json' \
  -d '{"username":"passerby","email":"a@b.com","password":"Passw0rd1"}' \
  http://127.0.0.1/api/auth/register

Expected output, a genuine refusal and not a redirect:

HTTP 403

Unauthenticated API requests are refused too.

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/api/warranties

Expected output:

HTTP 401

The published upstream database password does not work on your instance.

PGPASSWORD=<set-at-first-boot> psql -w -h 127.0.0.1 -U warranty_user -d warranty_db \
  -c 'SELECT 1' </dev/null >/dev/null 2>&1 \
  && echo 'ACCEPTED' || echo 'rejected'

Always include -w in a probe like this. Without it, psql prompts for a password and reads it from standard input, which in a script is the rest of the script. The run then ends early and still exits 0, so a test that never ran reports success.

Expected output:

rejected

The same protection is enforced at startup, not only at first boot. A guard runs before both the application service and nginx. It refuses to let either serve if the signing key or either database password is empty, is still the value the image ships, or is a value published in Warracker's own source.

sudo /usr/local/sbin/warracker-preflight.sh

Expected output:

warracker-preflight: OK

To add more users later, sign in as the administrator and open Settings to re-enable registration. Alternatively, create the accounts yourself from the admin area.

8

Record a warranty with its paperwork

Select Add New Warranty. The dialog walks you through product details, the warranty term, documents, tags and a summary.

  • Product: name, an optional link to the product page, one or more serial numbers, model number and vendor
  • Warranty: purchase date and price, then a duration in years, months and days, an exact expiry date, or a lifetime warranty
  • Documents: the receipt or invoice, the manual, a product photo, and any other supporting file (PDF, PNG, JPG)
  • Tags: free-form labels you can filter on later

Saved entries appear on the dashboard, colour-coded by how much cover is left and sorted so the ones about to expire come first. Each card carries links to its attached documents and a countdown of the days remaining on the cover.

The Status view charts the whole collection: how many warranties are active, how many are expiring soon, how many have lapsed, and when the remaining ones run out.

9

Your uploaded documents are private

Receipts, invoices, manuals and product photos are user data, and this image treats them as private. The upload directory is not world-readable.

stat -c '%a %U:%G %n' /var/lib/warracker /var/lib/warracker/uploads

Expected output:

750 warracker:warracker /var/lib/warracker 750 warracker:warracker /var/lib/warracker/uploads

nginx refuses to serve the upload paths directly, so a document can never be fetched by guessing its filename.

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/uploads/anything.pdf

Expected output:

HTTP 403

The only route to a stored document is /api/secure-file/. It requires a valid session and checks that the session owns the warranty the document belongs to. An unauthenticated request is refused.

curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1/api/secure-file/example.pdf

Expected output:

HTTP 401
10

Expiry email and push notifications

Warracker can send expiry reminders by email and push them to a long list of services. Both ship switched off with empty credentials, because SecureAnyCloud never bakes a mail account into an image.

To enable email, add your own mail server details to the environment file and restart the service. This command edits configuration, so run it only when you have real values to hand.

# Replace every placeholder with your own mail server details before running this.
sudo sed -i \
  -e 's|^SMTP_HOST=.*|SMTP_HOST=smtp.<your-domain>|' \
  -e 's|^SMTP_USERNAME=.*|SMTP_USERNAME=<your-email>|' \
  -e 's|^SMTP_PASSWORD=.*|SMTP_PASSWORD=<set-at-first-boot>|' \
  -e 's|^SMTP_FROM_ADDRESS=.*|SMTP_FROM_ADDRESS=<your-email>|' \
  /etc/warracker/warracker.env
sudo systemctl restart warracker

For push notifications, sign in as the administrator and open Settings to enable Apprise and add your notification URLs. Examples are discord://webhook_id/webhook_token, telegram://BotToken/ChatID and ntfy://topic.

You can also set them in the same environment file.

# Replace the webhook id and token with your own before running this.
sudo sed -i -e 's|^APPRISE_ENABLED=.*|APPRISE_ENABLED=true|' \
  -e 's|^APPRISE_URLS=.*|APPRISE_URLS=discord://webhook_id/<your-token>|' \
  /etc/warracker/warracker.env
sudo systemctl restart warracker

The reminder schedule lives in the same file. APPRISE_EXPIRATION_DAYS defaults to 7 and 30 days before expiry, and APPRISE_NOTIFICATION_TIME defaults to 09:00.

11

Where your data lives

  • /var/lib/warracker/uploads: uploaded receipts, invoices, manuals and product photos
  • PostgreSQL database warranty_db: warranty records, users, tags, settings and audit log
  • /etc/warracker/warracker.env: runtime configuration and this VM's secrets, mode 0640 root:warracker
  • /root/warracker-credentials.txt: the first-boot credential summary, mode 0600 root:root
  • /opt/warracker/app: the deployed backend, migrations and translation catalogues
  • /var/www/warracker: the deployed frontend served by nginx
  • /usr/share/warracker/source: the AGPL corresponding source, its checksum, the licence and the written offer

A complete backup is the database plus the upload directory. The following commands write a dump, so run them deliberately.

# Backs up the database and the uploaded documents to <backup-dir>.
sudo -u postgres pg_dump warranty_db | gzip > <backup-dir>/warranty_db.sql.gz
sudo tar czf <backup-dir>/warracker-uploads.tar.gz -C /var/lib/warracker uploads
12

The complete source is on the machine

Warracker is licensed under the GNU Affero General Public License v3. Section 13 of that licence gives anyone who interacts with the software over a network the right to its corresponding source. That source ships on this image, unmodified, with a checksum you can verify at any time.

ls -1 /usr/share/warracker/source/

Expected output:

LICENSE WRITTEN-OFFER.txt warracker-1.0.2-source.tar.gz warracker-1.0.2-source.tar.gz.sha256

Verify it:

cd /usr/share/warracker/source && sha256sum -c warracker-1.0.2-source.tar.gz.sha256

Expected output:

warracker-1.0.2-source.tar.gz: OK

WRITTEN-OFFER.txt records the upstream project, the exact version and the exact git commit this image was built from, and SecureAnyCloud's standing offer to supply the corresponding source to anyone who asks. The deployed tree itself, under /opt/warracker/app and /var/www/warracker, is interpreted Python and JavaScript, so it is source too.

13

Day to day operation

Restart the application after a configuration change.

sudo systemctl restart warracker

Read the application log.

sudo journalctl -u warracker -n 30 --no-pager

Check that the database is healthy.

sudo -u postgres psql -w -d warranty_db -c 'SELECT COUNT(*) AS warranties FROM warranties;' </dev/null

Security updates are applied automatically. Confirm that unattended upgrades are enabled.

cat /etc/apt/apt.conf.d/20auto-upgrades

Expected output:

APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1";

Security hardening

  • Restrict inbound ports. Limit 80 and 22 in the network security group to the address ranges you actually use. Warracker holds purchase prices and scanned receipts.
  • Change the administrator password after the first sign-in, from the account menu.
  • Put HTTPS in front of it. The appliance serves plain HTTP on port 80 so that it works the moment it boots. For anything beyond a private network, terminate TLS with a certificate for a real hostname. Use an Azure Application Gateway or Front Door in front of the VM, or use certbot on the VM itself once a DNS name points at it.

To use certbot, first make sure a DNS name already points at this VM. Then run sudo apt-get install -y certbot python3-certbot-nginx, followed by sudo certbot --nginx -d <your-domain>.

After enabling HTTPS, update FRONTEND_URL and APP_BASE_URL in /etc/warracker/warracker.env so that links in reminder email point at the right address.

  • Keep registration closed unless you are actively adding users, and prefer creating accounts yourself from the admin area.
  • Back up regularly. Back up the database and the upload directory together, as described in Step 11.
  • Leave the secrets alone. The signing key and both database passwords are generated for this VM. If you rotate them by hand, rotate them in /etc/warracker/warracker.env and in PostgreSQL together, then restart. The startup guard refuses to serve on an inconsistent or empty value rather than fall back to a published default.

Troubleshooting

The web interface returns 502. gunicorn is not running. Check systemctl is-active warracker and journalctl -u warracker -n 50 --no-pager. If the startup guard rejected the configuration, the log says so explicitly.

The web interface returns nothing and nginx is inactive. Both nginx and the application are held until first boot has finished writing this VM's secrets. Check systemctl status warracker-firstboot. If it failed, journalctl -u warracker-firstboot gives the reason, and sudo systemctl restart warracker-firstboot safely re-runs it.

Sign-in fails. Confirm that you are using the values from /root/warracker-credentials.txt and not a password from another instance. A wrong password returns 401.

Registration is refused and you want more users. This is by design, as Step 7 explains. Re-enable registration in Settings, add the users, then switch it off again.

A document will not download. Documents are served only to a session that owns the warranty. Confirm that you are signed in as the right user. Then check journalctl -u warracker for the SECURE_FILE log lines, which record exactly which path was requested.

Time or expiry dates look wrong. Set the instance timezone from Settings, and confirm the VM clock with timedatectl.

Support

SecureAnyCloud provides 24/7 support with a guaranteed 24-hour response SLA for this image, covering deployment, configuration and operation of the appliance.

  • Email: sales@secureanycloud.com
  • Guides: https://secureanycloud.com/guides/

Warracker itself is an independent open-source project. Send product feature requests and upstream bug reports to https://github.com/sassanix/Warracker.

Deploy on Azure

Launch Warracker 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

Deploy on Azure