# UniFi Backup Restore Pitfalls (v10.0.162, Jul 2026)

## The Core Problem

UniFi `.unf` backup files are **encrypted BSON**. They cannot be read by:
- `zipfile` (Python) — throws `BadZipFile`
- `bsondump` (MongoDB) — reports "invalid BSONSize"
- `mongorestore` — can't parse the encryption

The ONLY way to restore a `.unf` is through the UniFi `ace.jar restore` command, which decrypts and imports the data into a running MongoDB.

## Failed Restore Approaches

### 1. CLI: `java -jar ace.jar restore <file>` (FAILED)

**Error:** `NoSuchBeanDefinitionException: No qualifying bean of type 'com.ubnt.service.system.status.o0OO'`

**Root cause:** `ace.jar restore` instantiates the FULL Spring application context, not just a MongoDB connection. It requires all UniFi service beans to be available. Running it in isolation (even with just MongoDB) fails because the Spring context expects the complete application environment.

**Attempted workarounds that also failed:**
- SIGSTOP the running UniFi Java process, then run restore → Spring context collision
- SIGKILL the process → entrypoint restarts it
- One-off Docker container with just mongod + ace.jar → same Spring bean missing

### 2. API: `POST /api/s/default/cmd/backup` (FAILED)

**Error:** `{"meta":{"rc":"error","msg":"api.err.NoSiteContext"}}`

**Root cause:** In UniFi 10.x, even with valid authentication (`unifises` cookie from `/api/login` → `{"meta":{"rc":"ok"}}`), the backup/restore API endpoints require a site context. `/api/self` returns admin details correctly, but any site-scoped endpoint fails.

**Prerequisites that were configured correctly and still failed:**
- Admin with `super_site_permissions: ["super"]`, `is_super: true`, `super_site_role: "super"`
- Privilege records with ObjectId `admin_id` and `site_id` for both "super" and "default" sites
- Role: `SUPER_ADMIN`, permissions: `["ALL"]`

**Attempted endpoints that all return NoSiteContext or Invalid:**
- `/api/s/default/cmd/backup` (with `cmd: "list-backups"` or `cmd: "restore"`)
- `/api/s/super/cmd/backup`
- `/api/s/default/cmd/system`
- `/api/system/backup`
- `/api/system/backup/list`
- `/api/system/backup/restore`

## What DOES Work

### Fresh deploy + re-adopt devices

When the controller is deployed fresh (no backup restore), devices that were previously adopted to another controller will try to inform but fail with "inform decryption failed." To re-adopt:

1. SSH into each UniFi device
2. Run: `set-inform http://<controller-ip>:8080/inform`
3. The device appears in the controller UI as "pending adoption"
4. Click "Adopt" in the UI

### Backup restore via the UI Setup Wizard

On FIRST boot of `jacobalberty/unifi`, the setup wizard at `https://<host>:8443` offers a "Restore from Backup" option. This is the only reliable path to restore a `.unf` file on v10.x.

### What to check before deploying to avoid this

1. **Always export a `.unf` before decommissioning** the old controller
2. **Store the `.unf` in S3** — don't rely on it surviving on the server
3. **Note the old controller version** — major version jumps (9.x → 10.x) add restore risk
4. **Document device SSH credentials** — needed for re-adoption if restore fails

## Dual-Storage Architecture Trap

The `jacobalberty/unifi` Docker image uses TWO storage locations that can diverge:

```
Host /opt/unifi  →  Container /config   (bind mount: system.properties, certs, old backups)
Docker volume    →  Container /unifi    (anonymous: MongoDB data, live backups, sites)
```

The container symlinks `/usr/lib/unifi/data` → `/unifi/data`. When running a one-off container for restore, you MUST use `--volumes-from` to access the anonymous volume. Simply bind-mounting `/opt/unifi:/config` only gives you the stale config files, not the live MongoDB.

To inspect the anonymous volume path:
```bash
docker inspect unifi-controller --format '{{json .Mounts}}' | python3 -m json.tool
```
