I Got Locked Out of My Self-Hosted Termix Instance. Here's How I Got Back In.
TL;DR: Forgot your Termix username or password? Use this script -- it'll get you back in under a minute.
The Problem
I run a self-hosted Termix instance on Proxmox for managing SSH connections to my servers. After setting it up, I forgot to save the credentials to my password manager. Entirely my fault. By the time I needed to log in again, I had no idea what my username or password was.
No big deal, right? I have root access to the host machine. The database is right there at /opt/termix/data/db.sqlite.encrypted. I have the DATABASE_KEY sitting in data/.env. This should take five minutes.
It did not take five minutes.
What I Tried (and Why It All Failed)
Attempt 1: Plain SQLite
sqlite3 db.sqlite.encrypted "SELECT username FROM users;"
# → "file is not a database"
Fair enough -- it's encrypted.
Attempt 2: SQLCipher
sqlcipher db.sqlite.encrypted
PRAGMA key = '$DATABASE_KEY';
SELECT username FROM users;
ERROR CORE sqlcipher_page_cipher: hmac check failed for pgno=1
Parse error near line 2: file is not a database (26)
Hmm. Maybe the key needs hashing first? Tried PBKDF2, SHA-256, different KDF iterations. Nothing worked.
Attempt 3: Node.js scripts
Wrote inline scripts trying to use the sqlite3 npm package with encryption wrappers. Got MODULE_NOT_FOUND errors everywhere because Termix doesn't even ship the sqlite3 package -- it uses better-sqlite3, which is a completely different driver with no encryption support.
At this point I'd burned an evening and was starting to consider just nuking the database and starting fresh.
The Actual Answer
I finally dug into Termix's source code and found the real answer: Termix doesn't use SQLCipher at all.
The file db.sqlite.encrypted isn't an encrypted SQLite database. It's a normal SQLite database wrapped in a custom binary envelope:
┌──────────────────────────────────────────────────────┐
│ 4-byte big-endian metadata length │
├──────────────────────────────────────────────────────┤
│ JSON metadata { iv, tag, version, algorithm } │
├──────────────────────────────────────────────────────┤
│ AES-256-GCM ciphertext (the entire SQLite database) │
└──────────────────────────────────────────────────────┘
At startup, Termix reads this file, decrypts the whole thing into memory using Node's crypto.createDecipheriv, and operates on it as an in-memory SQLite database via better-sqlite3. The DATABASE_KEY is used directly as a raw 32-byte AES key -- just hex-decoded, no KDF applied.
That's why SQLCipher never worked. It was looking for page-level encryption with HMAC verification and KDF-derived keys. The file doesn't have any of that. It's just a blob.
The Fix
Once I understood the format, the recovery script was straightforward -- read the metadata header, decrypt with AES-256-GCM, load into better-sqlite3, query the users table:
cd /opt/termix
node recover.mjs list-users
Registered Users
─────────────────────────────────────
myusername [ADMIN]
ID: krmB6Yu_P3-Lz4AA4wIGJ
That's it. Username recovered. And if you also need to reset the password:
node recover.mjs reset-pass --user myusername --password 'NewSecurePassword!'
The script decrypts the database, updates the bcrypt hash, re-encrypts the file, and writes it back. It even creates an automatic backup before touching anything.
Get the Script
The full recovery tool is available as a GitHub Gist:
It supports five commands:
list-users-- see all registered accountsreset-pass-- reset any user's passworddump-settings-- view application settingsdecrypt-only-- export a plain SQLite file for manual inspectioninfo-- show encryption metadata
No extra dependencies needed -- it uses the better-sqlite3 and bcryptjs packages that ship with every Termix installation.
Lessons Learned
- "Encrypted SQLite" doesn't always mean SQLCipher. Applications can wrap SQLite files in custom encryption envelopes. Always check the source.
- Read the source before trying recovery tools. I could've saved hours by grepping for
DATABASE_KEYin the codebase first. - Use a password manager. Obviously.
Hope this saves someone else a frustrating evening.
Tags: termix, self-hosted, sqlite, encryption, database-recovery, aes-256-gcm, sysadmin