Skip to content

App Server Setup

Richard Kent Gates edited this page Jun 9, 2026 · 2 revisions

App Server Setup

The app.richstatistics.com server hosts the PWA web app, versioned app snapshots, Linux desktop packages (.deb), and the deploy webhook used by GitHub Actions CI. This page documents how to rebuild it from scratch on a fresh VPS.

This page is for disaster recovery. You should not need it under normal circumstances — the server stays up and CI handles all updates.

What the server hosts

URL path What it serves
/ PWA web app (current version) — app.js, app.css, index.html, etc.
/1.x.y/ Versioned app snapshots — older plugin versions load their matching app here
/desktop/ Linux .deb packages (amd64 + arm64) and Tauri update.json manifest
/_deploy/ PHP webhook called by CI on every release tag to trigger a web app update

Architecture

The deployment flow on every version tag:

  1. GitHub Actions builds the plugin ZIP and two .deb packages (amd64, arm64).
  2. The build-desktop CI job pushes the .deb files directly to the server via SSH (scp) and writes desktop/update.json.
  3. The build CI job commits versioned app snapshots to docs/app/<version>/ on the main branch.
  4. The ping-deploy CI job calls POST /_deploy/ with a secret token. The PHP webhook verifies the token and fires /usr/local/bin/rsa-app-update asynchronously.
  5. The update script sparse-clones docs/app/ from the latest GitHub release tag and rsyncs it to /var/www/rs-app/.

Server spec

Property Value
Provider Google Cloud Platform (Compute Engine)
Machine type e2-micro (or equivalent — 1 vCPU, 1 GB RAM)
OS Debian 12 (bookworm)
IP 104.197.231.120
OS user richardkentgates
Web root /var/www/rs-app/
Web server Apache 2.4 + PHP 8.2 (mod_php)
TLS Let's Encrypt (certbot, auto-renews)

Required GitHub secrets

These must be set at Repository → Settings → Secrets and variables → Actions before CI can deploy to the server.

Secret name What it is Where it comes from
APP_SERVER_SSH_KEY ED25519 private key for SSH access to richardkentgates@<server> Printed by the setup script
DEPLOY_WEBHOOK_TOKEN 32-byte hex token verified by the /_deploy/ webhook Contents of /etc/rsa-webhook-token on the server — printed by the setup script
TAURI_SIGNING_PRIVATE_KEY Tauri code-signing key for .deb update signatures Existing secret — carry over from the old repo/server

Recovery procedure

Follow these steps in order to bring a replacement server online.

Step 1 — Provision the VPS

Create a new Debian 12 instance on Google Cloud (or any provider). Minimum spec: 1 vCPU, 1 GB RAM, 10 GB disk. Note the external IP address.

  On Google Cloud: **Compute Engine → VM instances → Create instance**.
  Machine type `e2-micro` qualifies for the free tier in `us-central1`.
  Choose **Debian 12** as the boot disk. Allow HTTP and HTTPS traffic in the firewall settings.

Step 2 — Create the OS user

The setup script will create the richardkentgates user if it doesn't exist, but you need to SSH in first. On Google Cloud the default SSH user depends on your Google account; run as root or via sudo:

# On the new server — create the user if it doesn't exist yet
sudo adduser --disabled-password --gecos "" richardkentgates
sudo usermod -aG sudo richardkentgates

Step 3 — Point DNS to the new server

Update the A record for app.richstatistics.com to the new server's IP address. Let's Encrypt will fail if DNS does not resolve correctly.

Verify propagation before continuing:

dig +short app.richstatistics.com
# should return the new server IP

Step 4 — Clone the repo and run the setup script

SSH into the new server as root (or a sudo-capable user), then:

git clone https://github.com/richardkentgates/rich-statistics.git
cd rich-statistics
sudo bash bin/setup-app-server.sh --email you@example.com

The script accepts the following options:

Option Default Description
--domain app.richstatistics.com FQDN to configure Apache and certbot for
--email required for SSL Let's Encrypt registration address
--user richardkentgates OS user who owns /var/www/rs-app
--skip-ssl off Skip certbot — use this if DNS isn't ready yet; run sudo certbot --apache -d <domain> later
--skip-deploy off Skip the initial rsa-app-update run

The script will:

  • Install Apache 2.4, PHP 8.2, certbot, git, rsync, fail2ban, ufw
  • Enable Apache modules: rewrite, headers, ssl, php8.2
  • Create /var/www/rs-app/ with the correct ownership and subdirectory structure
  • Deploy the webhook handler to /var/www/rs-app/_deploy/index.php
  • Generate a random 32-byte deploy token and store it at /etc/rsa-webhook-token
  • Install the update script to /usr/local/bin/rsa-app-update
  • Configure sudoers so www-data can run the update script
  • Write the Apache virtual-host config and obtain a Let's Encrypt certificate
  • Generate a fresh ED25519 SSH keypair for CI and add the public key to ~/.ssh/authorized_keys
  • Run an initial deploy to pull the current app files from GitHub
  • Print the DEPLOY_WEBHOOK_TOKEN and APP_SERVER_SSH_KEY values for GitHub

