187 lines
9.3 KiB
Markdown
187 lines
9.3 KiB
Markdown
---
|
|
type: guide
|
|
title: Admin — System (Backup, Restore, Danger Zone)
|
|
description: How an admin navigates to /admin/system, creates a backup, stages and commits a restore, resets all scores, and wipes challenges — including the re-authentication confirmation, progress, and side effects.
|
|
tags: [guide, admin, system, backup, restore, danger-zone, confirmation]
|
|
timestamp: 2026-07-23T15:46:55Z
|
|
---
|
|
|
|
# Navigation
|
|
|
|
1. Sign in as an admin and open the admin side-nav (see the
|
|
[Admin Shell guide](/guides/admin-shell.md)).
|
|
2. Click **System** in the side-nav. The route resolves to
|
|
`/admin/system` and lazily loads the
|
|
`AdminSystemComponent` standalone component.
|
|
3. The page renders two panels side by side: **Database** (left) and
|
|
**Danger Zone** (right). On narrow screens they stack vertically.
|
|
|
|
# Database panel
|
|
|
|
## Create a backup
|
|
|
|
1. Click **Create backup** (data-testid `admin-system-create-backup`).
|
|
A spinner appears inside the button while the request is in flight.
|
|
2. The browser receives a JSON file via `Content-Disposition:
|
|
attachment; filename="hipctf-backup-YYYY-MM-DD.json"` and the
|
|
browser download UI saves it to disk.
|
|
3. On success the panel shows a green **Backup ready.** status line
|
|
(data-testid `admin-system-backup-success`).
|
|
4. On failure the panel shows a red error message under the buttons
|
|
(data-testid `admin-system-backup-error`). The friendly message
|
|
comes from `system.pure.ts` and resolves `SYSTEM_BACKUP_FAILED`,
|
|
`SYSTEM_FILE_READ_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, etc.
|
|
|
|
## Restore a backup
|
|
|
|
1. Click **Restore backup** (data-testid `admin-system-restore-backup`).
|
|
A hidden file picker opens restricted to `application/json`/`.json`.
|
|
2. Pick a backup JSON file. The progress line shows
|
|
**Reading selected backup…**, then **Validating backup
|
|
server-side…** (data-testid `admin-system-restore-progress`).
|
|
3. The server validates the JSON, decodes uploads, and verifies
|
|
sizes/checksums. On success a summary card appears
|
|
(data-testid `admin-system-restore-summary`) showing:
|
|
* Tables detected
|
|
* Uploaded files
|
|
* Total bytes
|
|
* Stage expiry (ISO timestamp)
|
|
4. From the summary card, the admin has two options:
|
|
* **Discard staged backup** (data-testid
|
|
`admin-system-discard-restore`) — drops the staged state and
|
|
resumes from a clean panel.
|
|
* **Continue to destructive restore…** (data-testid
|
|
`admin-system-confirm-restore`) — opens the confirmation modal.
|
|
|
|
### Confirmation modal (restore-backup)
|
|
|
|
* The modal title is **Restore database?**.
|
|
* The detail paragraph explains that the entire database and uploads
|
|
will be replaced (the modal's `preserved` line says **Nothing is
|
|
preserved**).
|
|
* The stage banner explicitly warns: *You will be logged out after
|
|
restore completes.*
|
|
* The admin must enter their current password and type
|
|
`RESTORE BACKUP` exactly into the confirmation phrase field.
|
|
* Click **Confirm**. The modal swaps the input fields for a
|
|
*Working…* spinner.
|
|
* The frontend calls `POST /confirmations` with `{ operation:
|
|
'restore-backup', password, stagingId }` to receive a single-use
|
|
token, then immediately calls `POST /restore/commit` with that
|
|
token and the staging id.
|
|
* On success the SPA calls `AuthService.forceServerInvalidation()`,
|
|
resets the `UserStore`, and navigates to `/login`. The
|
|
`ChallengesStore` and `ScoreboardStore` invalidate themselves via
|
|
the `SystemDataChangeService` `'restore-completed'` event.
|
|
* On failure the modal stays open and shows the resolved error
|
|
(e.g. `SYSTEM_TOKEN_MISMATCH`, `SYSTEM_RESTORE_ROLLED_BACK`). The
|
|
database panel also reflects the error inline.
|
|
|
|
# Danger Zone panel
|
|
|
|
The danger zone panel is outlined by a dashed red border and contains
|
|
two red buttons:
|
|
|
|
| Button | Operation |
|
|
|--------------------------------------------|---------------------|
|
|
| `admin-system-reset-scores` | `reset-scores` |
|
|
| `admin-system-wipe-challenges` | `wipe-challenges` |
|
|
|
|
Each button opens the same confirmation modal with operation-specific
|
|
copy:
|
|
|
|
* **Reset all scores?** — detail lists what is removed
|
|
(every solve and award), preserved (users, sessions, roles,
|
|
challenges, categories, etc.). Confirmation phrase: `RESET SCORES`.
|
|
* **Wipe all challenges?** — detail explains that every challenge, its
|
|
attached files, every solve, and every award are removed, and that
|
|
every uploaded challenge file on disk is deleted. Preserved:
|
|
users, sessions, roles, categories, settings, blog posts, and
|
|
root-level uploads (e.g. the site logo). Confirmation phrase:
|
|
`WIPE CHALLENGES`.
|
|
|
|
After the admin enters their password and types the confirmation
|
|
phrase, the SPA re-authenticates to issue a token and then commits the
|
|
operation. The panel shows a green summary
|
|
(`N solve record(s) removed.` or `N challenge(s), M file(s) wiped.`)
|
|
and a toast notification. The corresponding stores
|
|
(`ChallengesStore`, `ScoreboardStore`) automatically reset themselves
|
|
via the `SystemDataChangeService` `scores-reset` and `challenges-wiped`
|
|
events so every open tab sees fresh data.
|
|
|
|
# Cross-tab invalidation
|
|
|
|
After any of the destructive operations, the
|
|
`SystemDataChangeService` notifies all subscribers in the same tab via
|
|
an Angular signal. The injected effects in `ChallengesStore` and
|
|
`ScoreboardStore` reset their cached board/matrix/event-log/graph
|
|
data. For a restore, the SPA additionally forces a cross-tab
|
|
invalidation by calling `AuthService.forceServerInvalidation()`,
|
|
which clears the access token and notifies peer tabs via
|
|
`BroadcastChannel` + the `storage`-event fallback so they redirect to
|
|
`/login` as well.
|
|
|
|
# Error mapping
|
|
|
|
The pure helper `friendlySystemErrorMessage(op, err)` in
|
|
`system.pure.ts` converts the backend error codes into user-facing
|
|
messages. Notable mappings:
|
|
|
|
| Backend code | UI message |
|
|
|---------------------------------------|----------------------------------------------------------------------------|
|
|
| `SYSTEM_REAUTH_REQUIRED` | "Your session is no longer valid. Please log in again." |
|
|
| `SYSTEM_INVALID_CREDENTIALS` | "The current password is incorrect." |
|
|
| `SYSTEM_TOKEN_INVALID` / `_EXPIRED` | "Confirmation token is invalid / expired. Re-authenticate and try again." |
|
|
| `SYSTEM_TOKEN_REUSED` | "Confirmation token was already used. Re-authenticate to get a fresh token." |
|
|
| `SYSTEM_TOKEN_MISMATCH` | "Confirmation token does not match this operation." |
|
|
| `SYSTEM_RESTORE_VALIDATION_FAILED` | "Backup validation failed. No data was changed." |
|
|
| `SYSTEM_RESTORE_PAYLOAD_TOO_LARGE` | "The backup is larger than the configured upload limit." |
|
|
| `SYSTEM_RESTORE_STAGE_EXPIRED` | "The backup staging window expired. Re-upload and validate again." |
|
|
| `SYSTEM_RESTORE_ROLLED_BACK` | "Restore failed and was rolled back. Your data is unchanged." |
|
|
| `SYSTEM_DANGER_ROLLED_BACK` | "Operation failed and was rolled back. Nothing was changed." |
|
|
| `SYSTEM_BACKUP_FAILED` | "Backup could not be generated. No data was changed." |
|
|
| `SYSTEM_DANGER_FAILED` | "Operation could not complete. No data was changed." |
|
|
| `SYSTEM_OPERATION_IN_PROGRESS` | "Another destructive operation is currently running." |
|
|
|
|
# Examples
|
|
|
|
## Manual restore happy path
|
|
|
|
1. Download the backup from a known-good environment.
|
|
2. Sign in as the admin on the target environment.
|
|
3. Navigate to `/admin/system`, click **Restore backup**, select the
|
|
downloaded file.
|
|
4. Verify the summary matches the expected table counts and file
|
|
counts.
|
|
5. Click **Continue to destructive restore…**, enter the admin
|
|
password, type `RESTORE BACKUP`, click **Confirm**.
|
|
6. The modal closes, the page shows a brief *Restore complete.
|
|
Logging out…* toast, and the SPA navigates to `/login`.
|
|
7. If the server process is terminated during the database/uploads swap,
|
|
restart the server. Before accepting requests, startup recovery uses the
|
|
durable restore manifest to restore the complete old generation or finalize
|
|
the complete verified new generation; it never intentionally boots with a
|
|
mixed database/uploads generation. An ambiguous incomplete generation fails
|
|
startup closed and preserves the manifest and artifacts for diagnosis.
|
|
8. Sign in again with the same admin credentials. The data on the
|
|
target environment now matches the source backup.
|
|
|
|
## Manual reset happy path
|
|
|
|
1. Navigate to `/admin/system`.
|
|
2. Click **Reset all scores**, enter the password, type
|
|
`RESET SCORES`, click **Confirm**.
|
|
3. The danger zone panel shows the number of solve records removed
|
|
and a toast notifies the operation completed. Refresh the
|
|
Scoreboard page — every player is back at zero.
|
|
|
|
## Manual wipe happy path
|
|
|
|
1. Navigate to `/admin/system`.
|
|
2. Click **Wipe challenges**, enter the password, type
|
|
`WIPE CHALLENGES`, click **Confirm**.
|
|
3. The danger zone panel shows counts of challenges, files, and
|
|
solves removed. The Challenges page becomes empty and the
|
|
`/admin/challenges` list is empty. The `challenges/` folder on
|
|
disk is also gone.
|