Files
Termix/readme/DATABASE-LAYER-PHASE-0-AUDIT.md
T
ZacharyZcR 1e7c9ea53c draft: database layer refactor (#1054)
* feat(sshid) - sshid.io equivalent for termix (#919)

* feat(ssh-id): database schema, migrations and field encryption

Adds ssh_identities, ssh_identity_keys and ssh_identity_ca tables (public keys
stored plaintext for the unauthenticated resolver; CA private key registered
for per-user field encryption), with UNIQUE(user_id), an index on
ssh_identity_keys(identity_id), and idempotent CREATE TABLE migrations.

* feat(ssh-id): backend API — resolver, key management, CA and certificates

Mounts /sshid (nginx route added). Public text/plain authorized_keys resolver
(+ exact /:algo filter, HTML viewer) and CA public-key endpoint; no-store +
noindex headers on every resolver response including early 404s. Authenticated
management: claim/rename/delete handle, add/import/generate/enable/delete keys,
and a per-user CA (create/rotate/delete) with pure-Node OpenSSH certificate
issuance. Audit logging on all mutations; UNIQUE races map to a precise 409.
Unit tests for key parsing and certificate signing (ssh-keygen-validated).

* feat(ssh-id): frontend panel, API client and i18n

SSH ID panel wired into the app rail and AppShell: claim handle, resolver URL +
curl one-liner, key list, generate, paste/import, CA enable/rotate/remove with
server trust command, and per-key certificate issuance. API client re-exported
through main-axios.ts; all strings i18n'd.

* style(ssh-id): align panel and resolver page with Termix theme

- Rebuild the SSH ID sidebar panel with the theme's square components
  (SectionCard / SettingRow / FakeSwitch) instead of rounded ad-hoc cards;
  use accent-brand and destructive tokens rather than raw red/green.
- Fix panel scrolling: move overflow to a block scroll container so the
  cards keep their natural height instead of being clipped.
- Restyle the public resolver HTML page (/sshid/u/:handle) to the Termix
  dark theme: square corners, #18181b/#303032 palette, #f59145 accent,
  uppercase section labels.
- Tidy copy: 'Save To Credentials' label, drop the redundant generate intro,
  and correct the generate tooltip (the key is stored when saving to vault).

* feat: rename to Termix ID, improve UI, backend inconsistencies, and general bug fixes

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>

* ci(deps): bump actions/checkout from 6 to 7 in the github-actions group (#922)

Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump the dev-patch-updates group with 11 updates (#923)

Bumps the dev-patch-updates group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@codemirror/search](https://github.com/codemirror/search) | `6.7.0` | `6.7.1` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.0` | `6.43.1` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.0` | `4.3.1` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.1.8` | `4.1.9` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.8` | `4.1.9` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.2` | `0.5.3` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `17.0.7` | `17.0.8` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.8.4` |
| [sharp](https://github.com/lovell/sharp) | `0.35.1` | `0.35.2` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.1` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.8` | `4.1.9` |


Updates `@codemirror/search` from 6.7.0 to 6.7.1
- [Changelog](https://github.com/codemirror/search/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/search/commits)

Updates `@codemirror/view` from 6.43.0 to 6.43.1
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

Updates `@vitest/coverage-v8` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/coverage-v8)

Updates `@vitest/ui` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/ui)

Updates `eslint-plugin-react-refresh` from 0.5.2 to 0.5.3
- [Release notes](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/releases)
- [Changelog](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/compare/v0.5.2...v0.5.3)

Updates `lint-staged` from 17.0.7 to 17.0.8
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v17.0.7...v17.0.8)

Updates `prettier` from 3.8.3 to 3.8.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.8.3...3.8.4)

Updates `sharp` from 0.35.1 to 0.35.2
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.1...v0.35.2)

Updates `tailwindcss` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss)

Updates `vitest` from 4.1.8 to 4.1.9
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.9/packages/vitest)

