Initial scaffold: Borg engine + docs (Layer 1 milestone)

apiscp-borg is a sibling of apiscp-kopia built on BorgBackup. This first
milestone lands the project scaffold and the Layer 1 engine:
- borg-apiscp-backup: per-site named archives (siteN-shadow/info/db), fresh
  per-site DB dumps via ApisCP site-context export, system + custom archives,
  borg prune retention per prefix, Prometheus metrics, email notifications.
- apiscp-borg-common.sh: logging, mail, site/owner helpers.
- config example, systemd service+timer, install.sh, README, DESIGN.

Borg preserves POSIX ACLs and xattrs natively, so (unlike the kopia engine) no
metadata sidecar is required; DESIGN.md records how Borg reshapes the design.
Repository/restore tools, Layer 2 panel integration, hooks, uninstall, and the
full reference are the next milestones.
This commit is contained in:
Laurence Horrocks-Barlow 2026-07-24 22:33:37 +01:00
commit 2f0d93a0ae
10 changed files with 743 additions and 0 deletions

101
docs/DESIGN.md Normal file
View file

@ -0,0 +1,101 @@
# Design
apiscp-borg is a sibling of apiscp-kopia: the same ApisCP integration
architecture, but built on [BorgBackup](https://www.borgbackup.org/) instead of
kopia. This document records how Borg changes the design.
## What is the same as apiscp-kopia
- **Two upgrade-safe layers.** Layer 1 is a standalone POSIX-sh engine plus
tools, driven by a systemd timer, with zero coupling to ApisCP's PHP. Layer 2
is an ApisCP module, GUI apps, and account hooks under
`/usr/local/apnscp/config/custom` (git-ignored, so they survive `upcp`).
- **Runs `backup_dbs.php` first** so per-site logical database dumps are current
before they are archived. A plain cron job that runs before ApisCP's own
`10backup_dbs` captures day-stale dumps; running it in the engine fixes that.
- **Per-site granularity.** Each site is archived as its own named set so a
single site can be restored and retention can be reasoned about per site.
- **Per-site database handling through ApisCP's site-context API.** Host
`mysqldump` cannot see per-site databases, so the engine dumps each site's
databases with `cpcmd -d <domain> mysql:export` (and pgsql), which resolves
the target path INSIDE the site filesystem namespace, then archives the dump
from its real host location under the site's `fst` tree and removes it. Import
is the guarded native `mysql:import` / `pgsql:import`.
- **Security boundary.** The panel runs the module as the unprivileged `apnscp`
user; a scoped `/etc/sudoers.d/apiscp-borg` lets it run only the plugin
binaries as root, and the binaries validate their own input. Site-owner verbs
derive the site from the ApisCP auth context and never trust a site parameter.
- **Editions.** An appliance-admin edition and a site-owner ("My Backups")
edition scoped to a single account.
- **Retention, schedule, maintenance/verify, email notifications, one-time key
backup, and lifecycle hooks**, mirroring apiscp-kopia.
## What Borg changes (and why the rationale differs)
1. **ACLs and extended attributes are preserved natively.** This is the single
biggest difference from kopia. `borg create` stores POSIX ACLs and xattrs by
default on Linux, and `borg extract` restores them (use `--numeric-ids` for
servers so ownership maps by id, matching ApisCP's account model). apiscp-kopia
exists largely *because* kopia drops that metadata and needs ACL/xattr
sidecars; Borg needs no sidecars. The plugin's value here is instead: correct
ordering (fresh DB dumps), per-site archives, retention (`borg prune`),
encryption and passphrase safety, and native ApisCP GUI/CLI integration.
(An optional sidecar capture remains available as belt-and-braces but is off
by default because Borg already preserves the metadata.)
2. **Archives, not tags.** Borg has named archives in a repository rather than
kopia's tagged snapshots. The engine names archives with a structured prefix
so per-site operations are glob-addressable:
`siteN-shadow-{now}`, `siteN-info-{now}`, `siteN-db-{now}`, `_system-{now}`,
`_custom-<slug>-{now}` (timestamps in Borg's `{now}` placeholder). Restore
picks an archive by name; `borg list` enumerates them; per-site retention is
`borg prune --glob-archives 'siteN-*' --keep-*`.
3. **Retention is `borg prune`.** Retention runs as an explicit `borg prune`
after each backup (and can be run on demand), with keep-within/keep-daily/
weekly/monthly/annual rules from the config, applied per site prefix so one
site's churn cannot expire another's history.
4. **Maintenance is `borg compact`;** integrity is `borg check` (repository and
archive consistency, optionally `--verify-data` to read everything back).
5. **Transport and backends.** Borg speaks its own protocol over SSH. Backends
are a local path (`/mnt/...`) or a remote `ssh://user@host/path` (with a
dedicated SSH key and, ideally, an `append-only` `borg serve` restriction on
the server). There is no kopia-style repository server or sftp backend; ssh
is the remote story.
6. **Encryption and the key.** Repositories are created with
`--encryption=repokey-blake2` by default, so the encryption key lives inside
the repository and only the passphrase is needed to use it. That makes
recovery simpler than keyfile mode (which stores the key under
`~/.config/borg` and must be backed up separately). The one-time key backup
exports the passphrase plus `borg key export` output and stores it off the
machine; losing it makes the encrypted repository unrecoverable, exactly as
with kopia.
7. **Single-writer locking.** A Borg repository is locked to one operation at a
time. The engine holds a local lock and relies on Borg's own repository lock;
concurrent site-owner actions queue rather than run in parallel.
## Layer 1 (this scaffold)
- `bin/borg-apiscp-backup`: the engine (this milestone).
- `bin/borg-apiscp-repo`: repository init/connect, key backup, retention/prune,
maintenance (compact), check, schedule, notifications. (Next.)
- `bin/borg-apiscp-restore`: per-site, subpath, whole-account, and system
restore via `borg extract`, plus site-owner-scoped variants. (Next.)
- `lib/apiscp-borg-common.sh`: shared helpers (logging, mail, site helpers,
optional metadata sidecar).
## Layer 2 (next)
The ApisCP module (`Borg_Module_Surrogate`), the admin GUI app, the site-owner
GUI app ("My Backups"), the menu templates, and the account hooks, mirroring
apiscp-kopia verb-for-verb where it makes sense.
## Status
Scaffold and Layer 1 engine. The repository tools, restore tool, Layer 2 panel
integration, hooks, uninstall, and the full reference are being built out next,
tracking the apiscp-kopia feature set.