--- 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-23T12:04:50Z --- # 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. 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.