|
|
@@ -16,7 +16,9 @@ gallery/ client gallery viewer, served as /gallery/?g=<slug>
|
|
|
index.php password gate, expiry, grid + lightbox
|
|
|
download.php redirects to the presigned URL of the gallery's ZIP
|
|
|
worker.php background archive builder (self-dispatching, key-protected)
|
|
|
+manage-worker.php background backup + heartbeat runner (key-protected)
|
|
|
admin/ backoffice (session-protected)
|
|
|
+ maintenance.php backup, update, migrations — the manage client's UI
|
|
|
api.php JSON API for the uploader (presign / register)
|
|
|
archive-api.php JSON API for building an archive on demand
|
|
|
topics-api.php JSON API for moving one image into a topic
|
|
|
@@ -33,11 +35,17 @@ app/ library code — blocked by .htaccess
|
|
|
exif.php metadata stripping for uploads (JPEG/PNG/WebP containers)
|
|
|
zip.php store-only ZIP64 writer
|
|
|
archive.php archive build slices, dirty queue, worker dispatch
|
|
|
+ manage.php backup/heartbeat schedule for hosts without cron
|
|
|
+ after-update.php post-update hook, run by the manage client
|
|
|
+ version.php APP_VERSION, rewritten when a release is built
|
|
|
migrate.php numbered schema migrations + the schema version constant
|
|
|
csrf.php CSRF tokens
|
|
|
partials.php shared HTML header/footer for public + admin pages
|
|
|
config/ static config (S3, site) + admin credentials — blocked
|
|
|
data/ flat-file content: site.json, galleries/<slug>.json — blocked
|
|
|
+manage-client/ update + backup client, config.php holds the instance token
|
|
|
+migrations/ one-time scripts shipped inside a release package
|
|
|
+scripts/ release build script, cron examples
|
|
|
router.php local dev only: applies the .htaccess rules under php -S
|
|
|
```
|
|
|
|
|
|
@@ -316,6 +324,48 @@ inline after `fastcgi_finish_request()` instead, and progress needs one page vie
|
|
|
per slice. A real cron job hitting `worker.php?key=…` works too and is better
|
|
|
than either (see SETUP.md).
|
|
|
|
|
|
+## Updates and backups (manage-client/, app/manage.php)
|
|
|
+
|
|
|
+The installation talks to a central manage server: it fetches releases from it
|
|
|
+and sends backups to it. The client is vendor code kept unmodified in
|
|
|
+`manage-client/`, so a newer version of it can be dropped in; everything
|
|
|
+project-specific lives outside it — `manage-client/config.php` (paths, backup
|
|
|
+sources, protected paths), `app/after-update.php`, `admin/maintenance.php` and
|
|
|
+the schedule in `app/manage.php`.
|
|
|
+
|
|
|
+**Version.** `app/version.php` defines `APP_VERSION` (`vMAJOR.MINOR.PATCH`) and
|
|
|
+nothing writes it at runtime: `scripts/create-release-zip.sh` writes it into the
|
|
|
+package, and an update changes it as a side effect of copying the file. The
|
|
|
+client reads the literal back with a regular expression rather than by including
|
|
|
+the file, so the value is correct even in the request that just deployed it.
|
|
|
+
|
|
|
+**Update.** A release is a ZIP whose root is the document root. The client
|
|
|
+verifies size and SHA-256 against the manifest, refuses a package that does not
|
|
|
+contain `app/bootstrap.php`, copies every file over the installation while
|
|
|
+saving each replaced one to `data/manage/updates/`, then runs the pending
|
|
|
+scripts in `migrations/` and finally `app/after-update.php`. `config/config.php`,
|
|
|
+`config/credentials.php`, `data/` and `media/` are on the protected list and are
|
|
|
+never written. There is no rollback and no maintenance mode, which is why the
|
|
|
+Maintenance page takes a backup first by default and why updates never happen on
|
|
|
+a timer. Deleted files are not removed — deployment is an overlay, so dropping a
|
|
|
+file is a migration's job.
|
|
|
+
|
|
|
+**Backup.** `data/` and `media/` — the flat-file content and the locally hosted
|
|
|
+images. Not gallery photos: those live in S3, which is their own copy. Not
|
|
|
+`config/`: a backup is uploaded to the manage server and can be downloaded from
|
|
|
+it, so it must not carry the S3 keys or the password hash. There is no restore
|
|
|
+command; the archive is a plain ZIP to unpack over `data/` and `media/`.
|
|
|
+
|
|
|
+**Schedule without cron.** `manage_kick()` runs at the end of every backoffice
|
|
|
+page render, the same trick as `archive_kick()`: one `stat()` on `data/manage.tick`
|
|
|
+when nothing is due, otherwise flush the page and fire a request at
|
|
|
+`manage-worker.php`, falling back to inline work where the host cannot call
|
|
|
+itself. It runs a backup when the newest scheduled one is older than
|
|
|
+`MANAGE_BACKUP_AUTO_INTERVAL_SECONDS` (a week) and a heartbeat hourly, and never
|
|
|
+an update. The release check is not on the schedule either: it is a network
|
|
|
+round trip, so it happens only when the Maintenance page is opened, which keeps
|
|
|
+every other admin page independent of the manage server being reachable.
|
|
|
+
|
|
|
## Security model
|
|
|
|
|
|
- **Admin auth**: credentials in `config/credentials.php`
|
|
|
@@ -327,11 +377,19 @@ than either (see SETUP.md).
|
|
|
- **Gallery access**: bcrypt-hashed gallery passwords; unlock state is
|
|
|
per-gallery in the session. Expiry is a pure server-side date check —
|
|
|
expired and nonexistent galleries return the identical 404 page.
|
|
|
+- **Manage client**: `manage-client/config.php` holds an instance token that is
|
|
|
+ equivalent to write access to the backups on the manage server — it is
|
|
|
+ gitignored, kept out of release packages and out of backups, and blocked from
|
|
|
+ the web twice over. `manage-worker.php` is authenticated by the same
|
|
|
+ `data/worker-key.json` key as `worker.php` and does nothing an unauthenticated
|
|
|
+ caller could exploit; `admin/maintenance.php`, which can deploy code, sits
|
|
|
+ behind the admin session and the CSRF token like every other admin page.
|
|
|
- **Web exposure**: the document root is the project folder. The root
|
|
|
- `.htaccess` blocks `app/`, `config/`, `data/`, `docs/` (via `mod_rewrite`)
|
|
|
+ `.htaccess` blocks `app/`, `config/`, `data/`, `docs/`, `manage-client/`,
|
|
|
+ `migrations/`, `scripts/` (via `mod_rewrite`)
|
|
|
and denies dotfiles, `*.json`, `*.md` and config templates (via
|
|
|
- `FilesMatch`); each of `app/`, `config/`, `data/` also carries a deny-all
|
|
|
- `.htaccess` as a fallback for hosts without `mod_rewrite`. `media/.htaccess`
|
|
|
+ `FilesMatch`); each of `app/`, `config/`, `data/` and `manage-client/` also
|
|
|
+ carries a deny-all `.htaccess` as a fallback for hosts without `mod_rewrite`. `media/.htaccess`
|
|
|
serves images only and disables PHP execution. `router.php` reproduces these
|
|
|
rules for the PHP built-in server during local development.
|
|
|
- **Input hygiene**: slugs validated by regex before touching the filesystem;
|