On-server nightly copy
store.backupTo() copies the live file into BACKUP_DIR at 02:00 and deletes copies older than 30 days. CLI: node server.js --backup. This stays.
Implementation plan · 18 August 2026 · not started
Put the existing Node tracker on a Hostinger VPS behind a real domain, leave the nightly on-disk backup alone, and add one extra job: a consistent SQLite dump into an org-owned Google Drive folder every week, pruned to about 60 days. Database file only. No npm Drive SDK.
Decision
VPS, not Web Apps
Weekly Drive, daily local
Service account + folder ID
What the assignment is
The zip is a long-lived Node process plus the HTML/JS/CSS it serves from public/. That HTML is not a separate static site. If you upload public/ as a Hostinger static website, staff get a shell with no API, no login, no database. Domain DNS must point at the Node process (through nginx or Hostinger’s proxy).
The database is a single file, data/gtm.db (or gtm-data.json if SQLite is forced off). There are no uploads or attachments. Offsite backup is that file, consistently snapshotted.
store.backupTo() copies the live file into BACKUP_DIR at 02:00 and deletes copies older than 30 days. CLI: node server.js --backup. This stays.
Same snapshot, then upload to a Google Drive folder the org controls. Prune Drive objects older than 60 days. Failures log; they must not crash the tracker.
VPS, Node 22 or 24, existing systemd unit and nginx server block, Let’s Encrypt, first admin via CLI. Bind 127.0.0.1:8080; nginx terminates TLS.
Hosting
Hostinger Business/Cloud “Web Apps” can run Node from GitHub. That path is a poor fit here. Redeploys write into ~/domains/{domain}/nodejs, the panel’s database wizard is MySQL/Supabase/Mongo (not SQLite), and the process must listen on 0.0.0.0 plus a dynamic PORT. A Git push that replaces the app tree can wipe ./data.
This repo already ships production files for a VPS: deploy/gtm-tracker.service and deploy/nginx-gtm-tracker.conf. Data and backups live in /opt/gtm-tracker/data and /opt/gtm-tracker/backups, outside the git checkout, so code updates do not touch the database.
| Step | What happens |
|---|---|
| DNS | A/AAAA of the chosen hostname to the VPS. Existing nginx example is tracker.globaltiesmiami.org — swap if they use another name. |
| Runtime | Node 22 or 24 so node:sqlite is used. User gtm, files under /opt/gtm-tracker. |
| Process | Copy the systemd unit. HOST=127.0.0.1, PORT=8080, hardened ReadWritePaths for data + backups only. |
| TLS | nginx reverse proxy + certbot. App never faces the internet on plain HTTP. Login rate-limit already in the server block. |
| Calendar | Keep /calendar/ publicly reachable (token-protected). Google fetches it from outside. |
| Admin | node server.js --create-admin … once. Staff accounts from the Administration screen. |
Offsite copy
gtm, path in GDRIVE_SA_FILE — or the JSON string in an env var if they prefer.createdTime older than 60 days.The first sentence of the brief said “everyday”; the next paragraph said weekly. Interpretation: local disk stays daily (already written). Drive is the offsite dump and is weekly, so a week of mistakes can still be unwound from the server’s 30-day local copies, and Drive holds ~9 files for two months.
A naive copyFileSync of a WAL SQLite file can miss recent writes. For the Drive object (and as a small hardening of local backup when we touch it), checkpoint or VACUUM INTO a temp file, then copy/upload that snapshot.
App changes
| Path | Change |
|---|---|
| lib/drive.js (new) | Sign a JWT, exchange for an access token, upload, list, prune. Skip entirely if folder ID or key is unset so local/dev stays quiet. |
| lib/store.js | Add a consistent snapshot helper used by local backup and Drive. Do not remove the existing 30-day prune. |
| server.js | Keep the 02:00 local backup. Add a weekly Drive pass (e.g. Sunday 03:00) and --backup-drive for a manual run. |
| deploy/gtm-tracker.service | Document GDRIVE_FOLDER_ID and GDRIVE_SA_FILE. Allow the key file in ReadWritePaths or a read-only path under /etc/gtm-tracker. |
| README.md | Hostinger VPS steps, domain, Drive folder sharing, how to confirm a dump landed. |
| tests | Mock HTTPS: token, upload, prune of a 61-day-old file, no-op when env is empty. Do not call Google from CI. |
When you say go
Consistent dump file, existing local backup still 30 days, Drive client with mocked HTTP.
Sunday job and --backup-drive. Silent when Drive env is absent.
Node, systemd, nginx, certbot, first admin, confirm HTTPS login and calendar path.
Share folder, set env, run CLI once, confirm file in Drive, confirm prune logic on a fake old file.
Need from you before implementation: which Hostinger product (VPS vs Web Apps), the hostname to attach, and whether the Drive folder already exists. Sources: local server.js backup timer, lib/store.js backupTo, deploy/, Hostinger Node docs (Web Apps entry file / PORT / 0.0.0.0, 22 July 2026), Google Drive API v3 files.upload. Plan dated 18 August 2026.