* 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>
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.
SimpleDBOpsis 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
sqliteTableand manualCREATE 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:
userRepositorysessionRepositorytrustedDeviceRepositoryapiKeyRepositoryssoProviderRepositorytermixIdentityRepository
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:
hostRepositorycredentialRepositorycredentialUsageRepositoryhostAccessRepositorysharedCredentialRepositoryhostFolderRepositorynetworkTopologyRepository
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:
roleRepositoryaccessRepository
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:
fileManagerRepositorytransferRecentRepository
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:
auditRepositorysessionRecordingRepositoryactivityRepositorycommandHistoryRepositoryopenTabsRepositoryuserPreferencesRepositorysettingsRepository
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:
metricsRepositoryhealthCheckRepositoryalertRepositorynotificationRepository
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:
tunnelPresetRepositoryopksshTokenRepositoryvaultRepositorydashboardRepositoryhomepageRepositorytmuxRepository
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
settingsRepositoryuserRepositorysessionRepositoryhostRepositorycredentialRepository
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
hostAccessRepositoryroleRepositorysharedCredentialRepositoryauditRepositoryuserPreferencesRepository
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
snippetRepositoryfileManagerRepositorymetricsRepositoryalertRepositoryhomepageRepository
Reasons:
- These are broad feature domains with many routes.
- They should reuse patterns from the core slices.
7.4 Fourth Slice
vaultRepositoryopksshTokenRepositorytermixIdentityRepositorytunnelPresetRepositorytmuxRepository
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.tssrc/backend/database/runtime/adapter.tssrc/backend/database/runtime/sqlite-adapter.tssrc/backend/database/repositories/settings-repository.tssrc/backend/database/repositories/user-repository.tssrc/backend/database/repositories/session-repository.tssrc/backend/database/repositories/host-repository.tssrc/backend/database/repositories/credential-repository.tssrc/backend/database/repositories/field-encryption-boundary.tssrc/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
SettingsRepositoryskeleton and testsUserRepositoryandSessionRepositoryskeletons and testsHostRepositoryandCredentialRepositoryskeletons and testsFieldEncryptionBoundaryskeleton and tests- first settings route slice wired through
SettingsRepository - user settings route direct
settingstable access moved behindSettingsRepository - host metrics settings route direct
settingstable access moved behindSettingsRepository - ACME SSL settings route direct
settingstable access moved behindSettingsRepository - terminal route direct
settingstable access moved behindSettingsRepository - tailscale route direct
settingstable access moved behindSettingsRepository - Guacamole route and WebSocket server direct
settingstable 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_configthroughSettingsRepository - 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
settingsaccess cleanup inroutes/users.ts - user encryption metadata in
utils/user-crypto.tsmoved behind the current settings repository boundary - database startup and schema migration defaults in
database/db/index.tsmoved to local raw settings helpers - database import/export settings handling in
database/database.tsmoved to local helper boundaries - core
auth-manager.tssession create/read/update/revoke/list paths started using the current session repository boundary - remaining
auth-manager.tssession cleanup/middleware/logout paths anduser-session-routes.tssingle-session lookup moved behind the current session repository boundary - current user repository factory/write-save hook added, and
user-admin-routes.tslist/admin promotion/admin removal/admin-create user paths moved behind the current user repository boundary - low-risk
routes/users.tscurrent-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.tsmoved behind the current user repository boundary, with first-user admin creation kept transactional insideUserRepository - traditional login username lookup and
auth-manager.tsadmin 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
userstable access fromroutes/users.ts,user-admin-routes.ts, andauth-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_ROLLOUTfor 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, anddb/index.tsmust be rewritten or isolated. settingscontains 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.