App Server Setup

The rs-app.richardkentgates.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 pathWhat 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

PropertyValue
ProviderGoogle Cloud Platform (Compute Engine)
Machine typee2-micro (or equivalent — 1 vCPU, 1 GB RAM)
OSDebian 12 (bookworm)
IP104.197.231.120
OS userrichardkentgates
Web root/var/www/rs-app/
Web serverApache 2.4 + PHP 8.2 (mod_php)
TLSLet'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 nameWhat it isWhere it comes from
APP_SERVER_SSH_KEYED25519 private key for SSH access to richardkentgates@<server>Printed by the setup script
DEPLOY_WEBHOOK_TOKEN32-byte hex token verified by the /_deploy/ webhookContents of /etc/rsa-webhook-token on the server — printed by the setup script
TAURI_SIGNING_PRIVATE_KEYTauri code-signing key for .deb update signaturesExisting 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 rs-app.richardkentgates.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 rs-app.richardkentgates.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:

OptionDefaultDescription
--domainrs-app.richardkentgates.comFQDN to configure Apache and certbot for
--emailrequired for SSLLet's Encrypt registration address
--userrichardkentgatesOS user who owns /var/www/rs-app
--skip-ssloffSkip certbot — use this if DNS isn't ready yet; run sudo certbot --apache -d <domain> later
--skip-deployoffSkip the initial rsa-app-update run

The script will:

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://rs-app.richardkentgates.com/

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

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

# Webhook returns 405 on GET (method not allowed — correct)
curl -I https://rs-app.richardkentgates.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
├── 1.3.0/                  # versioned snapshot (immutable, 1-year cache)
├── 1.4.0/                  # …
├── 1.4.1/
├── 1.4.2/
├── 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:

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 pathInstalled toPurpose
bin/setup-app-server.shrun once on new serverFull server provisioning script
bin/server-webhook.php/var/www/rs-app/_deploy/index.phpDeploy webhook handler
bin/server-update-webapp.sh/usr/local/bin/rsa-app-updateWeb app update script (called by webhook)