# Open WebUI Admin Credential Recovery

## When to Use
- Admin password lost/forgotten
- Need to change admin email
- Docker container running Open WebUI with SQLite backend

## Procedure

### 1. Copy DB from container
```bash
docker cp open-webui:/app/backend/data/webui.db /tmp/webui.db
```

### 2. Inspect users and auth
```bash
python3 -c "
import sqlite3
conn = sqlite3.connect('/tmp/webui.db')
cur = conn.cursor()

# Show all users
cur.execute('SELECT id, name, email, role FROM user;')
print('Users:', cur.fetchall())

# Show auth table with password hashes
cur.execute('SELECT id, email, password, active FROM auth;')
rows = cur.fetchall()
for r in rows:
    print(f'  email={r[1]}, active={r[3]}, hash={r[2][:20]}...')
conn.close()
"
```

### 3. Reset password
```bash
python3 -c "
import sqlite3, bcrypt

password = b'<new-password>'
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt).decode()

conn = sqlite3.connect('/tmp/webui.db')
cur = conn.cursor()
cur.execute('UPDATE auth SET password = ? WHERE email = ?', (hashed, '<email>'))
conn.commit()
conn.close()
print('Password updated')
"
```

### 4. Update email (both tables)
```bash
python3 -c "
import sqlite3
conn = sqlite3.connect('/tmp/webui.db')
cur = conn.cursor()
cur.execute('UPDATE auth SET email = ? WHERE email = ?', ('<new-email>', '<old-email>'))
cur.execute('UPDATE user SET email = ? WHERE email = ?', ('<new-email>', '<old-email>'))
conn.commit()
conn.close()
print('Email updated')
"
```

### 5. Copy DB back and restart
```bash
docker cp /tmp/webui.db open-webui:/app/backend/data/webui.db
docker restart open-webui
rm -f /tmp/webui.db
```

## Syncing Model IDs After llama-swap Changes

When llama-swap's model list changes (models added/removed/renamed), Open WebUI caches stale `model_ids` in its `config` table JSON blob. This causes missing or phantom models in the UI.

### Check cached model IDs
```bash
python3 -c "
import sqlite3, json
conn = sqlite3.connect('/tmp/webui.db')
cur = conn.cursor()
cur.execute('SELECT id, data FROM config;')
for cid, blob in cur.fetchall():
    data = json.loads(blob)
    if 'openai' in data:
        for k, v in data['openai'].get('api_configs', {}).items():
            print(f'api_config.{k} model_ids:', v.get('model_ids'))
conn.close()
"
```

### Update to match current llama-swap models
```bash
python3 -c "
import sqlite3, json

conn = sqlite3.connect('/tmp/webui.db')
cur = conn.cursor()
cur.execute('SELECT id, data FROM config;')
for cid, blob in cur.fetchall():
    data = json.loads(blob)
    if 'openai' in data:
        oai = data['openai']
        for k in oai.get('api_configs', {}):
            cfg = oai['api_configs'][k]
            cfg['model_ids'] = [
                'deepseek-v2-lite',      # or whatever llama-swap serves
                'gemma-12b',
                'gemma-26b-200k',
                'nemotron-3-nano-30b',
                'nemotron-term-14b',
                'qwen36-35b-mtp'
            ]
        # Also fix base URL if it's wrong
        if 'api_base_urls' in oai:
            oai['api_base_urls'] = ['http://host.docker.internal:9292/v1']
        new_blob = json.dumps(data)
        cur.execute('UPDATE config SET data = ? WHERE id = ?', (new_blob, cid))
        conn.commit()
        print(f'Config id={cid} updated')
conn.close()
"
```

### Pitfalls
- `model_ids` is inside a JSON blob in the `config` table's data column — can't use SQL alone
- The `api_base_urls` may use `127.0.0.1` (wrong from inside container) instead of `host.docker.internal` — fix both
- Container restart required after DB update: `docker cp /tmp/webui.db open-webui:/app/backend/data/webui.db && docker restart open-webui`
- The `OPENAI_API_BASE_URL` env var in docker-compose.yml sets the default, but saved config in the DB overrides it

## Pitfalls
- `sqlite3` CLI may not be in the container — use `python3 -c` with built-in sqlite3 module instead
- Password is bcrypt-hashed ($2b$...) — must generate a proper hash, can't use plaintext
- The `auth` and `user` tables are separate — update BOTH or login will break
- Container health check uses `/health` endpoint — wait for "healthy" before testing login
- If `ENABLE_SIGNUP=false` in env, you must recover existing admin rather than creating a new one
- Open WebUI stores `WEBUI_SECRET_KEY` in env — if empty, it auto-generated one on first run; don't modify it
