# UniFi MongoDB Admin Schema (v10.0.162) ## Database: `ace` ### `admin` Collection — Admin Accounts | Field | Type | Required | Description | |---|---|---|---| | `_id` | ObjectId | auto | MongoDB ID | | `name` | string | yes | Display name | | `email` | string | yes | Login email (username) | | `x_shadow` | string | yes | SHA-512 crypt password hash (`$6$salt$hash`) | | `time_created` | int32 (epoch) | yes | Account creation timestamp | | `last_site_name` | string | no | Last site accessed (set on login) | | `super_site_permissions` | array | **yes** | Must be `["super"]` for super admin | | `super_site_role` | string | **yes** | Must be `"super"` | | `is_super` | bool | **yes** | Must be `true` | | `last_login_timestamp` | int64 (epoch ms) | auto | Set on login | | `last_login_ip` | string | auto | Set on login | | `ui_settings` | object | auto | `{"preferredLanguage":"en"}` etc. | | `ui_version` | string | auto | UI version string | | `email_alert_enabled` | bool | no | Email notifications | | `is_owner` | bool | auto | Set to `true` on first admin | **Critical:** Without `super_site_permissions: ["super"]`, `super_site_role: "super"`, and `is_super: true`, the admin will log in but see an empty dashboard even if the database has devices and configs. ### `privilege` Collection — Site Access Control | Field | Type | Required | Description | |---|---|---|---| | `_id` | ObjectId | auto | MongoDB ID | | `admin_id` | ObjectId | **yes** | References `admin._id` — MUST be ObjectId, NOT string | | `site_id` | ObjectId | **yes** | References `site._id` — MUST be ObjectId, NOT string | | `role` | string | **yes** | `"SUPER_ADMIN"` or `"ADMIN"` | | `permissions` | array | yes | `["ALL"]` for full access | | `is_super` | bool | **yes** | `true` for super admin | **Critical pitfall:** Using `.str` on ObjectIds when inserting privileges (e.g., `admin._id.str` instead of `admin._id`) creates string references that don't match. The admin logs in but sees NO data — exactly the "no content on screen" symptom. ### `site` Collection | Field | Type | Description | |---|---|---| | `_id` | ObjectId | Site ID | | `name` | string | `"super"` or `"default"` | | `desc` | string | Description | | `attr_hidden_id` | string | Internal site key | | `attr_hidden` | bool | `true` for "super" site | | `attr_no_delete` | bool | `true` for both default sites | A fresh `jacobalberty/unifi` deploy creates two sites automatically: `"super"` (hidden) and `"default"`. ### Full Admin Creation Script ```javascript db = db.getSiblingDB("ace"); // 1. Generate hash externally: openssl passwd -6 'password' var hash = "$6$salt$hash"; var now = Math.floor(new Date().getTime() / 1000); // 2. Create admin var result = db.admin.insertOne({ name: "Admin Name", email: "admin@example.com", x_shadow: hash, time_created: now, last_site_name: "default", super_site_permissions: ["super"], super_site_role: "super", is_super: true }); // 3. Create privileges for ALL sites var admin = db.admin.findOne({email: "admin@example.com"}); var sites = db.site.find({}).toArray(); sites.forEach(function(site) { db.privilege.insertOne({ admin_id: admin._id, // ObjectId, NOT .str site_id: site._id, // ObjectId, NOT .str role: "SUPER_ADMIN", permissions: ["ALL"], is_super: true }); }); print("Admin created: " + admin._id); print("Privileges: " + db.privilege.count()); ``` ### Verifying Permissions After Login ```bash # Login curl -sk -c /tmp/uf-cookie -X POST https://localhost:8443/api/login \ -H "Content-Type: application/json" \ -d '{"username":"admin@example.com","password":"password"}' # Check self — should show is_super:true curl -sk -b /tmp/uf-cookie https://localhost:8443/api/self | python3 -m json.tool ``` If `is_super` is `false` or `super_site_permissions` is `[]`, the MongoDB update didn't take — likely the admin document was overwritten by the login process or the update used the wrong query.