Deployment guide

Warracker 1.0.2

Overview

Warracker is a self-hosted warranty tracker. It keeps one searchable record of everything you own that came with a warranty: what you paid, when you bought it, when the cover runs out and where the paperwork is. Each product carries its purchase date and price, vendor, serial and model numbers, the warranty length (or an exact expiry date, or lifetime cover), notes and tags. You can attach the receipt or invoice, the manual, a product photo and any other document you upload.

The dashboard colors every warranty by how much cover is left, and the status view charts what is active, expiring soon and expired. Warracker can remind you before cover lapses by email or through Apprise push notifications. It supports several users with an administrator role, optional OpenID Connect single sign-on and optional Paperless-ngx integration.

This SecureAnyCloud image runs Warracker 1.0.2 natively on Ubuntu 24.04 on Amazon EC2. nginx serves the interface on port 443 and passes the API to gunicorn on the loopback address, backed by PostgreSQL 16 on the same instance. It is one version of the SecureAnyCloud Helpdesk, ITSM and Project Management listing on AWS Marketplace. You select it as the software version when you subscribe and launch.

What you get:

  • HTTPS from the first boot. nginx serves Warracker on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • Nobody can claim your instance. Upstream Warracker makes the first person to register the administrator and owner, and accepts that registration on an empty database whatever the registration setting says. First boot creates exactly one administrator with a password unique to your instance and closes public registration in the same step, before the web server starts.
  • Secrets and data unique to your instance. The administrator password and the key that signs every session token are generated on first boot and written to root-only files. The PostgreSQL database itself is also created on first boot. The image ships no database at all, so no account, password or password hash from the build can be in it. Warracker connects to PostgreSQL over the local socket as its own system user, so there is no database password to manage or leak.
  • Fail closed. The application refuses to start with a missing, example or published default signing key, and nginx refuses to start until first boot has finished.
  • Upstream security fixes applied. Warracker 1.0.2 is the newest release. Two security fixes made since on Warracker's own main branch, and not yet released, are applied to it. One stops a signed-in user from downloading another user's receipts and manuals through the /api/files/ route. The other escapes warranty fields so they cannot inject script into the page. The Python packages are upstream's choices, moved to releases with no known vulnerability.
  • Documents stay private. Receipts, manuals and photos are stored readable by the Warracker service only. nginx never serves them from disk, and the only way to download one is through the signed-in, owner-checked API.
  • Host names from configuration, never from a request. The names Warracker answers to come from your instance's EC2 metadata (or a name you set). Requests for any other host name get no answer, and links in password reset and expiry emails are built from the configured name.
  • Loopback only, metadata blocked. gunicorn (127.0.0.1:5000) and PostgreSQL (127.0.0.1:5432 and the local socket) are reachable only on the instance. The only open ports are 22, 80 and 443. gunicorn, nginx and PostgreSQL are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • The real client address, no cross-origin access. nginx passes the address of the actual connection to Warracker, never a header a client can forge. It strips cross-origin (CORS) headers so another website cannot use your session.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Warracker 1.0.2 (the GitHub release archive, checksum verified, with the two upstream security fixes), Flask 3.1, gunicorn 23, psycopg2, PostgreSQL 16, nginx, Python 3.12, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management)
  • A security group that allows TCP 22 (SSH), TCP 443 (Warracker) and TCP 80 (redirects to HTTPS). Allow all three from your own networks only. Warracker holds your purchase records and documents, and is meant for you and the people you add.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Warracker 1.0.2 on Ubuntu 24.04 as the software version and select your Region. Then choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above.

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=warracker}]'
2

Connect to your instance

Version SSH user

Warracker 1.0.2 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot creates the PostgreSQL database and the Warracker schema. Allow about 30 seconds after the instance reaches Running before you sign in. Then confirm that the units are active:

systemctl is-active postgresql@16-main warracker nginx warracker-firstboot
active active active active

To see how long first boot took on your instance, run:

sudo journalctl -u warracker-firstboot -o cat | grep -E 'migrations took|first boot complete'
[<time>] warracker-firstboot: migrations took <n> s (54 applied) [<time>] warracker-firstboot: first boot complete in <n> s; Warracker will be served at https://<public-ip>/

gunicorn and PostgreSQL listen on loopback only. nginx serves ports 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /gunicorn|postgres|nginx|sshd/' | awk '{print $1, $4}'
State Local LISTEN 127.0.0.1:5000 LISTEN 127.0.0.1:5432 LISTEN 0.0.0.0:443 LISTEN 0.0.0.0:80 LISTEN 0.0.0.0:22 LISTEN [::]:443 LISTEN [::]:80 LISTEN [::]:22
4

