Files
hermes-skills/skills/devops/ubiquiti-controller-management/references/unifi-mongodb-admin-schema.md
T

3.9 KiB

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

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

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