backups: daily backup, Back up now, download, restore.sh
ci / check (push) Successful in 54s
ci / images (push) Successful in 20s

A backup is data/backups/selfphoneng-YYYYMMDD-HHMM.tar.gz (mode 0600):
pg_dump, voicemail, custom sounds, music on hold, recordings if asked,
and a manifest. No generated Asterisk config: it holds plaintext
passwords and comes back from the database. The api makes one a day after
backup.time (once-a-minute check, one try a day) and the dashboard warns
when today's is missing. Settings -> Backups sets the schedule, lists them
for download and has Back up now.

scripts/restore.sh <file> refuses a backup made under another APP_SECRET
(the manifest carries a fingerprint, not the secret), asks for yes, then
replaces the database and the folders the backup has.

Tested on two throwaway servers (docker:dind running the real images):
back up on one, restore on the other. That found the api image crashing
on start since M1: tsc doesn't copy auth/common-passwords.txt into dist.
The Dockerfile copies it now and CI loads the app inside the image.

Task: M11-T3
This commit is contained in:
iamdoubz committed 2026-10-19 17:07:58 -05:00
1 parent 803859a1d2
commit 164a5f0990
16 files changed
+738 -10

No files matched your search

+17 -1
View File
@@ -407,7 +407,7 @@ Reports, history, system:
| GET/PATCH | `/api/settings` | whole settings object; PATCH merges known keys only. `smtp` comes back as `{host, port, secure, username, from, hasPassword}` and is written with an optional `password` (empty keeps the stored one). `system.*`, `sms.webhookToken` and `setup.completed` are read-only. References (DIDs, extensions, destinations) are checked, and a transport a trunk uses can't be turned off |
| POST | `/api/settings/smtp/test` | `{to}` sends a test email |
| POST | `/api/settings/emergency/test` | runs 04 §5.3 steps 2–4 with `TEST` in front (email, ring-out, banner); records nothing and dials nothing (06 §6.5). Returns `{notified}` |
| GET | `/api/system/warnings` | `{items: [{code, message, link}]}` for the dashboard banner: `restart_required`, `e911_cid_missing` (a trunk exists, no default E911 caller ID), `e911_address_unconfirmed` (that DID has no `e911_address`), `smtp_missing`, `tls_cert` (TLS on and the cert missing or expiring within 14 days, M11-T2). "Trunk down" comes from live state in the browser |
| GET | `/api/system/warnings` | `{items: [{code, message, link}]}` for the dashboard banner: `restart_required`, `e911_cid_missing` (a trunk exists, no default E911 caller ID), `e911_address_unconfirmed` (that DID has no `e911_address`), `smtp_missing`, `tls_cert` (TLS on and the cert missing or expiring within 14 days, M11-T2), `backup_old` (backups on and today's not made yet though the job had its chance, M11-T3). "Trunk down" comes from live state in the browser |
| POST | `/api/settings/network/detect-public-ip` | |
| GET | `/api/system/status` | `{ db: ok, ami: connected, asteriskVersion, uptime, trunks: [...], disk: {free, total}, restartRequired, lastGeneratorRun }` |
| GET | `/api/system/tls` | the SIP TLS certificate in `TLS_DIR`: `{ok: true, subject, names, notAfter}` or `{ok: false, error}` (M11-T2) |
@@ -456,6 +456,22 @@ Folder `/var/lib/selfphoneng/backups/selfphoneng-YYYYMMDD-HHMM.tar.gz` containin
Restore is a CLI script only: `scripts/restore.sh <file>`. It stops api+asterisk, restores the DB and files, and starts them again.
`APP_SECRET` must be the same as when the backup was made. The script warns about this.
As built (M11-T3):
- `services/backup.ts`: `pg_dump --no-owner` and GNU tar (both in the api image), folders renamed
in the archive to their `data/` names (`voicemail`, `sounds-custom`, `moh`, `recordings`) so
restore untars straight into `data/`. Written as `.<name>.tmp`, then renamed. `BACKUPS_DIR`.
- `manifest.json` = `{version: 1 (the backup format), createdAt, includesRecordings, folders,
secretCheck}`. `secretCheck` is the first 16 hex of `sha256("selfphoneng-backup:" + APP_SECRET)`:
restore.sh computes the same from `.env` and **refuses** (not just warns) when they differ.
- Restore runs from the install folder: asks for `yes`, stops api + asterisk, drops and recreates
the database, loads `db.sql`, replaces only the folders the backup has (owner `APP_UID`), starts
them again. AstDB and the generated config come back from the database when the api starts.
- Schedule: the api's once-a-minute timer (no croner yet) runs it when it's past `backup.time` in
`general.timezone` and no backup from today exists, so a fresh install backs up within a minute
of starting. One try a day; a missed one shows as the `backup_old` dashboard warning.
- Tested on two throwaway Docker hosts (`scripts/e2e/m11-t3.mjs`): back up on one, restore on the
other.
## 9. Webhooks
Events: `call.started`, `call.answered`, `call.ended`, `voicemail.new`, `sms.received`,