--- 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.