Commit Graph
37 Commits
Author SHA1 Message Date
ZacharyZcR 64a80f411a make the repository layer engine-agnostic (#1127)
DatabaseContext handed every repository a raw better-sqlite3 handle alongside
drizzle, and three of them used it for retention queries built on datetime('now',
?) — a SQLite-only function. That handle is the one thing standing between the
repository layer and a second engine.

Drop it. The two time-based prunes compute their cutoff in JS against the
CURRENT_TIMESTAMP text format, which every engine writes the same way and which
compares correctly as a string; the health-history prune becomes a select of the
rows to keep followed by a NOT IN delete. All three turn async, so their two
callers await them.

Name the dialect rather than repeating a string literal, so adding an engine is
one edit instead of a search.

Tests built their schema through context.sqlite?.exec(). Optional chaining meant
removing the field type-checked cleanly and then silently created no tables, so
the fixture now owns exec() and a raw handle for direct assertions — schema setup
belongs to the test harness, not to the interface repositories consume.

No behaviour change, and no Postgres yet: this only removes the coupling that
would have to be undone first.
2026-07-28 16:59:10 +08:00
ZacharyZcR cdb030dff1 restore lint by pinning typescript below 7 (#1131)
#1090 bumped typescript to 7.0.2. typescript-eslint declares
`typescript: >=4.8.4 <6.1.0`, and TypeScript 7 removed `ts.Extension`, which
@typescript-eslint/typescript-estree dereferences at import time:

    node_modules/@typescript-eslint/typescript-estree/dist/create-program/shared.js:59
        ts.Extension.Cjs,
    TypeError: Cannot read properties of undefined (reading 'Cjs')

ESLint hits that while loading eslint.config.mjs, so `npm run lint` fails before
linting anything. Node reports it as ERR_INTERNAL_ASSERTION, which hides the
cause. Every open PR fails this check, not just new ones.

Even the latest typescript-eslint prerelease still caps at <6.1.0, so there is
nothing to upgrade to yet. Pin back to ~6.0.3 and tell dependabot to hold major
typescript bumps until the ecosystem catches up.

Also fixes biome.json pointing vcs.defaultBranch at dev-2.5.0, a branch that no
longer exists.
2026-07-28 16:57:00 +08:00
ZacharyZcR 291ccf2549 fix: use jump host SOCKS proxy settings (#1116) 2026-07-28 02:21:21 +08:00
ZacharyZcR 5c709d38d6 fix desktop preference synchronization (#1106) 2026-07-28 02:21:09 +08:00
ZacharyZcR a9c2aec165 improve settings navigation and legal disclosure (#1105) 2026-07-28 02:20:56 +08:00
ZacharyZcR 44a4534baa fix: show remote sync account identity (#1110)
* fix: show remote sync account identity

* cover getRemoteSyncUserInfo and make its null contract hold

Nothing asserted the renderer-side gate: browser builds must not reach for the
IPC bridge, and a missing bridge, an unconfigured server, an expired JWT or a
failed channel all have to degrade to no identity rather than throw.

Writing that turned up a mismatch — with no preload bridge the optional chain
resolved to undefined while the signature promises null. The only caller uses
??, so nothing is broken today, but the type was not telling the truth.

The main-process half (token expiry, /users/me, the roles fallback) stays
uncovered: remote-sync.cjs requires electron at load, so exercising it means
stubbing safeStorage and the filesystem, which is a bigger change than this PR
warrants.
2026-07-28 02:08:37 +08:00
ZacharyZcR 5f3e840892 fix: clarify desktop local profile (#1095)
* fix: clarify desktop local profile

* cover the AccordionSection hidden branch

The desktop build hides the Security section because the embedded profile signs
in automatically and has no login password, so the controls there would imply a
protection that does not exist. Nothing asserted that hidden actually keeps the
children out of the DOM rather than merely collapsing them.

Export the component and cover both states, including that an expanded hidden
section still renders nothing.
2026-07-28 01:57:52 +08:00
ZacharyZcR 8a1db9da37 fail the guacamole-lite patch when an anchor is gone (#1126)
Each patch bails out with a console.log and process.exit(0) when its anchor
string is missing. The write-back happens at the end of the file, so an upstream
release that moves any one anchor drops every patch, exits successfully, and
leaves postinstall reporting nothing wrong. Termix then builds and starts
normally and drops VNC/RDP sessions at runtime — with no signal pointing at the
patch.

Every patch here is required for correctness: protocol negotiation, the guacd
1.6.0 name handshake, dynamic argument answering, UTF-8 tokens, read-only joins.
A missing anchor means the patch no longer applies, so exit non-zero and say
which one and what to do.

Unchanged: a missing guacamole-lite still skips quietly, and an already-patched
tree still exits 0.
2026-07-28 01:49:21 +08:00
ZacharyZcR 8cd6288d19 stop session-log route test importing the real repository layer (#1125)
The test mocks db, logger and AuthManager, but the route module also calls
PermissionManager.getInstance() at import time and pulls in the repository
factory, which loads the drizzle schema and the better-sqlite3 native binding.
Importing that costs seconds when the full suite runs its projects
concurrently, and the test times out at 5s. On its own it passes, so it read as
flaky rather than as a missing mock.

Mock both. None of it is under test here, and the file now imports in
milliseconds regardless of load.
2026-07-28 01:49:17 +08:00
ZacharyZcR f2055caa5b stop highlighting inside a split control string (#1124)
A control string (OSC/DCS/APC/PM) carries text that must never be displayed —
an OSC 0 title holds the user, host and path, and PROMPT_COMMAND emits one on
every prompt. Its opener and its terminator routinely land in different
websocket frames, and the continuation frame contains no escape byte at all, so
every guard in the highlighter misses it: TUI_SEQUENCE, CONTROL_STRING_SEQUENCE
and hasIncompleteAnsiSequence all only look at one chunk.

Highlighting that continuation injects an SGR sequence into the middle of the
open string, which aborts it early in xterm.js and prints the remainder as
ordinary text — the stray ~/path glued to the prompt, and the cursor arithmetic
drift behind the duplicate prompts and Ctrl+R corruption.

Track the state across chunks the way alternate-screen mode already is, and skip
any chunk that starts or ends inside a control string. A trailing lone ESC counts
as inside, since its meaning only arrives with the next chunk.

Closes Termix-SSH/Support#1025
2026-07-28 01:49:13 +08:00
ZacharyZcR f0d2e36332 move the Homebrew cask to where a tap looks for it (#1123)
A tap discovers casks in a top-level Casks/ directory. The cask sat in
packaging/Casks/, so tapping the repository succeeded and every subsequent
brew install --cask termix reported that no cask with that name exists.

Move it and repoint the five workflow references. The release job still rewrites
the version and checksum in place, and the electron job still copies it into the
generated and submission trees.

Closes Termix-SSH/Support#1044
2026-07-28 01:49:09 +08:00
ZacharyZcR 60109be10f route desktop guacd calls to the connected remote server (#1122)
resolveConnectionOrigin() pins RDP/VNC/Telnet to "remote" because the embedded
desktop backend does not bundle guacd, and the Guacamole websocket already
follows that. The status check and both token calls did not: they use the shared
authApi, which in Electron is hard-coded to the embedded backend.

So the desktop app asked the backend without guacd whether guacd was available,
got "disconnected", and refused to connect — while the connected server it would
actually have used reports it as connected and serves the same host fine from the
web client.

Send those three calls through a remote-origin instance in Electron, alongside
the existing file-manager, tunnel and stats ones.

Closes Termix-SSH/Support#1043
2026-07-28 01:49:05 +08:00
ZacharyZcR 1d26f820c6 honour per-host recording flags and explain a missing recording (#1121)
The session recording section offers a recording path, a filename template and
four content toggles, but the backend overwrote five of the six on every
connection. A host could set none of them and get no indication why.

Location and filename genuinely are not the host's to choose — recordings are
indexed by them for playback and the backend refuses to read outside its
recordings directory — so drop those two inputs rather than keep pretending they
apply. The content flags are a host-level decision, so default them instead of
forcing them.

That still leaves the reported case, where guacd writes the file somewhere the
backend cannot see it. The warning now reports both paths and names the two env
vars that align them, which is otherwise guesswork for a split-container setup.

Closes Termix-SSH/Support#1041
2026-07-28 01:49:01 +08:00
ZacharyZcR 94a072b76d apply the configured RDP resolution to the session (#1120)
The host editor stores width and height in guacamoleConfig, and the backend
passes them to guacd in the connection token. The renderer then appends its own
width and height query parameters measured from the container, which take
precedence, so a configured resolution never reached the session — only dpi did,
because that was the one display field GuacamoleApp read back.

Pass the configured width and height alongside dpi, and skip the container-driven
sendSize on connect and on resize when a resolution is pinned. rescaleDisplay
still fits the fixed display into the available space.

Closes Termix-SSH/Support#1039
2026-07-28 01:48:57 +08:00
ZacharyZcR 6bdd38159c stop read-only shared hosts from being dragged into folders (#1119)
Shared hosts hide their edit, share and delete actions based on the recipient's
permission level, but the sidebar row stays draggable regardless. Dropping one on
a folder issues a bulk folder update the server rejects, so a recipient without
edit rights gets a failure toast for an action the UI offered them.

Gate draggable on canEditHost, and skip hosts the recipient cannot edit in the
move handler so a mixed selection moves what it can instead of failing whole.

Closes Termix-SSH/Support#1011
2026-07-28 01:48:52 +08:00
ZacharyZcR ac5da581e2 refuse to start with an empty database when data exists elsewhere (#1118)
When the data directory holds no database, startup treats it as a first run and
silently creates an empty one. A deployment that loses DATA_DIR — an .env file
the service no longer loads, a volume that did not mount — lands in exactly that
state, so the user is asked to register an admin account again while the real
database sits untouched one directory over. It is indistinguishable from the
upgrade having deleted everything.

Check the known data locations before creating a new database and refuse to
start when one of them already holds a database, naming both directories.
ALLOW_EMPTY_DATA_DIR=true starts anyway for anyone deliberately starting over.

This matches how a failed decryption already behaves: it throws rather than
falling back to an empty database.

Closes Termix-SSH/Support#1006
2026-07-28 01:48:48 +08:00
ZacharyZcR 384abebe37 fix OIDC login with unverifiable ID tokens (#1117)
verifyOIDCToken passed the raw id_token straight to jose's jwtVerify, which
throws JWSInvalid when the token is not a three-segment compact JWS. Authentik
issues an encrypted JWE id_token when the provider has an encryption key set,
so the callback threw and every OIDC login failed with 'Invalid Compact JWS'.

2.5.0 hid this behind a catch-all that decoded the unverified payload; removing
that fallback fixed the trust bug but turned the pre-existing verification
failure into a hard login failure.

Check the segment count before verifying and raise a distinct
OIDCTokenFormatError, which the callback treats as 'no usable claims here' and
falls through to the userinfo endpoint. Signature and claim failures still
reject the login.

Fixes Termix-SSH/Support#1016
Fixes Termix-SSH/Support#1018
2026-07-28 01:48:44 +08:00
ZacharyZcR 760c7b86c3 fix: expose jump tunnels to guacd (#1115) 2026-07-28 01:48:40 +08:00
ZacharyZcR 066d7e77b3 fix: forward Android hardware keyboard keys (#1114) 2026-07-28 01:48:35 +08:00
ZacharyZcR 8743e6daa2 fix: support Tailscale auth in tmux monitor (#1113) 2026-07-28 01:48:31 +08:00
ZacharyZcR 3f33961657 fix: keep localhost database export same-origin (#1112) 2026-07-28 01:48:27 +08:00
ZacharyZcR 50372cb25f fix: export repository user record (#1111) 2026-07-28 01:48:23 +08:00
ZacharyZcR 9dd81cf813 fix: recognize Windows terminal Tab events (#1109)
* fix: recognize Windows terminal Tab events

* style: format terminal key event test
2026-07-28 01:48:19 +08:00
ZacharyZcR 5258edf7a1 fix: persist host command history setting (#1107) 2026-07-28 01:48:15 +08:00
ZacharyZcR c48bc478f8 fix database persistence during container shutdown (#1104) 2026-07-28 01:48:11 +08:00
ZacharyZcR 697e363b74 fix file manager navigation after permission errors (#1103) 2026-07-28 01:48:07 +08:00
ZacharyZcR 6866176778 fix OIDC verification for JWKs without alg (#1102) 2026-07-28 01:48:03 +08:00
ZacharyZcR cf827f9a9c fix outbound DNS lookup callback shape (#1101) 2026-07-28 01:47:58 +08:00
ZacharyZcR 1139f17319 fix SSH login alert delivery (#1100) 2026-07-28 01:47:54 +08:00
ZacharyZcR 6d790b8d61 fix snippet execution result handling (#1099) 2026-07-28 01:47:50 +08:00
ZacharyZcR 9c61ae1ac4 fix: deduplicate shared hosts (#1098) 2026-07-28 01:47:46 +08:00
ZacharyZcR 5c30c5862f fix: preserve WoL broadcast address (#1097) 2026-07-28 01:47:42 +08:00
ZacharyZcR c12fc19c76 fix: allow sharing empty folders (#1096) 2026-07-28 01:47:38 +08:00
ZacharyZcR 7c397e3f8a fix: preserve architecture in unpacked ASAR path (#1094) 2026-07-28 01:47:33 +08:00
ZacharyZcR fdeb79e9e9 fix: centralize outbound address validation (#1093) 2026-07-28 01:47:29 +08:00
ZacharyZcR 113eb5619c fix: preserve remote sync references (#1092) 2026-07-28 01:47:25 +08:00
ZacharyZcR 38e128bd16 Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050)
This reverts commit 0712fdd731.
2026-07-14 01:36:12 +08:00