Get your URL and administrator password

Read the credentials file:

sudo cat /root/warracker-credentials.txt

The file holds the address Warracker is served on (your instance's public IP address by default), the administrator user name and the password generated on this instance:

WARRACKER_URL=https://<public-ip>/ WARRACKER_ADMIN_USERNAME=admin WARRACKER_ADMIN_EMAIL=admin@example.invalid WARRACKER_ADMIN_PASSWORD=<WARRACKER_ADMIN_PASSWORD>

The database has no password. The warracker PostgreSQL role can only log in over the local socket as the warracker system user, and no role on the instance has a password at all:

sudo -u postgres psql -Atc "SELECT rolname, rolsuper, rolcreaterole, rolpassword IS NOT NULL FROM pg_authid WHERE rolname IN ('postgres','warracker') ORDER BY 1"
postgres|t|t|f warracker|f|f|f

The credentials file is readable by root only. The environment file that holds the session signing key is readable by root and the warracker service only. A checker on the image proves that none of these secrets reached the system journal, any log, /var/backups, shell history or a command line:

sudo stat -c '%a %U:%G %n' /root/warracker-credentials.txt /etc/warracker/warracker.env /etc/ssl/warracker/warracker.key
sudo bash /usr/local/sbin/warracker-secret-leak-check.sh
600 root:root /root/warracker-credentials.txt 640 root:warracker /etc/warracker/warracker.env 600 root:root /etc/ssl/warracker/warracker.key LEAKCHECK_OK no per-instance secret in the journal, any log, /var/backups, history, MOTD, /etc (outside the env file) or a command line
5

Sign in

Open WARRACKER_URL in your browser. The certificate is self-signed and generated on your instance, so accept the browser warning once, or set your own name and certificate (Step 10).

Warracker has no anonymous access. You land on the sign-in page, which also tells visitors that new account registration is disabled.

Sign in as admin with the password from Step 4. Then change the password under Settings (the user menu at the top right).

6

Record a warranty with its paperwork

Select Add New Warranty. The dialog walks you through the product, the warranty term, the 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 lifetime cover
  • Documents: the receipt or invoice, the manual, a product photo and any other supporting file (PDF, PNG, JPG), up to 32 MB each
  • Tags: labels you can filter on later

Saved warranties appear on the home page as cards, colored by how much cover is left: green, orange when expiring soon, and red when expired. Each card shows a countdown of the days remaining and links to its documents. Use the toolbar to search, filter, sort and change the view.

Status charts the whole collection. It shows how many warranties are active, expiring soon and expired, the distribution, an expiration timeline, and a table of what has recently lapsed or is about to. You can export the table to CSV.

7

Add people

Public registration is closed, so nobody can create an account on your instance by finding its address.

Under Settings, the Admin Settings section lists users and lets you manage them. Its Registration Enabled switch reopens registration for a while if you want family or colleagues to sign themselves up. Switch it off again afterwards.

Ordinary users see only their own warranties and documents, because the Global View Admin Only setting is on. An administrator can change that under Admin Settings. This command confirms that registration is closed:

curl -sk --resolve "$(sudo sed -nE 's#^WARRACKER_URL=https://([^/]+)/.*#\1#p' /root/warracker-credentials.txt):443:127.0.0.1" \
  "https://$(sudo sed -nE 's#^WARRACKER_URL=https://([^/]+)/.*#\1#p' /root/warracker-credentials.txt)/api/auth/registration-status"; echo
{"enabled":false}
8

Your documents are private

Uploaded receipts, manuals and photos live in /var/lib/warracker/uploads, readable by the warracker service only. nginx refuses to serve that directory. The only download route is the Warracker API, which needs a signed-in session and checks that the document belongs to you, that you are an administrator, or that an administrator has shared that kind of document.

The image includes upstream's fix for the older /api/files/ route, which in Warracker 1.0.2 skipped that ownership check. The fixes applied, and the reason for each, are written down on the instance:

sed -n '1,12p' /usr/share/warracker/SAC-SECURITY-BACKPORTS.txt
Security fixes applied to Warracker 1.0.2 by sac ===================================================== Warracker 1.0.2 (2025-10-30) is the newest Warracker release. Two security fixes have since been made on Warracker's main branch and are not yet in a release; this image carries both, applied to the 1.0.2 source. 1. Upstream commit 696f3a2b (2026-10-04) "Fix authorization bypass on /api/files/ document route" In 1.0.2 the legacy /api/files/<path> route served any uploaded document to any signed-in user, with no ownership check and no path-traversal guard. Both download routes now share one authorization check (the document's owner, an administrator, or the admin's document-sharing setting). Applied: backend/file_routes.py (upstream's change, verbatim).