---
updated-dependencies:
- dependency-name: "@codemirror/search"
  dependency-version: 6.7.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: eslint-plugin-react-refresh
  dependency-version: 0.5.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: lint-staged
  dependency-version: 17.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: prettier
  dependency-version: 3.8.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: sharp
  dependency-version: 0.35.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: vitest
  dependency-version: 4.1.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump nanoid in the prod-patch-updates group (#925)

Bumps the prod-patch-updates group with 1 update: [nanoid](https://github.com/ai/nanoid).


Updates `nanoid` from 5.1.11 to 5.1.15
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.11...5.1.15)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-version: 5.1.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump the major-updates group with 5 updates (#926)

Bumps the major-updates group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.2.0` | `5.0.0` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.4` | `10.0.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.2` | `26.0.0` |
| [concurrently](https://github.com/open-cli-tools/concurrently) | `9.2.1` | `10.0.3` |
| [eslint](https://github.com/eslint/eslint) | `9.39.4` | `10.5.0` |


Updates `js-yaml` from 4.2.0 to 5.0.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...5.0.0)

Updates `@eslint/js` from 9.39.4 to 10.0.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v10.0.1/packages/js)

Updates `@types/node` from 25.9.2 to 26.0.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `concurrently` from 9.2.1 to 10.0.3
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v9.2.1...v10.0.3)

Updates `eslint` from 9.39.4 to 10.5.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.4...v10.5.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@eslint/js"
  dependency-version: 10.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: "@types/node"
  dependency-version: 26.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: concurrently
  dependency-version: 10.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
- dependency-name: eslint
  dependency-version: 10.5.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(ssh): add HashiCorp Vault SSH signer authentication

* fix: small fixes to vault feature to align with Termix codebase

* chore: add view docs links for vault/termix id

* fix: file upload fails with 400 and missing schema migrations on upgrade (#929)

Two bugs introduced in v2.4.1:

1. uploadFileStream uses fileManagerApi.post() which triggers axios's
   transformRequest to JSON-serialize the FormData because the instance
   default Content-Type is application/json. Change to postForm() which
   sets Content-Type: multipart/form-data so the browser XHR sends the
   correct multipart body with boundary.

2. Two schema items added to schema.ts were not included in migrateSchema()
   in db/index.ts, causing 500 errors on existing installations upgrading
   from v2.4.0:
   - user_preferences.status_color_scheme (no such column)
   - dashboard_service_links table (no such table)

Fixes #928

Co-authored-by: sash <sash@fominykh.io>

* fix: support PuTTY PPK ssh keys (#930)

* fix: chunk large file manager uploads (#932)

* fix: route dashboard hosts by protocol (#934)

* fix: resolve tunnel endpoints reliably (#935)

* Fix Electron OIDC browser auth failures (#936)

* Allow RDP connections without stored credentials (#937)

* Sync role credential shares for OIDC users (#938)

* Fix terminal link dialog layering (#940)

* Confirm large files before opening editor (#942)

* Confirm closing active host connections (#943)

* Preserve file path case in file manager UI (#941)

* fix: preserve unicode guacamole tokens (#933)

* Persist VNC authentication settings (#944)

* Fix Guacamole websocket base path (#946)

* Promote file manager terminals to tabs (#939)

* Guard Guacamole disconnect during startup (#945)

* chore: increment ver

* feat: bitwarden ssh agent integration

* feat: serial connections support

* fix: various small bug fixes

* feat: open all sessions in a folder and terminal custom theme color support

* feat: cross host file manager clipboard and several small bug fixes

* feat: tailscale/wireguard support and added a new status state for when backend is checking status

* feat: grafana like server stats history, new alert system, ntfy/webhook support

* feat: new grid and widget based homepage function

* feat: new donate button in dashboard

* fix: alert ui incorrectly using termix css and fixed issue with alert system not loading

* chore: start database layer refactor

* docs: plan database layer refactor

* docs: audit database layer refactor phase zero

* chore: add database runtime adapter skeleton

* chore: add settings repository skeleton

* chore: add user session repository skeleton

* chore: add host credential repository skeleton

* chore: add field encryption boundary

* chore: migrate settings route slice

* chore: migrate user settings routes

* chore: migrate host metrics settings routes

* chore: migrate acme settings route

* chore: migrate terminal settings route

* chore: migrate tailscale settings read

* chore: migrate guacamole settings reads

* chore: migrate session timeout settings reads

* chore: migrate auth route settings reads

* chore: migrate host metrics settings reads

* chore: migrate startup settings reads

* chore: migrate user settings cleanup

* chore: migrate password reset settings

* chore: migrate oidc legacy settings read

* chore: migrate user route settings slice

* chore: migrate oidc state settings

* chore: migrate user login settings reads

* chore: migrate user crypto settings

* chore: consolidate startup settings defaults

* chore: consolidate database settings import export

* chore: migrate core session auth paths

* chore: migrate remaining session auth paths

* chore: migrate admin user routes

* chore: migrate user route admin checks

* chore: migrate user lifecycle routes

* chore: migrate auth user lookups

* chore: migrate oidc user routes

* chore: migrate api key repository paths

* docs: add database gray rollout guide

* chore: migrate trusted device paths

* chore: migrate user session route user lookups

* chore: add database repository rollout guard

* chore: expose repository rollout status

* chore: warn on repository rollout misconfiguration

* chore: migrate remaining user lookup helpers

* chore: migrate ssh user lookups

* chore: migrate user settings admin lookups

* chore: migrate acme ssl user lookups

* chore: migrate audit log admin checks

* chore: migrate oidc account user updates

* chore: migrate password reset user updates

* chore: migrate user deletion core records

* chore: migrate snippet audit user lookups

* chore: migrate ldap user sync paths

* chore: migrate totp user updates

* chore: migrate rbac user checks

* chore: migrate rbac role paths

* chore: migrate permission role lookups

* chore: migrate rbac access list reads

* chore: migrate shared rbac reads

* chore: migrate rbac access writes

* chore: migrate permission host access

* chore: migrate role host access lookup

* chore: migrate snippet access lookup

* chore: migrate shared credential access lookups

* chore: migrate host access cleanup writes

* chore: migrate host list access checks

* chore: migrate host access cleanup routes

* chore: migrate shared credential role lookups

* chore: migrate user role cleanup

* chore: migrate admin role sync

* chore: migrate ldap role sync

* chore: migrate user role assignment

* chore: migrate sso provider access

* chore: migrate audit log access

* chore: migrate user preference access

* chore: migrate open tab access

* chore: migrate dismissed alert access

* chore: migrate homepage layout access

* chore: migrate network topology access

* chore: migrate dashboard service link access

* chore: migrate command history access

* chore: migrate recent activity cleanup

* chore: migrate ssh credential usage access

* chore: migrate transfer recent access

* chore: migrate file manager bookmark access

* chore: migrate c2s tunnel preset access

* chore: migrate homepage item access

* chore: migrate session recording access

* chore: migrate tmux session tag access

* chore: migrate opkssh token access

* chore: migrate vault token access

* chore: migrate vault profile access

* chore: migrate host metrics preference access

* chore: migrate host health access

* chore: migrate host metrics history access

* chore: migrate alert persistence access

* chore: route alert host lookup through repository

* chore: migrate user data export reads

* chore: route host metrics stats sync through repository

* chore: migrate host folder persistence

* chore: migrate host resolution reads

* chore: route jump host resolution reads

* chore: route docker console jump host reads

* chore: route docker ssh resolution reads

* chore: route proxmox discovery resolution reads

* chore: route file manager activity host reads

* chore: route host metrics resolution reads

* chore: route ssh auth credential reads

* chore: route tunnel endpoint credential reads

* chore: route credential deployment resolution reads

* chore: route command history host flag reads

* chore: route snippet execution resolution reads

* chore: route terminal host resolution reads

* chore: route vault oidc host resolution reads

* chore: route wake on lan host reads

* chore: route internal host list reads

* chore: route host key verification persistence

* chore: route credential read paths

* chore: route credential host usage reads

* chore: route credential folder rename

* chore: route host owner access checks

* chore: route shared credential source reads

* chore: route user host credential cleanup

* chore: route credential delete reads

* chore: route credential update reads

* chore: route host credential reads

* chore: route host read paths

* chore: route host projection reads

* chore: route host list reads

* chore: route snippet read paths

* chore: route snippet folder writes

* chore: route snippet crud paths

* chore: route snippet bulk import

* chore: route rbac ownership reads

* chore: route user count reads

* chore: route cleanup snippets folders

* chore: route shared credential persistence

* chore: route dashboard activity

* chore: route guacamole host reads

* chore: route host bulk lookups

* chore: remove unlock-only simple db ops

* chore: route host autostart persistence

* chore: route ldap provisioning through users

* chore: route credential encrypted writes

* chore: route host encrypted writes

* chore: route bulk host encrypted writes

* chore: route termix id credentials

* chore: route termix id ca persistence

* chore: route termix identity persistence

* chore: route credential system migration

* chore: isolate user encryption migration storage

* chore: remove legacy simple db ops

* chore: isolate legacy sqlite migration copy

* chore: route database settings import export

* chore: route database host credential export

* chore: route database host credential import

* chore: route database file-manager import export

* chore: route database alert usage import export

* chore: route database user checks

* chore: isolate auth lazy migration storage

* chore: route explicit database saves

* chore: initialize database save boundary

* chore: route migration snapshot saves

* chore: isolate sqlite import constraints

* chore: route import sqlite boundary

* chore: route user encryption migration store

* chore: centralize current repository runtime

* chore: route more current repositories

* chore: route activity repository runtimes

* chore: route token repository runtimes

* chore: route health repository runtimes

* chore: route identity repository runtimes

* chore: route rbac repository runtime

* chore: centralize current sqlite runtime access

* chore: route user deletion key cleanup

* chore: route user deletion vault cleanup

* chore: route user deletion homepage cleanup

* chore: route user deletion health cleanup

* chore: route user deletion alert cleanup

* chore: route user deletion identity cleanup

* chore: add database layer preupgrade backup

* Fix database repository type errors

* fix: complete post-merge compile fixes for database refactor

Restore missing DatabaseSaveTrigger/getDb imports, session log format
fallback, OIDC provider resolution, guacamole recording insert, and
passwordFallbackOnly typing after merging current dev.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: DivByZero <mr.oplus@yahoo.fr>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: devdanetra <46488477+devdanetra@users.noreply.github.com>
Co-authored-by: Aleksandr Fominykh <neoformalex@users.noreply.github.com>
Co-authored-by: sash <sash@fominykh.io>
2026-07-15 23:28:10 -05:00

28 KiB

Database Layer Refactor Phase 0 Audit

Status: Draft
Branch: feature/database-layer-refactor
Purpose: Establish the current database access inventory, domain map, sensitive field map, and first implementation boundaries before changing runtime persistence.

1. Scope

Phase 0 does not change runtime behavior.

It produces the evidence needed for the next implementation phases:

  • where database access currently happens
  • which modules write directly to the database
  • which tables belong to which product domains
  • which fields are sensitive
  • which direct writes are risky under the current in-memory snapshot model
  • which domains should move first into repositories

2. Current Evidence

Commands used for the initial audit:

rg -n "getDb\\(|getSqlite\\(|DatabaseSaveTrigger|SimpleDBOps|db\\.\\$client|\\.prepare\\(" src/backend
rg -n "getDb\\(\\)\\.(insert|update|delete)|await db\\.(insert|update|delete)|db\\.(insert|update|delete)|\\.\\$client\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|\\.prepare\\(\\\"(INSERT|UPDATE|DELETE)|DatabaseSaveTrigger\\.triggerSave|DatabaseSaveTrigger\\.forceSave" src/backend
rg -n "export const .* = sqliteTable\\(" src/backend/database/db/schema.ts

High-level findings:

  • Database infrastructure is concentrated in src/backend/database/db/index.ts.
  • Business database access is spread across route modules, SSH modules, utilities, and auth helpers.
  • SimpleDBOps is not the only write path.
  • There are many direct Drizzle writes and raw SQLite writes.
  • Some direct writes manually trigger DatabaseSaveTrigger; many write paths do not.
  • Schema is SQLite-specific through sqliteTable and manual CREATE TABLE IF NOT EXISTS / addColumnIfNotExists.

3. Database Access Hotspots

The most database-heavy files by audit hits:

File Approx. hits Notes
src/backend/database/db/index.ts 75 database init, schema creation, ad-hoc migration, snapshot save
src/backend/database/routes/alert-rules-routes.ts 64 alert rules, channels, firings, raw SQL deletes
src/backend/database/routes/users.ts 54 users, settings, OIDC/GitHub settings, admin flows
src/backend/database/database.ts 27 import/export, legacy SQLite handling
src/backend/ssh/host-metrics.ts 26 metrics connection and settings reads
src/backend/database/routes/user-settings-routes.ts 16 user settings
src/backend/dashboard.ts 15 dashboard aggregation reads
src/backend/ssh/docker.ts 14 host/credential reads for Docker SSH
src/backend/ssh/managers/health.ts 13 host health checks
src/backend/ssh/alert-engine.ts 13 alert evaluation and firing state
src/backend/utils/user-crypto.ts 12 settings writes for crypto metadata
src/backend/ssh/host-metrics-settings-routes.ts 12 metrics settings
src/backend/ssh/tmux-monitor.ts 11 tmux session tags
src/backend/guacamole/routes.ts 11 Guacamole config/routes
src/backend/database/routes/host.ts 10 host operations and related rows

This confirms the refactor must be domain-by-domain. A mechanical replacement of getDb() would be noisy and unsafe.

4. Direct Write Risk Inventory

Under the current architecture, a direct write is risky when it bypasses SimpleDBOps and does not trigger DatabaseSaveTrigger.

4.1 Direct Writes That Need Repository Ownership

These areas perform direct writes and should move behind repositories/services:

Area Representative files Examples
users/settings/auth routes/users.ts, utils/auth-manager.ts, utils/user-crypto.ts sessions, trusted devices, OIDC settings, registration settings
RBAC/sharing routes/rbac.ts, utils/shared-credential-manager.ts roles, host access, snippet access, shared credentials
hosts/credentials routes/host.ts, routes/credentials.ts, host-resolver.ts host access cleanup, credential usage
file manager metadata host-file-manager-bookmark-routes.ts recent, pinned, shortcuts
alerts alert-rules-routes.ts, ssh/alert-engine.ts channels, rules, firings
metrics host-metrics-preferences-routes.ts, managers/health.ts preferences, health checks, history
terminal logs terminal-session-manager.ts session recording metadata
tmux tmux-monitor.ts tmux session tags
import/export database/database.ts SQLite import and forced save
open tabs routes/open-tabs.ts tab persistence and cleanup
API keys user-api-key-routes.ts, utils/auth-manager.ts API key create/delete/last-used
SSO and identity sso-provider-routes.ts, termix-id.ts providers, identity keys/CA

4.2 Immediate Compatibility Rule

Until a domain is migrated to repositories:

  • Every direct write must either be moved into a repository or explicitly trigger persistence in the old runtime.
  • New code should not add direct getDb() writes outside infrastructure or repositories.
  • The draft branch should add an enforcement check before Phase 8, not immediately, because the current codebase still violates the target rule widely.

5. Table Domain Map

5.1 Identity and Authentication

Tables Notes
users local/OIDC users, TOTP fields, password hash
sessions JWT sessions
trusted_devices remembered devices
api_keys API token hashes/prefixes
sso_providers configured SSO providers
termix_identities Termix identity records
termix_identity_keys public/private identity key metadata
termix_identity_ca CA material, private key is sensitive

Suggested repository:

  • userRepository
  • sessionRepository
  • trustedDeviceRepository
  • apiKeyRepository
  • ssoProviderRepository
  • termixIdentityRepository

5.2 Hosts and Credentials

Tables Notes
ssh_data primary host table, many config JSON/text fields
ssh_credentials reusable credentials
ssh_credential_usage usage history
ssh_folders folder metadata
host_access sharing/RBAC access rows
shared_credentials encrypted shared credential material
network_topology topology graph/config

Suggested repository:

  • hostRepository
  • credentialRepository
  • credentialUsageRepository
  • hostAccessRepository
  • sharedCredentialRepository
  • hostFolderRepository
  • networkTopologyRepository

5.3 RBAC

Tables Notes
roles role definitions
user_roles user to role assignments
host_access host-level grants
snippet_access snippet-level grants

Suggested repository:

  • roleRepository
  • accessRepository

5.4 File Manager

Tables Notes
file_manager_recent recently opened paths
file_manager_pinned pinned paths
file_manager_shortcuts saved shortcuts
transfer_recent host-to-host transfer destinations

Suggested repository:

  • fileManagerRepository
  • transferRecentRepository

5.5 Snippets

Tables Notes
snippets command snippets
snippet_folders snippet folder metadata
snippet_access snippet sharing

Suggested repository:

  • snippetRepository

5.6 Runtime Metadata and Audit

Tables Notes
audit_logs append-only audit trail
session_recordings terminal recording metadata, log content is file-based
recent_activity host activity feed
command_history terminal command history
user_open_tabs UI restore state; currently cleared on startup
user_preferences per-user UI/preferences
settings global settings

Suggested repository:

  • auditRepository
  • sessionRecordingRepository
  • activityRepository
  • commandHistoryRepository
  • openTabsRepository
  • userPreferencesRepository
  • settingsRepository

5.7 Metrics and Alerts

Tables Notes
host_metrics_preferences metrics layout/preferences
host_health_checks health check definitions
host_health_history health check results
host_metrics_history metrics history
alert_rules alert definitions
notification_channels webhook/ntfy/etc config
alert_rule_channels rule/channel joins
alert_firings firing/ack state
dismissed_alerts dismissed system alerts

Suggested repository:

  • metricsRepository
  • healthCheckRepository
  • alertRepository
  • notificationRepository

5.8 Integrations and Feature Config

Tables Notes
c2s_tunnel_presets tunnel preset config
opkssh_tokens OPKSSH cert/private key cache
vault_profiles Vault profile config, mostly non-secret
vault_tokens Vault cert/private key cache
dashboard_service_links dashboard links
homepage_items homepage widgets/items
homepage_layouts homepage layouts
tmux_session_tags tmux tag metadata

Suggested repository:

  • tunnelPresetRepository
  • opksshTokenRepository
  • vaultRepository
  • dashboardRepository
  • homepageRepository
  • tmuxRepository

6. Sensitive Field Map

The current explicit FieldCrypto map encrypts:

Table Fields
users passwordHash, clientSecret, totpSecret, totpBackupCodes, oidcIdentifier
ssh_data password, key, keyPassword, sudoPassword, autostartPassword, autostartKey, autostartKeyPassword, socks5Password, rdpPassword, vncPassword, telnetPassword
ssh_credentials password, privateKey, keyPassword, key, publicKey
opkssh_tokens sshCert, privateKey
termix_identity_ca privateKey
vault_tokens sshCert, privateKey

Additional sensitive fields by table semantics:

Table Fields / reason
ssh_credentials systemPassword, systemKey, systemKeyPassword are system-key encrypted credential copies
shared_credentials encryptedUsername, encryptedAuthType, encryptedPassword, encryptedKey, encryptedKeyPassword, encryptedKeyType are already encrypted payload fields
api_keys tokenHash is not plaintext but is authentication material; tokenPrefix may remain plaintext for display
settings some keys may hold provider secrets or reset codes; repository must classify by key
notification_channels config may include webhook URLs/tokens; treat config as sensitive unless split
alert_rules rule definitions are usually not secret but can include host/resource metadata
homepage_items widget config can include URLs/API config; classify per widget type
c2s_tunnel_presets config can include connection details; review before plaintext external DB storage
termix_identity_keys inspect key material fields before migration; public keys are not secret but private material must never be plaintext
vault_profiles current comments say profile fields are non-secret; keep that invariant explicit

Open privacy decision:

  • ssh_data.ip, domain fields, usernames, folders, and tags are queryable today.
  • Encrypting them improves confidentiality but breaks search/filter/sort unless blind indexes are added.
  • Recommended initial migration: keep them plaintext and document privacy implications; add optional privacy mode later.

7. Repository Migration Order

Use a vertical slice instead of broad replacement.

7.1 First Slice

  1. settingsRepository
  2. userRepository
  3. sessionRepository
  4. hostRepository
  5. credentialRepository

Reasons:

  • Covers the app's boot/login/core host management path.
  • Exercises field encryption.
  • Exercises per-user data unlock requirements.
  • Exercises transaction and migration behavior.
  • Builds reusable patterns for the rest of the backend.

7.2 Second Slice

  1. hostAccessRepository
  2. roleRepository
  3. sharedCredentialRepository
  4. auditRepository
  5. userPreferencesRepository

Reasons:

  • Completes permission and sharing boundaries.
  • Removes high-risk direct writes in admin/RBAC flows.
  • Moves audit to an append-only repository.

7.3 Third Slice

  1. snippetRepository
  2. fileManagerRepository
  3. metricsRepository
  4. alertRepository
  5. homepageRepository

Reasons:

  • These are broad feature domains with many routes.
  • They should reuse patterns from the core slices.

7.4 Fourth Slice

  1. vaultRepository
  2. opksshTokenRepository
  3. termixIdentityRepository
  4. tunnelPresetRepository
  5. tmuxRepository

Reasons:

  • Sensitive token/key cache domains need careful encryption tests.
  • Some data is transient and should have retention cleanup.

8. Adapter Design Notes

The adapter boundary should expose:

interface DatabaseAdapter {
  dialect: "sqlite" | "postgres" | "mysql";
  connect(): Promise<void>;
  close(): Promise<void>;
  migrate(): Promise<void>;
  transaction<T>(fn: (tx: DatabaseTransaction) => Promise<T>): Promise<T>;
}

The repository layer should not depend on better-sqlite3.

For Drizzle, likely options:

  • keep dialect-specific Drizzle clients internally
  • expose repositories instead of exposing the raw Drizzle client
  • keep schema definitions close to migrations, not route handlers

Important: do not make route modules import dialect-specific schema objects after migration.

9. Compatibility Shims

During the migration, avoid a flag day.

Add a compatibility database module that lets old code continue to run while new repositories are introduced:

legacy getDb() path
new adapter/repository path

Rules:

  • New modules use repositories only.
  • Migrated modules must not fall back to getDb().
  • Legacy path is removed only after all domains move.

10. Phase 1 Entry Criteria

Before implementing the adapter skeleton:

  • This audit document exists.
  • The sensitive field map is reviewed.
  • The first vertical slice is accepted.
  • The draft PR remains draft.
  • No runtime behavior has changed.

11. Phase 1 Deliverables

Recommended first implementation PR on this branch:

  • src/backend/database/runtime/config.ts
  • src/backend/database/runtime/adapter.ts
  • src/backend/database/runtime/sqlite-adapter.ts
  • src/backend/database/repositories/settings-repository.ts
  • src/backend/database/repositories/user-repository.ts
  • src/backend/database/repositories/session-repository.ts
  • src/backend/database/repositories/host-repository.ts
  • src/backend/database/repositories/credential-repository.ts
  • src/backend/database/repositories/field-encryption-boundary.ts
  • src/backend/database/repositories/current-settings-repository.ts
  • tests for config parsing and SQLite adapter boot

Started:

  • runtime config parser
  • SQLite adapter skeleton
  • migration metadata table bootstrap
  • SettingsRepository skeleton and tests
  • UserRepository and SessionRepository skeletons and tests
  • HostRepository and CredentialRepository skeletons and tests
  • FieldEncryptionBoundary skeleton and tests
  • first settings route slice wired through SettingsRepository
  • user settings route direct settings table access moved behind SettingsRepository
  • host metrics settings route direct settings table access moved behind SettingsRepository
  • ACME SSL settings route direct settings table access moved behind SettingsRepository
  • terminal route direct settings table access moved behind SettingsRepository
  • tailscale route direct settings table access moved behind SettingsRepository
  • Guacamole route and WebSocket server direct settings table access moved behind the current settings repository boundary
  • auth token expiry and terminal session timeout settings reads moved behind the current settings repository boundary
  • open tabs, TOTP, and LDAP auth route settings reads moved behind the current settings repository boundary
  • host metrics polling settings reads moved behind the current settings repository boundary
  • backend startup settings reads moved behind the current settings repository boundary
  • user deletion cleanup now removes per-user settings through SettingsRepository.deleteLike
  • password reset route reset code and temporary token settings access moved behind SettingsRepository
  • OIDC utility legacy config fallback reads oidc_config through SettingsRepository
  • user route registration/password flags and OIDC config administration moved behind the current settings repository boundary
  • OIDC authorize/callback temporary state and auto-provision reads moved behind the current settings repository boundary
  • user login settings reads moved behind the current settings repository boundary, completing direct settings access cleanup in routes/users.ts
  • user encryption metadata in utils/user-crypto.ts moved behind the current settings repository boundary
  • database startup and schema migration defaults in database/db/index.ts moved to local raw settings helpers
  • database import/export settings handling in database/database.ts moved to local helper boundaries
  • core auth-manager.ts session create/read/update/revoke/list paths started using the current session repository boundary
  • remaining auth-manager.ts session cleanup/middleware/logout paths and user-session-routes.ts single-session lookup moved behind the current session repository boundary
  • current user repository factory/write-save hook added, and user-admin-routes.ts list/admin promotion/admin removal/admin-create user paths moved behind the current user repository boundary
  • low-risk routes/users.ts current-user lookup and admin gate checks moved behind the current user repository boundary
  • user registration, self-delete, password change hash updates, and admin delete-user lookup paths in routes/users.ts moved behind the current user repository boundary, with first-user admin creation kept transactional inside UserRepository
  • traditional login username lookup and auth-manager.ts admin user checks moved behind the current user repository boundary
  • GitHub and standard OIDC callback user lookup/create/rollback/profile/admin sync writes moved behind the current user repository boundary, removing direct Drizzle users table access from routes/users.ts, user-admin-routes.ts, and auth-manager.ts
  • API key create/list/delete and API key authentication last-used updates moved behind the current API key repository boundary
  • trusted device check/add/remove and TOTP trusted-device cleanup moved behind the current trusted device repository boundary
  • user session routes moved user/admin lookups and admin session username enrichment behind the current user repository boundary
  • vault admin checks, Termix ID audit username lookup, permission manager admin checks, and user data export user lookup moved behind the current user repository boundary
  • SSH credential OIDC username expansion and tmux monitor audit actor username lookup moved behind the current user repository boundary
  • user settings route admin checks and audit actor username lookups moved behind the current user repository boundary
  • ACME SSL route admin checks and audit actor username lookups moved behind the current user repository boundary
  • audit log route admin checks moved behind the current user repository boundary
  • OIDC account link/unlink route user lookups and OIDC field updates moved behind the current user repository boundary
  • password reset route user lookups, password hash updates, and TOTP reset fields moved behind the current user repository boundary
  • user deletion helper now removes sessions through the current session repository and the final user record through the current user repository
  • snippet create/update/delete audit actor username lookups moved behind the current user repository boundary
  • LDAP login existing-user lookup, encryption rollback delete, admin sync, and display-name sync moved behind the current user repository boundary
  • TOTP setup/enable/disable/backup-code/login verification user updates and session revocation moved behind the current user/session repository boundaries
  • RBAC host sharing, role assignment, and snippet sharing target-user existence checks moved behind the current user repository boundary, with existing RBAC/snippet owner username joins retained
  • RBAC role list/create/update/delete, user-role assignment/removal/listing, and shared host/snippet role-id lookups moved behind the current role repository boundary, with existing RBAC/share credential joins retained
  • permission manager role permission aggregation, role-id lookups for shared host access, and admin role checks moved behind the current role repository boundary
  • RBAC host/snippet access-list read models moved behind the current RBAC access repository boundary, and snippet route shared-access role-id lookups moved behind the current role repository boundary
  • RBAC shared host/shared snippet read models and the main snippet shared-snippet read model moved behind the current RBAC access repository boundary
  • RBAC host/snippet access grant, revoke, and direct host-access credential override writes moved behind the current RBAC access repository boundary, with shared credential material creation retained in the existing manager
  • permission manager host-access expiration cleanup, shared host-access lookup, and last-access timestamp updates moved behind the current RBAC access repository boundary
  • repository rollout guard added through DATABASE_LAYER_REPOSITORY_ROLLOUT for the migrated settings/users/sessions/API-key/trusted-device/role/RBAC-access slice

Keep it small. Do not wire host or credential routes into the new repositories in the same first implementation commit.

12. Current Unknowns

  • Exact Drizzle multi-dialect strategy needs a spike.
  • The project may need a migration generator or a custom migration runner.
  • Some raw SQL in routes/users.ts, alert-rules-routes.ts, and db/index.ts must be rewritten or isolated.
  • settings contains mixed public and sensitive values; key-level classification is required.
  • homepage_items.config, notification_channels.config, and tunnel configs may contain embedded secrets.
  • Legacy encrypted snapshot fixtures need to be created before migration implementation.

13. Decision Log

  • Default upgrade target should be persistent SQLite, not PostgreSQL/MySQL.
  • PostgreSQL/MySQL migration should be explicit and initially manual/experimental.
  • Runtime SSH/WebSocket/tunnel state remains memory-only.
  • Field-level encryption is mandatory for every database backend.
  • Field-level encryption must use a stable record id; temporary encryption contexts are forbidden for newly written repository data.
  • Repository migration should start with settings/users/sessions/hosts/credentials.