Step 5 — Update GitHub secrets

At the end of the setup script, two secret values are printed to stdout. Copy them into GitHub:

Repository → Settings → Secrets and variables → Actions

  1. Update (or create) APP_SERVER_SSH_KEY with the new ED25519 private key.

  2. Update (or create) DEPLOY_WEBHOOK_TOKEN with the token string.

    The old server's token and SSH key will no longer work once you update these secrets. Make sure the new server is fully up and serving traffic before cutting over.

Step 6 — Verify the server

# App is reachable over HTTPS
curl -I https://app.richstatistics.com/

# .deb files are accessible
curl -I https://app.richstatistics.com/desktop/rich-statistics-linux-amd64.deb

# Tauri update manifest is valid JSON
curl -s https://app.richstatistics.com/desktop/update.json | python3 -m json.tool

# Webhook returns 405 on GET (method not allowed — correct)
curl -I https://app.richstatistics.com/_deploy/

Step 7 — Trigger a re-release (optional)

If you want to push the current version's .deb files and run the full CI pipeline against the new server, re-push the latest tag:

git fetch --tags
LATEST=$(git describe --tags $(git rev-list --tags --max-count=1))
git tag -d "${LATEST}" && git push origin ":refs/tags/${LATEST}"
git tag "${LATEST}" && git push origin "${LATEST}"
  Re-pushing a tag re-runs the full `build-release` workflow, which builds the plugin ZIP, both `.deb` packages, and creates a new GitHub Release. Only do this if you actually need fresh `.deb` files or want to test the new server end-to-end.

File layout on the server

/var/www/rs-app/
├── .deployed-version       # tag of the last successful deploy, e.g. "v1.4.2"
├── app.js                  # current PWA JavaScript
├── app.css                 # current PWA styles
├── index.html              # PWA entry point
├── config.js               # PWA configuration (site URL list is user-specific)
├── sw.js                   # service worker
├── manifest.json           # PWA manifest
├── chart.min.js            # bundled Chart.js 4.x
├── versions.json           # ordered list of all deployed version tags
├── icons/                  # PWA icon set
├── 2.4.25/
│   ├── stable/             # versioned snapshot (immutable, 1-year cache)
│   └── beta/
├── 2.4.26/
│   ├── stable/
│   └── beta/
├── desktop/
│   ├── rich-statistics-linux-amd64.deb
│   ├── rich-statistics-linux-amd64.deb.sig
│   ├── rich-statistics-linux-arm64.deb
│   ├── rich-statistics-linux-arm64.deb.sig
│   └── update.json          # Tauri auto-update manifest
└── _deploy/
    └── index.php            # deploy webhook (from bin/server-webhook.php)

/etc/rsa-webhook-token       # shared secret (root:www-data, mode 640)
/etc/sudoers.d/rsa-app-update
/usr/local/bin/rsa-app-update   # deploy script (from bin/server-update-webapp.sh)
/etc/letsencrypt/              # certbot managed — do not touch manually
/etc/apache2/sites-available/rs-app.conf
/etc/apache2/sites-available/rs-app-le-ssl.conf  # written by certbot
/var/log/apache2/rs-app-*.log
/var/log/rsa-deploy.log         # output of rsa-app-update runs

Troubleshooting

Webhook returns 401

The X-Deploy-Token header doesn't match /etc/rsa-webhook-token. Check that the DEPLOY_WEBHOOK_TOKEN GitHub secret matches the file exactly (no trailing newline).

cat /etc/rsa-webhook-token   # value on the server (no newline after hex string)

Webhook returns 202 but app files don't update

The update script runs asynchronously — check the deploy log:

tail -50 /var/log/rsa-deploy.log

Common causes:

  • sudo permission missing — verify /etc/sudoers.d/rsa-app-update exists and is 440.
  • git or rsync not installed — run sudo apt-get install git rsync.
  • GitHub API rate limit — check curl -sf https://api.github.com/repos/richardkentgates/rich-statistics/tags.

SSL certificate not renewing

Certbot installs a systemd timer that auto-renews. Check it:

sudo systemctl status certbot.timer
sudo certbot renew --dry-run

.deb files are 404

The build-desktop CI job uploads .deb files directly via SSH (not via the webhook). If the job failed, the files won't be on the server. Check the GitHub Actions run for the tag and re-run the build-desktop job if needed.

Manual app update

Re-run the update script at any time as the server user:

sudo -u richardkentgates /usr/local/bin/rsa-app-update

Source files in the repo

These files in the repository are the authoritative source for the server-side components. The setup script installs them to the right places — do not edit them directly on the server.

Repo path Installed to Purpose
bin/setup-app-server.sh run once on new server Full server provisioning script
bin/server-webhook.php /var/www/rs-app/_deploy/index.php Deploy webhook handler
bin/server-update-webapp.sh /usr/local/bin/rsa-app-update Web app update script (called by webhook)

Documentation

Features

User Guide

Compliance


External Links

Clone this wiki locally