The nginx access log records the path of each request but never its query string, so a session token passed in a link never ends up in the log.

9

Optional expiry email and push notifications

Email and push notifications are optional and switched off by default. SecureAnyCloud never bakes a mail account into an image. To send password reset and expiry emails through your own SMTP server, fill in the mail settings in /etc/warracker/warracker.env:

SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USERNAME=warracker@example.com SMTP_PASSWORD=<your-smtp-password> SMTP_SENDER_EMAIL=warracker@example.com SMTP_FROM_ADDRESS=warracker@example.com SMTP_USE_TLS=true

Then restart the application:

sudo systemctl restart warracker

For push notifications through Apprise (Discord, Slack, Telegram, Teams, ntfy, Pushover, webhooks and many more), sign in as the administrator and enable Apprise under Settings. Alternatively, set APPRISE_ENABLED=true and APPRISE_URLS in the same file. The reminder schedule lives there too: APPRISE_EXPIRATION_DAYS (by default 7 and 30 days before expiry) and APPRISE_NOTIFICATION_TIME.

Links in emails are always built from the name Warracker is configured with (Step 10), never from the host name a visitor's request claims.

10

Your own DNS name and certificate

Point a DNS record at the instance, then set the name Warracker is served on. The command below creates a new self-signed certificate for the name. It updates nginx, the base URL used in email links and single sign-on redirects, and the URL in /root/warracker-credentials.txt. The name persists across reboots:

sudo warracker-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running warracker-set-hostname again replaces it with a self-signed one:

sudo install -m 0644 <your-domain>.crt /etc/ssl/warracker/warracker.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/warracker/warracker.key
sudo nginx -t && sudo systemctl reload nginx
11

Security checks

Run these checks to confirm four things. A request for a host name the instance does not serve gets no answer. Port 80 redirects to HTTPS. The API needs a sign-in. nginx refuses to serve uploads from disk.

H=$(sudo sed -nE 's#^WARRACKER_URL=https://([^/]+)/.*#\1#p' /root/warracker-credentials.txt)
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: evil.example' http://127.0.0.1/ || true
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'warranty list without signing in: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/warranties"
curl -sk -o /dev/null -w 'uploads straight off disk: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/uploads/anything.pdf"
unknown host name: 000 port 80: 301 -> https://<public-ip>/ warranty list without signing in: 401 uploads straight off disk: 403

These checks confirm that the application runs unprivileged in a sandbox, that gunicorn, nginx and PostgreSQL cannot reach the instance metadata service, and that nginx passes the real client address from the connection and strips cross-origin headers:

systemctl show warracker -p User -p ProtectSystem -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx postgresql@16-main -p IPAddressDeny grep -E 'proxy_set_header (X-Forwarded-For|X-Real-IP|X-Forwarded-Host)|proxy_hide_header Access-Control-Allow-Origin' /etc/nginx/sites-available/sac-warracker
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 User=warracker ProtectSystem=full NoNewPrivileges=yes IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Host ""; proxy_hide_header Access-Control-Allow-Origin;

The certificate is generated on this instance and names its own addresses:

H=$(sudo sed -nE 's#^WARRACKER_URL=https://([^/]+)/.*#\1#p' /root/warracker-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = <public-ip>, O = sac X509v3 Subject Alternative Name: IP Address:<public-ip>, DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
12

Verify the whole workflow yourself

The image ships the check it is built and tested with. Over HTTPS on the configured name, the script proves the following:

  • Registration is closed, and the API refuses anonymous callers.
  • A wrong administrator password is refused, and the real one signs in as the owner.
  • A session token forged with Warracker's published fallback key is refused.
  • A warranty with a PDF receipt can be recorded and downloaded back byte for byte, and the download is refused without a session.
  • A second, ordinary user cannot read the administrator's receipt through either download route.
  • The escaping fix is in the front end being served.

Finally, the script removes the probe warranty and user, so nothing is left behind:

sudo bash /usr/local/sbin/warracker-verify-workflow.sh
WORKFLOW_OK warranty=<id> receipt=round-trip anonymous=401 uploads=403 other-user=403/403 forged-jwt=401 register=403

To list the installed versions, run:

/opt/warracker/venv/bin/python -c "import importlib.metadata as m; print(' | '.join(f'{p} {m.version(p)}' for p in ('flask','werkzeug','gunicorn','psycopg2','pyjwt','flask-cors')))" grep -oE "currentVersion = '[^']+'" /var/www/warracker/version-checker.js head -2 /usr/share/doc/sac/warracker-python-audit.txt | tail -1
flask 3.1.3 | werkzeug 3.1.9 | gunicorn 23.0.0 | psycopg2 2.9.11 | pyjwt 2.15.0 | flask-cors 6.0.5 currentVersion = '1.0.2' No known vulnerabilities found
13

Backups

Back up the database and the uploaded documents. The dump goes to a root-only directory. Never copy /etc/warracker/warracker.env into a shared backup location, because it holds the session signing key:

sudo install -d -o root -g root -m 0700 /var/backups/warracker
sudo sh -c 'umask 077; runuser -u postgres -- pg_dump warranty_db | gzip > /var/backups/warracker/warranty_db-$(date -u +%Y%m%d).sql.gz'
sudo tar -C /var/lib/warracker -czf /var/backups/warracker/uploads-$(date -u +%Y%m%d).tar.gz uploads

Copy the files off the instance to your own backup storage, or snapshot the EBS volume.

14

Server components

Component Version Path

Warracker backend 1.0.2 + 2 upstream security fixes /opt/warracker/app, warracker.service, 127.0.0.1:5000

Warracker front end 1.0.2 + upstream escaping fix /var/www/warracker

Python environment Flask 3.1, gunicorn 23 /opt/warracker/venv

PostgreSQL 16 postgresql@16-main.service, 127.0.0.1:5432 and the local socket, /var/lib/postgresql/16/main (created on first boot)

nginx 1.24 /etc/nginx/sites-available/sac-warracker

Settings /etc/warracker/warracker.env (root:warracker, 0640)

Uploaded documents /var/lib/warracker/uploads (warracker, 0750)

Certificate and key /etc/ssl/warracker/

First boot warracker-firstboot.service, /usr/local/sbin/warracker-firstboot.sh

Host name helper /usr/local/sbin/warracker-set-hostname

Credentials /root/warracker-credentials.txt (0600)

Source (AGPL-3.0) /usr/share/warracker/source

Logs: journalctl -u warracker (application), journalctl -u warracker-firstboot (first boot), /var/log/nginx/, /var/log/postgresql/.

15

The complete source is on the instance

Warracker is licensed under the GNU Affero General Public License v3. The image ships the exact upstream release archive with its checksum, the license, the two security fixes as applied, and a written offer:

cd /usr/share/warracker/source && sha256sum -c warracker-1.0.2-source.tar.gz.sha256 && ls -1
warracker-1.0.2-source.tar.gz: OK LICENSE WRITTEN-OFFER.txt sac-backport-696f3a2b-file-routes-authz.patch sac-backport-a1ced8b6-xss.py warracker-1.0.2-source.tar.gz warracker-1.0.2-source.tar.gz.sha256

Troubleshooting

The site does not answer. First boot may still be running. It creates the database, so give it about 30 seconds. Alternatively, first boot may have failed, in which case nginx and the application stay stopped by design. Check the first-boot unit:

systemctl show -p ActiveState -p Result warracker-firstboot.service
Result=success ActiveState=active

If Result is not success, read sudo journalctl -u warracker-firstboot --no-pager.

The browser gets no answer for a name you use. Warracker answers only to its configured name, the instance's own EC2 addresses and names, and localhost. Set your name with sudo warracker-set-hostname <your-domain> (Step 10).

The application will not start after you edited the settings. A guard refuses to start Warracker with an empty or weak signing key, or with a database password set. To see the reason, run sudo /usr/local/sbin/warracker-preflight.sh.

The administrator password is lost. If outbound email is set up (Step 9), use Forgot Password? on the sign-in page. Without email, sign in as another administrator, or ask SecureAnyCloud support for the reset procedure.

Security notes

  • Allow ports 22, 80 and 443 from your own networks only.
  • Change the admin password after your first sign-in, give everyone a named account and keep public registration closed.
  • Keep /root/warracker-credentials.txt and /etc/warracker/warracker.env as shipped. The environment file holds the session signing key, and anyone who reads it can forge sessions.
  • Use your own DNS name and a certificate your users trust (Step 10).
  • Keep the operating system patched. Unattended security upgrades are on.
  • Back up the database and documents regularly (Step 13).

Support and licensing

Warracker is open source under the GNU Affero General Public License v3. The source, license and written offer are in /usr/share/warracker/source. There is no per-user or per-CPU license fee. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.

Warracker is the name of the open source project by sassanix. SecureAnyCloud is not affiliated with or endorsed by the Warracker project. The image ships with no warranties.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch Warracker 1.0.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS