* fix: keep concurrent ssh pool connects from overwriting each other (#1619)
* fix: stop auto-accepting changed jump host keys (#1620)
A jump hop whose key no longer matched the pinned fingerprint was
silently accepted and the pin overwritten. Background status probes
reconnect to jump hosts every cycle, so a MITM on that hop got the
credentials with no user interaction. Changed jump host keys now go
through the same path as any other host: prompt when there is a
terminal socket, otherwise refuse.
* fix(electron): validate external login URL scheme and callback port (#1621)
* fix(plugins): read artifact manifest with the same tar parser as the unpack (#1622)
The consent check read the first manifest.json with a hand-written tar
reader, while the unpack keeps the last one and also follows pax and
long-name headers. An archive with two manifests could pass the check
with one capability list and get granted another. Read the manifest
with node-tar, count only plain files, and refuse archives that hold
more than one manifest.json.
* fix: only trust the server's origin in the desktop login frame (#1624)
The desktop sign-in iframe accepted AUTH_SUCCESS and system browser requests from whatever page it had navigated to, and posted replies (including 2FA temp tokens) to any origin. Messages must now come from the server's origin and replies are posted to it.
The web UI also treated any framing page as the desktop app and posted the JWT to it. It now also requires the Termix-Desktop user agent.
* fix: check trusted proxy login against nginx's peer, not nginx (#1625)
The backend only sees the bundled nginx on loopback, so the allowlist
could only work by trusting 127.0.0.1, which let any client that reached
nginx log in as anyone by sending the identity headers. nginx now passes
its own TCP peer (before real_ip rewriting) in X-Termix-Proxy-Peer, and
loopback connections are judged by that address instead.
* fix: keep foreign keys on during user data import (#1626)
Import credentials before hosts and remap the file's credential ids to
the ones created here, so hosts and protocol logins no longer point at
another server's ids. With rows written in dependency order, the import
no longer turns off SQLite foreign keys on the shared connection. The
uploaded file is now removed on every exit path.
* fix: enforce unique usernames (#1628)
* fix: enforce unique usernames
Registration checked for a taken name, hashed the password, then
inserted, with nothing in the schema enforcing uniqueness. Concurrent
sign-ups could create two accounts with one name, after which login,
password reset, proxy login and admin lookups by username picked
between them arbitrarily.
Add a unique index on users.username for every dialect. Existing
duplicates are renamed (later rows get a -<id> suffix, the oldest keeps
the name) rather than deleted. Registration and admin user creation now
return the same 409 when the insert loses the race.
* fix: avoid username collisions during migration
* fix: don't auto-enable synced plugins signed only by a server-sent key (#1623)
* fix: don't auto-enable synced plugins signed only by a server-sent key
Registry keys a linked server sends still let its plugins load on the desktop, but no longer count as a signer for switching them on or copying their grants. Keys the desktop added itself keep their trust.
* fix: enforce local plugin trust across remote source labels
* fix: require positive local trust for synced plugin keys
* fix(desktop): harden external editor and session cookie IPC (#1627)
* fix(desktop): stop the renderer choosing the external editor binary or reading the session cookie
open-external-editor now launches only the editor saved from a file dialog
and refuses to hand executable file types to the OS opener. The unused
get-session-cookie and wait-session-cookie channels, which returned the
httpOnly jwt, are removed.
* fix: validate setting keys before editor authorization
* fix(sidebar): sort hosts by IP address numerically (#1629)
* fix(desktop): find spawn-helper in universal macOS builds (#1630)
The universal build ships app-arm64.asar and app-x64.asar instead of
app.asar, so node-pty's helper path rewrite never pointed at the
unpacked copy and every local terminal failed with posix_spawnp failed.
* fix(sync): send shared hosts with text columns in their stored form (#1631)
resolveHostById turns forceKeyboardInteractive into a boolean, and the
shared-host export passed it on as is. A desktop could not bind the boolean
into the text column, so every shared host failed to apply.
* fix: drop ssh_data.vault_profile_id so removing the vault plugin keeps hosts writable (#1633)
The vault plugin renames vault_profiles to p_vault_profiles, and SQLite
rewrites ssh_data's foreign key to follow it. Uninstalling the plugin then
dropped p_vault_profiles and left ssh_data pointing at a missing table, so
creating, cloning and deleting hosts failed with "no such table:
main.p_vault_profiles". Postgres and MySQL refused the uninstall instead.
The column was already copied into the vault plugin's host settings by 2.9,
so retire it on every engine. The SQLite boot drop also repairs databases
already in the broken state; MySQL drops the column's foreign key first.
* fix(sync): let a linked desktop enable mirrored plugins it does not trust on its own (#1632)
* fix(sync): let a linked desktop enable mirrored plugins it does not trust on its own
* fix(sync): switch off an untrusted mirrored plugin when the server updates it
* fix: require local consent for mirrored plugin activation
* docs: describe consent fields on the plugin state endpoint
* fix(i18n): keep Indonesian technical terms consistent (#1636)
Use the English terms Indonesian users know for tunnel, snippet, deploy,
file manager, remote desktop, jump host, passphrase, sidebar, toolbar,
endpoint, browser and SSH agent, instead of mixing literal translations
(terowongan, cuplikan, terapkan, bilah sisi, titik akhir, ...) with the
English words. Match the split shortcuts to the split screen menu.
Also fix the user data export description, which said secrets are
encrypted while the English says they are decrypted.
* fix(settings): leave user manage view when switching admin sections (#1649)
The admin panel stays mounted across Settings pages, so an open user
manage view kept replacing every other admin section until Back to users
was clicked. Reset it whenever the selected section changes.
* fix(sync): don't delete a local row when the server can't send its copy (#1650)
* fix(sync): don't delete a local row when the server can't send its copy
A push conflict on a live record whose server row failed to serialize
came back as row: null, deleted: false, and the desktop treated that as
a delete. The server now rejects that op as unavailable, the desktop
never deletes on a conflict without a row, and serialization failures
are logged.
* fix(sync): retry temporarily unavailable records
* fix: smooth host editor jumps to lower sections
* chore: add changelog entries for 26.10.2 fixes
* fix: stop building ssh2 native crypto in the docker image (#1644)
* chore: test that an update keeps a changed auth type (#1601)
* feat: option to hide the sidebar after opening a host (#1652)
* feat: sort the dashboard host status card (Plugin-Host-Metrics#6)
* fix: let the hover side menu overlay content instead of resizing it (Plugin-Remote-Desktop#2)
* fix: say the server is down and keep retrying instead of a database error (#1637)
* fix: show why a client tunnel failed to start (Plugin-Tunnels#2)
* fix(sdk): skip comment-only chunks when splitting migrations
* feat: setting to let the hover side menu push content again
* fix: use the vite proxy for plugin sockets on any dev port
* feat(sdk): let a plugin raise its public route rate limit
* chore: update translations
* chore: add the overview video to the readme
* chore: update the overview video and add a play button to its thumbnail
* feat: plugin builds bundle worker entries and import wasm and fonts as bytes
* feat: plugin packs include LICENSE and NOTICE
* chore: drop guacd from compose files and dev runner
* chore: update remote desktop origin comment for the built-in engine
* fix: show plugin names and descriptions in the UI language (#1658)
* fix: show plugin names and descriptions in the UI language
Read plugin.name and plugin.description from each plugin's own locale
namespace, falling back to the manifest, everywhere core shows a plugin's
name: the Plugins manager, onboarding picker, host editor tabs and
sections, host filters and bulk actions, beta updates, interface presets
and plugin crash/loading notices.
* docs(sdk): document plugin.name and plugin.description locale keys
---------
Co-authored-by: ZacharyZcR <zkl205305@gmail.com>
* fix(hosts): reject or infer a missing authType on host create (#1664)
Creating an SSH host through the API without authType hit the NOT NULL
auth_type column and came back as a 500. The create route now infers the
type from the credential, key or password in the body, falls back to the
host defaults, and otherwise answers 400. A non-string authType is a 400
on create and update.
* chore: changelog for merged plugin name and host auth type PRs
* fix: move macOS split pane navigation to Cmd+Option+Arrow
* fix: pick a free port for the desktop backend when 30001 is taken (#1663)
* feat: build the Windows desktop app for ARM64 (#1615)
* fix(sync): keep the changes cursor within the 32-bit seq column (#1665)
The desktop learned the server's entity types by asking for changes after
Number.MAX_SAFE_INTEGER. sync_records.seq is a 32-bit integer on Postgres,
which rejects that bound value, so every pass needing the probe failed with
22003 out of range.
The server now clamps the cursor to the largest seq the column holds and
answers 400 for a cursor or limit that is not a non-negative integer. The
desktop asks for a one-row page from its saved cursor instead.
* fix(audit): coalesce identical plugin routine audits so they cannot flush the log (#1667)
Successful plugin_as_user and plugin_service_* entries written by a plugin
with the same user, action, resource and details are recorded once per
10 minutes. Failures and every other action are still written each time.
* feat(plugins): publish host.created when a host is added (#1668)
Plugins that keep an inventory of hosts had no way to hear about a new
one short of polling. Core now publishes host.created with the same
{ hostId, userId } payload as host.updated wherever a host row is
created: the host create route, ctx.hosts.create, JSON and SSH config
bulk import, database file import, and sync writing a host it did not
have yet.
* fix: show the login for signed-out full-screen view links and return to them (#1669)
A ?view= link opened without a session spun forever: the gate rendered
the bare view when the session check failed, so the plugin runtime never
started and the view never registered. A regular browser now keeps the
link in sessionStorage, shows the normal login and opens the link again
after any login, including redirect logins. Only same-origin paths with
a view are restored. The desktop app, embedded sign-in frames and guest
views keep their current behavior.
* fix(desktop): apply linked server proxy headers and certificate exception to WebSockets (#1670)
* fix(desktop): send linked server proxy headers on WebSocket upgrades
isLinkedOrigin compared the request origin with the link's http(s)
origin, so the terminal WebSocket (wss://host/...) never matched and its
upgrade went out without the proxy headers and basic auth. Behind a
forward-auth proxy that upgrade was redirected to the login and the
terminal closed with "connection closed unexpectedly". ws and wss now
count as http and https for the comparison, the same mapping the
WebSocket handshake itself uses, with host and port still matched
exactly.
* fix(desktop): allow a linked server's self-signed certificate for its WebSockets
isInvalidCertificateAllowedForUrl compared the URL with a local
getOrigin copy, so wss://host never matched the link's https://host
origin and an allowed self-signed certificate did not cover the
terminal and tunnel sockets. It now uses linkedServer.isLinkedOrigin,
which maps ws and wss the same way as the proxy headers, and the local
copy is removed.
* feat(sdk): let plugins mint a short-lived user session and read a user's locale (#1666)
ctx.auth.issueUserToken(userId, { purpose, ttlSeconds }) gives background
work a session for calling Termix's own API as an existing user. It needs
users:impersonate, defaults to 300s and is capped at 900s, is stored as a
normal session row marked as plugin-issued, and every issue is audited.
Such sessions are refused on routes that change the user's password,
second factors, API keys or linked logins.
ctx.users.getLocale(userId) reads the user's UI language under users:read.
SDK 1.0.11, plugin API 1.3.0.
* chore: changelog for merged sync, audit, event and desktop PRs
* feat: add shell.rightEdge slot so plugin panels can push the page over
* chore: pin bundled plugins to the latest releases
* chore: lint, format, and bump version to 26.10.2
---------
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: Ferdina Kusumah <19812460+FerdinaKusumah@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zkl205305@gmail.com>
Co-authored-by: Lukas <80832688+0skater0@users.noreply.github.com>
* fix(sw): stamp __TERMIX_SW_BASE_PATH__ and version into sw.js at build time
vite copies public/ verbatim, so the placeholder in public/sw.js reached
dist/sw.js untouched. The service worker then requested
__TERMIX_SW_BASE_PATH__/favicon.ico and friends, its install promise
rejected, and it never activated - while a worker from an older release
kept serving its cached frontend against a newer backend.
scripts/stamp-sw-build-info.cjs now runs right after vite build and
replaces both placeholders in dist/sw.js:
- __TERMIX_SW_BASE_PATH__ -> the base path the build used (VITE_BASE_PATH,
or root-relative by default, matching vite.config.ts)
- __TERMIX_CACHE_VERSION__ -> package.json version, so CACHE_NAME changes
per release and the activate handler evicts the previous build's cache
Closes#1617
* chore: format stamp-sw-build-info.cjs to prettier style
* fix: preserve runtime service worker base paths
---------
Co-authored-by: inontz <inontz@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: skip existing hosts on JSON and SSH config import
Import (skip existing) only loaded existing hosts when overwriting, so
every host was created again. Always load them, and skip a match unless
overwrite is set. A skipped host still resolves as a jump host for the
hosts imported after it.
Fixes#1606
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: use .js extension for test import
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: AI translations at release, 7 new languages and regional fallback
* fix: proper plural forms for count strings and full translations
* fix: plural forms for selected, hidden and active counts
* fix: synced plugin updates failing on desktop and server-only plugins shown as stopped
* fix: restore rounded corners on app icons and favicon
* feat: desktop app auto updates with automatic, ask first or never modes
* feat: community and custom plugin registries
* feat: plugin guide for AI tools in the SDK with a lint check
* fix: keep the terminal WebSocket open past nginx's 60s default (#1588)
* chore: update changelog
* feat: show plugin download counts only and stop CI builds inflating them
* feat: hide installed plugins in the browse tab by default
* chore: pin bundled plugins to latest releases
* chore: update translations
* fix: focusing a saved password field and saving no longer erases it
* fix: tell desktop users when their 2.8 data needs 2.9 first
* fix: point windows update info at the x64 installer
* fix: keep desktop client tunnel sockets alive behind proxies
* chore: pin bundled plugins to latest releases
* fix: cover client tunnel keepalive in the c2s runtime tests
* fix: typing after opening a host no longer goes to the host search
* fix: accept tab and = separators in SSH config import (#1598)
* chore: update changelog
* chore: drop plugin versions from core changelog
* chore: update translations
* chore: lint, format, and bump version to 26.10.1
---------
Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com>
Co-authored-by: Iain <Iain1970@protonmail.com>
* feat: optional auto reconnect for dropped ssh sessions (#1321)
* feat: tighter compact host rows with inline tags (#1325)
* feat: bring back the alert feed homepage widget
* feat: pick target terminals for snippets again
* feat: share button on connection tabs again
* feat: recent servers list when linking the desktop app
* feat: list the fixed app shortcuts in keyboard shortcuts
* fix: translate hardcoded toasts and drop em dashes from ui text
* fix: translate leftover hardcoded ui text
* fix: toolbar files button opens at the shell directory again
* fix: show the remote desktop note under connection origin again
* chore: openapi docs for host metrics manager routes
* chore: openapi docs for undocumented routes and fix two broken doc blocks
* fix: declare terminal.listSessions use for snippet targets
* revert: drop the alert feed widget, the notification center replaces it
* feat: restore the alert feed homepage widget
* fix: quick connect fills the host address from plugin auth editors like tailscale
* fix: docker container logs use the base background color
* chore: fix dependabot alerts and apply safe npm upgrades
* fix: proxmox stats tab reads its switch from plugin host settings
* fix: count plugin ssh logins toward host status and stop showing reachable as a warning
* fix: hide the built-in source pill in the host editor
* fix: host status shows online when the port answers and only warns on a failed login
* fix: drop the top border on the split layout menu
* fix: host defaults tabs sit where the host editor puts them and skip plugins with nothing to default
* fix: remote desktop auth method labels and notes for vnc and telnet
* fix: add description to the web endpoint enable switch
* fix: crowdin sync and pretranslate for plugin locales, run plugin tests on release
* feat: move telemetry out of core into a usage statistics plugin
* fix: recent activity card no longer cuts off host names early
* fix: remove duplicate top border on split screen menu
* fix: redesign session log player controls, fix tooltip overlap and drop dot separators
* fix: file manager and sftp text showing raw i18n keys
* fix: onboarding rerunning for existing users after upgrading
* fix: fleet detail buttons overflowing the sidebar
* fix: session log player scrolling off screen horizontally
* fix: network graph lag when adding hosts and on every render
* fix: maintenance index name, shared host read only view and badge polling
* fix: meeting tabs, workspace tab merging, rail panels as tabs and general ui lag
* chore: update release notes
* fix: remote desktop tabs reconnecting on every tab switch
* fix: build plugin sdk before linting in release workflow
* chore: lint, format, and bump version to 2.9.0
* chore: skip crowdin pre-translate in release for now
* chore: switch to calver, bump version to 26.10.0
* fix(sso): send the PKCE verifier for GitHub login (#1534)
* fix(remote-desktop): clip cursor overflow without disabling zoom (#1535)
* test(remote-desktop): preserve sessions beyond one hour (#1536)
* fix(hosts): expose controls for overflowing editor tabs (#1537)
* fix(hosts): expose controls for overflowing editor tabs
* test(hosts): mock resize observation in admin panel tests
* fix(status): skip TCP probes while host sessions are active (#1538)
* fix(database): batch session activity persistence (#1539)
* docs(api): describe cookie and API key authentication (#1540)
* fix(snippets): repair missing note column on SQLite upgrades (#1541)
* fix(docker): retain stored SSH credential association (#1543)
* fix(file-manager): render transfer toasts without plugin hooks (#1546)
* feat(hosts): keep compact row actions inline (#1547)
* fix(auth): allow retrying unavailable second-factor interfaces (#1549)
* fix(auth): reject unresolved legacy identity provisioning (#1550)
* fix(desktop): ship the plugin SDK inside Mac App Store builds (#1551)
* fix(tmux): pass full session info to the terminal's session picker (#1552)
The tmux.sessions service reduced detected sessions to bare names, but the
picker reads session.name, so every entry rendered blank and selecting one
sent an empty name, which created a new session instead of attaching.
* fix(ssh): read force keyboard-interactive as a boolean when resolving hosts (#1553)
The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".
* fix(database): batch database saves for bulk host writes (#1554)
* fix(database): yield to the event loop between database saves
Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.
* fix(database): save once per bulk host write instead of once per row
On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.
DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.
* feat(hosts): add instance-wide predefined tag suggestions (#1548)
* feat(hosts): add shared predefined tag suggestions
* style(hosts): format tag catalog routes
* test(database): advance fake timers past the save yield (#1555)
* fix(i18n): complete Simplified Chinese core and plugin translations (#1542)
* fix(i18n): complete Chinese onboarding and navigation labels
* fix(i18n): translate remaining Chinese core and plugin interfaces
* fix(i18n): translate host editor tab overflow controls
* fix(i18n): use consistent Chinese fleet terminology
* fix(i18n): localize hardcoded controls and plugin views
* fix(i18n): translate built-in homepage widget catalog
* test(homepage): follow translated timezone placeholder
* fix(i18n): translate plugin-provided homepage widgets
* fix(i18n): localize feature settings section titles
* fix: 2.8 oidc accounts unable to sign in after upgrading (#1381)
* fix: plugins losing the saved ssh login when copying a host (#1391)
* fix: fleets, proxmox and automations not getting the host sudo password
* fix: host imports running a defaults pass and metrics restart per host (#1384)
* fix: high cpu from status probes and full database saves on every sample (#1300)
* fix: user data export freezing the server (#1393)
* fix: op:// secret references rejected as ssh keys on credentials (#1394)
* fix: slow file deletes from the trash lookups on every delete (#1390)
* fix: add openapi docs and error handling to host tag routes
* test: compare download stream buffers directly so it stops timing out
* chore: drop em dash from host row comment
* chore: update release notes for 2.9.1
* feat(ai): add remote coding agent sessions for SSH hosts (#1545)
* feat(ai): add host coding agent sessions with configured providers
* Wait for agent initialization and shutdown before resuming sessions
* Keep Pi transcripts outside its configuration migration directory
* Cover Pi session directory separation
* Update core permission catalog expectations for remote agents
* Install remote agent runtimes from verified official sources
* Wait for the identity panel credentials request in its test
* Add scoped SSH forwarding setup for remote agents
* Verify forwarding setup through a fresh SSH connection
* Bound the SSH forwarding verification timeout
* Return the forwarding verification callback result
* fix(ai): support agent send and interrupt keyboard shortcuts
* feat(ai): add agent queues attachments worktrees and session recovery
* fix(ai): handle deleted workspace paths and duplicate status events
* fix(ai): accept file references in root workspaces
* fix(ai): refresh paused queues after agent errors
* fix(ai): store agent sessions in plugin tables and lock down OpenCode
Agent sessions lived in a single ctx.kv value holding up to 2000 events,
which passed the 256 KB kv limit within minutes of a normal run. Every
save after that failed, so history, nativeId, queue and archive changes
were lost and every event wrote a kv audit row.
Sessions, events and queued prompts now have their own tables. A running
agent appends events in batches at most once a second with a lazy save;
session and queue changes save immediately. Prompts, drafts and stored
event text are capped in UTF-8 bytes to fit MySQL TEXT. kv:own is no
longer requested.
The OpenCode server now requires a per-session password, so other local
accounts on the host cannot drive the agent through its loopback port.
* test(upgrade): include agent session tables in the reviewed upgrade SQL
* test: run the posix-only ai agent tests on linux only
* fix: restore space to add host tags and redesign predefined tags editor
* fix: host key silently accepted when the host is missing from the database (#1397)
* fix: clearer host login failed status label and fix its translations (#1396)
* chore: add host key and status label fixes to release notes
* fix: sso and ldap provider dialogs overflowing the screen and using mismatched toggles
* chore: increment ver
* fix(audit): store plugin entries with no acting user as a null user_id (#1556)
Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.
* chore: add sso/ldap dialog and audit log fixes to release notes
* fix: tunnels and host settings missing from shared hosts on desktop
* fix: remote desktop logins missing from shared hosts on desktop
* fix: simplify host status to online/offline and keep it live without a refresh
* chore: sync Crowdin translations for 2.9.1
* fix: build docker frontend/backend natively to stop arm64 hang, build sdk before openapi
* chore: rework plugin readmes, json changelogs and standalone repo setup
* chore: move plugins to their own repos and drop the plugins folder
* chore(deps-dev): bump globals (#1560)
Bumps the dev-minor-updates group with 1 update: [globals](https://github.com/sindresorhus/globals).
Updates `globals` from 17.12.0 to 17.13.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.12.0...v17.13.0)
---
updated-dependencies:
- dependency-name: globals
dependency-version: 17.13.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: drop the 2.8 columns, moves and aliases kept for a downgrade
* chore: remove the 2.9 finish list
* feat: port the demo redesign with Manage and Settings tabs
* chore: pin the plugin signing key and use TERMIX_PAT in workflows
* chore: rename the plugin sdk to @termix-ssh/plugin-sdk
* feat: add a publish workflow for the plugin sdk
* chore: cleanup
* fix: harden plugin sockets, public routes, prebuilt binaries and login fallback
* chore: add a 2.9.1 upgrade check for postgres and mysql
* fix: stop full database rewrites on every plugin db call
* fix: stop full database rewrites on every plugin db call
* fix: restore release video link in 2.9.2 notes
* fix: use TERMIX_PAT in workflows
* feat: pin the official plugins in the docker image
* feat: implement plugin management UI
* feat: shared list kit, settings search, rail order and ui consistency fixes
* chore: run all workflows on blacksmith runners
* chore: split dashboard quick actions into quarters and drop host status row lines
* fix: open plain settings from the profile button, navigation only from the rail menu
* feat: consent check, developer mode file installs, install counts and issue links for plugins
* feat: row detail options for hosts and credentials, open manage from sidebar header
* fix: unify connection failure screens and rebuild stale local plugins
* feat: dev:all runner for web, electron and docker with plugin hot reload, termix-plugin dev
* chore: clean up npm scripts, make npm run dev the full runner and drop concurrently
* fix: settle quick connect panel loads in tests so they stop flaking
* fix: stop the dev runner rebuilding plugins when windows reports file reads as changes
* chore: update the plugin sdk ui export snapshot
* feat: full screen onboarding with a plugin picker and per step versions
* feat: changelog.md release notes, v tags, plugin release notes and videos
* chore: add temp changelog
* chore: release the plugin sdk as 1.0.1
* feat: show only the latest plugin release notes and cap long ones
* chore: publish docker images to termix-ssh on ghcr and termixssh on docker hub
* chore: rename docker hub token secret to DOCKERHUB_BUGATTIGUY527_TOKEN
* chore: renew mac app store provisioning profile
* chore: drop docs job from release workflow
* chore: run sdk publish on github hosted runner
* chore: remove dependabot
* chore: general file cleanup
* chore: rewrite readme and taglines for plugin-based termix
* chore: drop optional from ai plugin row in readme
* feat: per-repo issue forms and prefilled bug reports instead of the support repo
* feat: add report a bug link to the your account settings section
* fix: open settings goes to the plugin's page and hides when it has none
* feat: show plugin features from the manifest on the plugin page
* fix: retry failed or blocked plugins on restart instead of leaving them disabled
* fix: refresh plugin settings after enabling a plugin and match select and disabled bar styling
* feat: request a feature link for plugins and use the bug form for unverified plugins
* feat: beta channels for core and plugins with one stable/beta release workflow
* fix(sync): resolve shared hosts for connect-only recipients (#1568)
* fix(ssh): accept full passwords in keyboard-interactive prompts (#1569)
* fix(ssh): normalize PKCS8 private keys before connecting (#1570)
* fix(sidebar): reclaim space from hidden folder actions (#1571)
* fix(hosts): display protocol credential usernames for non-SSH hosts (#1572)
* fix: ask for a push/TOTP menu choice as text instead of a push confirm
* fix: keep client tunnels open when one of their connections fails (Support#1417)
* fix: send plugin form uploads to the plugin's route (Support#1406, Support#1420)
* fix: add missing sync ids to old snippet tables so snippets sync again (Support#1419)
* fix: start the macOS local terminal when the shell or spawn helper is broken (Support#1418)
* fix: refresh cached plugin translations when only a translation changes (Support#1411)
* fix: import sshpk as a default so the backend starts under ESM
* feat: add a plugin sdk changelog and tag sdk releases, release 1.0.2
* fix: drop the ./ from the plugin sdk bin path so npm stops warning
* chore: name core and sdk releases Termix vX and Plugin SDK vX
* fix: lock the desktop backend to the app's own origin (GHSA-frx8-qqgg-7j7g)
* fix: trust devices by a server-issued token, not a client header (GHSA-6gr3-r7jx-wfmh)
* fix: keep global settings out of non-admin data exports (GHSA-v3vp-ffww-q7pw)
* fix: refuse sessionless tokens and keep the secrets file owner only
* feat: add TRUSTED_PROXIES to choose who nginx takes client IPs from (GHSA-7j7m-85p4-2cpm)
* chore: standardize repo setup files
* chore: remove unused knip config
* chore: add LICENSE and use Node 24 from .nvmrc
* chore: update translation guidance, only en.json needs editing
* feat: plugin docs and env manifest fields, in-app docs links, SDK 1.0.3
* fix: header separators render as borders and center the status divider
* fix: lighter confirm prompt overlay to stop lag when it opens
* fix: put plugin-only tailwind classes in core's utilities layer, SDK 1.0.4
* fix: dev stages local plugin builds on paths with spaces and builds them with the local SDK
* fix: keep plugin tailwind variants in core's layer so overrides keep their order
* fix: manage tab refetches before giving up on a host or credential it cannot find
* fix: tab buttons like share show as soon as the terminal is ready
* fix: folder rows no longer nest action buttons inside a button
* fix: repin remote-desktop after its 1.0.0 re-release
* chore: repin bundled plugins after the 1.0.0 re-release
* fix: recent activity rows use the same separator as host status
* feat: upload banner
* fix: reject expired sessions on sockets, api key desktop links and 192.0.0.0/24
* fix: plugin sdk 1.0.5 with optional ui peers, safer timeouts and validate checks
* fix: bundled plugins path for source runs and backend cleanup
* fix: load plugin strings on guest pages, plugin wording and drop unused keys
* fix: beta version stamping, homebrew cask bump, dockerignore and readme platforms
* docs: 26.10.0 changelog
* fix: current version in the version route example
* chore: stop pushing the release- docker tag
* feat: remove the donation reminder and its column
* docs: note the removed donation reminder
* fix: link server wizard always opens inline, onboarding card grows for it
* feat: plugin text columns are longtext on mysql so they are not capped at 64KB
* fix: linked desktops keep a plugin when its update from the server fails
* chore: default mobile download links to v tags
* fix: sso only accounts cannot sign in with a passkey or other local method
* feat: note in the plugins tab that community plugins are coming
* fix: plugin validate accepts beta versions
* chore: pin bundled plugins rebuilt on sdk 1.0.5
* fix: skip locale fetches for disabled plugins
* fix: show translated names for the built-in admin and user roles
* fix: show host keys as SHA256 fingerprints like ssh-keygen
* fix: host editor scrolls to a newly enabled protocol section
* fix: pass the host to dashboard host row plugins
* fix: plugin cards show dividers between author, version and category
* fix: refuse plugin archives with paths outside the plugin folder
* fix: theme picker shows one dropdown arrow
* fix: read activity and audit times as UTC so they show in local time
* fix: show the real error for a wrong password or code on plugin sign in
* fix: explain that external sign in needs Auto-create external accounts
* fix: clear leftover plugin unpack folders at startup
* fix: shared session links open for guests again
* fix: share panels no longer list yourself as a share target
* fix: a host update without an auth type keeps the saved password
* fix: guest pages get the tab ids a guest plugin registers
* fix: refuse a 2.8 database, which already had a plugins table
* fix: dashboard cards fit on a phone
* fix: desktop setup no longer asks to secure the local account
* fix: the desktop app keeps the desktop layout in a narrow window
* fix: the desktop app shows the rail and no phone bar in a narrow window
* fix: name every sync type in the Sync panel and pluralize the link prompt
* chore: update the sdk ui snapshot for the host key dialog
* fix: turning a plugin back on restarts the plugins it turned off
* fix: quick connect asks to trust the host key
* fix: revoking a manager also removes the shares they handed out
* fix: database export works for users with TOTP on (#1426)
* fix: rdp and vnc logins use the folder credential when none is picked (#1429)
* fix: imported hosts get status checks without a restart
* fix: re-pin bundled plugins after 1.0.0 re-release
* fix: re-pin bundled plugins after search bar fix
* chore: update app icon to smaller version
* chore: round the readme logo, keep square source for app icons
* feat: grid columns and table view in the plugins tab
* fix: match the host key copy button to the fingerprint height
* fix: use brand color for toggles in sync settings
* fix: re-pin bundled plugins after 1.0.0 re-release
* fix: web terminals failing auth behind a proxy that rewrites Host (Support#651)
* fix: re-pin snippets and termix-identity after 1.0.0 overwrite
* fix: re-pin file-manager after 1.0.0 overwrite
* fix: re-pin file-manager after 1.0.0 overwrite
* chore: remove Crowdin from the release workflow
* chore: remove crowdin config
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix(sso): send the PKCE verifier for GitHub login (#1534)
* fix(remote-desktop): clip cursor overflow without disabling zoom (#1535)
* test(remote-desktop): preserve sessions beyond one hour (#1536)
* fix(hosts): expose controls for overflowing editor tabs (#1537)
* fix(hosts): expose controls for overflowing editor tabs
* test(hosts): mock resize observation in admin panel tests
* fix(status): skip TCP probes while host sessions are active (#1538)
* fix(database): batch session activity persistence (#1539)
* docs(api): describe cookie and API key authentication (#1540)
* fix(snippets): repair missing note column on SQLite upgrades (#1541)
* fix(docker): retain stored SSH credential association (#1543)
* fix(file-manager): render transfer toasts without plugin hooks (#1546)
* feat(hosts): keep compact row actions inline (#1547)
* fix(auth): allow retrying unavailable second-factor interfaces (#1549)
* fix(auth): reject unresolved legacy identity provisioning (#1550)
* fix(desktop): ship the plugin SDK inside Mac App Store builds (#1551)
* fix(tmux): pass full session info to the terminal's session picker (#1552)
The tmux.sessions service reduced detected sessions to bare names, but the
picker reads session.name, so every entry rendered blank and selecting one
sent an empty name, which created a new session instead of attaching.
* fix(ssh): read force keyboard-interactive as a boolean when resolving hosts (#1553)
The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".
* fix(database): batch database saves for bulk host writes (#1554)
* fix(database): yield to the event loop between database saves
Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.
* fix(database): save once per bulk host write instead of once per row
On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.
DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.
* feat(hosts): add instance-wide predefined tag suggestions (#1548)
* feat(hosts): add shared predefined tag suggestions
* style(hosts): format tag catalog routes
* test(database): advance fake timers past the save yield (#1555)
* fix(i18n): complete Simplified Chinese core and plugin translations (#1542)
* fix(i18n): complete Chinese onboarding and navigation labels
* fix(i18n): translate remaining Chinese core and plugin interfaces
* fix(i18n): translate host editor tab overflow controls
* fix(i18n): use consistent Chinese fleet terminology
* fix(i18n): localize hardcoded controls and plugin views
* fix(i18n): translate built-in homepage widget catalog
* test(homepage): follow translated timezone placeholder
* fix(i18n): translate plugin-provided homepage widgets
* fix(i18n): localize feature settings section titles
* fix: 2.8 oidc accounts unable to sign in after upgrading (#1381)
* fix: plugins losing the saved ssh login when copying a host (#1391)
* fix: fleets, proxmox and automations not getting the host sudo password
* fix: host imports running a defaults pass and metrics restart per host (#1384)
* fix: high cpu from status probes and full database saves on every sample (#1300)
* fix: user data export freezing the server (#1393)
* fix: op:// secret references rejected as ssh keys on credentials (#1394)
* fix: slow file deletes from the trash lookups on every delete (#1390)
* fix: add openapi docs and error handling to host tag routes
* test: compare download stream buffers directly so it stops timing out
* chore: drop em dash from host row comment
* chore: update release notes for 2.9.1
* fix: restore space to add host tags and redesign predefined tags editor
* fix: host key silently accepted when the host is missing from the database (#1397)
* fix: clearer host login failed status label and fix its translations (#1396)
* chore: add host key and status label fixes to release notes
* fix: sso and ldap provider dialogs overflowing the screen and using mismatched toggles
* chore: increment ver
* fix(audit): store plugin entries with no acting user as a null user_id (#1556)
Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.
* chore: add sso/ldap dialog and audit log fixes to release notes
* fix: tunnels and host settings missing from shared hosts on desktop
* fix: remote desktop logins missing from shared hosts on desktop
* fix: simplify host status to online/offline and keep it live without a refresh
* chore: sync Crowdin translations for 2.9.1
* fix: build docker frontend/backend natively to stop arm64 hang, build sdk before openapi
---------
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix(sso): send the PKCE verifier for GitHub login (#1534)
* fix(remote-desktop): clip cursor overflow without disabling zoom (#1535)
* test(remote-desktop): preserve sessions beyond one hour (#1536)
* fix(hosts): expose controls for overflowing editor tabs (#1537)
* fix(hosts): expose controls for overflowing editor tabs
* test(hosts): mock resize observation in admin panel tests
* fix(status): skip TCP probes while host sessions are active (#1538)
* fix(database): batch session activity persistence (#1539)
* docs(api): describe cookie and API key authentication (#1540)
* fix(snippets): repair missing note column on SQLite upgrades (#1541)
* fix(docker): retain stored SSH credential association (#1543)
* fix(file-manager): render transfer toasts without plugin hooks (#1546)
* feat(hosts): keep compact row actions inline (#1547)
* fix(auth): allow retrying unavailable second-factor interfaces (#1549)
* fix(auth): reject unresolved legacy identity provisioning (#1550)
* fix(desktop): ship the plugin SDK inside Mac App Store builds (#1551)
* fix(tmux): pass full session info to the terminal's session picker (#1552)
The tmux.sessions service reduced detected sessions to bare names, but the
picker reads session.name, so every entry rendered blank and selecting one
sent an empty name, which created a new session instead of attaching.
* fix(ssh): read force keyboard-interactive as a boolean when resolving hosts (#1553)
The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".
* fix(database): batch database saves for bulk host writes (#1554)
* fix(database): yield to the event loop between database saves
Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.
* fix(database): save once per bulk host write instead of once per row
On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.
DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.
* feat(hosts): add instance-wide predefined tag suggestions (#1548)
* feat(hosts): add shared predefined tag suggestions
* style(hosts): format tag catalog routes
* test(database): advance fake timers past the save yield (#1555)
* fix(i18n): complete Simplified Chinese core and plugin translations (#1542)
* fix(i18n): complete Chinese onboarding and navigation labels
* fix(i18n): translate remaining Chinese core and plugin interfaces
* fix(i18n): translate host editor tab overflow controls
* fix(i18n): use consistent Chinese fleet terminology
* fix(i18n): localize hardcoded controls and plugin views
* fix(i18n): translate built-in homepage widget catalog
* test(homepage): follow translated timezone placeholder
* fix(i18n): translate plugin-provided homepage widgets
* fix(i18n): localize feature settings section titles
* fix: 2.8 oidc accounts unable to sign in after upgrading (#1381)
* fix: plugins losing the saved ssh login when copying a host (#1391)
* fix: fleets, proxmox and automations not getting the host sudo password
* fix: host imports running a defaults pass and metrics restart per host (#1384)
* fix: high cpu from status probes and full database saves on every sample (#1300)
* fix: user data export freezing the server (#1393)
* fix: op:// secret references rejected as ssh keys on credentials (#1394)
* fix: slow file deletes from the trash lookups on every delete (#1390)
* fix: add openapi docs and error handling to host tag routes
* test: compare download stream buffers directly so it stops timing out
* chore: drop em dash from host row comment
* chore: update release notes for 2.9.1
* fix: restore space to add host tags and redesign predefined tags editor
* fix: host key silently accepted when the host is missing from the database (#1397)
* fix: clearer host login failed status label and fix its translations (#1396)
* chore: add host key and status label fixes to release notes
* fix: sso and ldap provider dialogs overflowing the screen and using mismatched toggles
* chore: increment ver
* fix(audit): store plugin entries with no acting user as a null user_id (#1556)
Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.
* chore: add sso/ldap dialog and audit log fixes to release notes
* fix: tunnels and host settings missing from shared hosts on desktop
* fix: remote desktop logins missing from shared hosts on desktop
* fix: simplify host status to online/offline and keep it live without a refresh
* chore: sync Crowdin translations for 2.9.1
---------
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix(macos): skip single-instance lock in Mac App Store builds (#1454)
fix(macos): skip single-instance lock in Mac App Store builds
* Fix/backend optional sharp (#1455)
Fix/backend optional sharp
* feat: issue #1218 (#1218)
https://github.com/Termix-SSH/Support/issues/1218
* feat: Add option to use saved global custom theme under Connection Defaults (#1209)
https://github.com/Termix-SSH/Support/issues/1209
* feat: Add Fleet Sharing functionality to the UI (#1228)
https://github.com/Termix-SSH/Support/issues/1228
* feat: Add a list view to the Docker management page (#1237)
https://github.com/Termix-SSH/Support/issues/1237
* feat: issue #1264 (#1264)
https://github.com/Termix-SSH/Support/issues/1264
* feat: 2FA With FortiToken (#1288)
https://github.com/Termix-SSH/Support/issues/1288
* feat: Allow to disable showing paths to folders (#1274)
https://github.com/Termix-SSH/Support/issues/1274
* feat: Sync Network Graph between Desktop and Remote Server (#1245)
https://github.com/Termix-SSH/Support/issues/1245
* feat: Version Number on offline servers (#1291)
https://github.com/Termix-SSH/Support/issues/1291
* feat: Support OrbStack Docker socket path on macOS / Fix "Docker is not installed" on mac... (#1302)
https://github.com/Termix-SSH/Support/issues/1302
* feat: Sidebar host click should focus existing tab instead of opening a new connection (#1289)
https://github.com/Termix-SSH/Support/issues/1289
* fix: Search in side bar shows overlapping hosts when grouped by tags (#1303)
https://github.com/Termix-SSH/Support/issues/1303
* fix: I can't send a file on its own. (#1304)
https://github.com/Termix-SSH/Support/issues/1304
* fix: sudo password not autofilling (#1248)
https://github.com/Termix-SSH/Support/issues/1248
* fix: node-MainThread (#1300)
https://github.com/Termix-SSH/Support/issues/1300
* fix: Tunnel authentication with shared credentials (#1295)
https://github.com/Termix-SSH/Support/issues/1295
* fix: host key updates not syncing, metrics 404 race on first connect
Host key writes never bumped updatedAt so sync never picked them up.
Also retry the first metrics fetch briefly instead of failing right away.
* chore: increment ver
* feat: add category and icon fields to plugin manifest schema
* ci(deps): bump the github-actions group with 2 updates (#1458)
Bumps the github-actions group with 2 updates: [crowdin/github-action](https://github.com/crowdin/github-action) and [actions/github-script](https://github.com/actions/github-script).
Updates `crowdin/github-action` from 2 to 3
- [Release notes](https://github.com/crowdin/github-action/releases)
- [Commits](https://github.com/crowdin/github-action/compare/v2...v3)
Updates `actions/github-script` from 7 to 9
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v9)
---
updated-dependencies:
- dependency-name: crowdin/github-action
dependency-version: '3'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/github-script
dependency-version: '9'
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 13 updates (#1459)
Bumps the dev-patch-updates group with 13 updates:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.11.0` | `6.11.1` |
| [@codemirror/search](https://github.com/codemirror/search) | `6.7.1` | `6.7.2` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.9` | `6.43.12` |
| [@testing-library/dom](https://github.com/testing-library/dom-testing-library) | `10.4.1` | `10.4.2` |
| [@testing-library/react](https://github.com/testing-library/react-testing-library) | `16.3.2` | `16.3.3` |
| [@testing-library/user-event](https://github.com/testing-library/user-event) | `14.6.5` | `14.6.7` |
| [@types/ssh2](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ssh2) | `1.15.5` | `1.15.6` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.1.0` | `6.1.1` |
| [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.34.1` | `3.34.3` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.4` | `0.5.7` |
| [i18next](https://github.com/i18next/i18next) | `26.4.0` | `26.4.2` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.8` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.12` | `17.0.14` |
Updates `@codemirror/commands` from 6.11.0 to 6.11.1
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@codemirror/search` from 6.7.1 to 6.7.2
- [Changelog](https://github.com/codemirror/search/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/search/commits)
Updates `@codemirror/view` from 6.43.9 to 6.43.12
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)
Updates `@testing-library/dom` from 10.4.1 to 10.4.2
- [Release notes](https://github.com/testing-library/dom-testing-library/releases)
- [Changelog](https://github.com/testing-library/dom-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/dom-testing-library/compare/v10.4.1...v10.4.2)
Updates `@testing-library/react` from 16.3.2 to 16.3.3
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v16.3.2...v16.3.3)
Updates `@testing-library/user-event` from 14.6.5 to 14.6.7
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.6.5...v14.6.7)
Updates `@types/ssh2` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ssh2)
Updates `@vitejs/plugin-react` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.1/packages/plugin-react)
Updates `cytoscape` from 3.34.1 to 3.34.3
- [Release notes](https://github.com/cytoscape/cytoscape.js/releases)
- [Commits](https://github.com/cytoscape/cytoscape.js/compare/v3.34.1...v3.34.3)
Updates `eslint-plugin-react-refresh` from 0.5.4 to 0.5.7
- [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.4...v0.5.7)
Updates `i18next` from 26.4.0 to 26.4.2
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.4.0...v26.4.2)
Updates `prettier` from 3.9.6 to 3.9.8
- [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.9.6...3.9.8)
Updates `react-i18next` from 17.0.12 to 17.0.14
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.12...v17.0.14)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@codemirror/search"
dependency-version: 6.7.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
dependency-version: 6.43.12
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/dom"
dependency-version: 10.4.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/react"
dependency-version: 16.3.3
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/user-event"
dependency-version: 14.6.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@types/ssh2"
dependency-version: 1.15.6
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: cytoscape
dependency-version: 3.34.3
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.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: i18next
dependency-version: 26.4.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: prettier
dependency-version: 3.9.8
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react-i18next
dependency-version: 17.0.14
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 the prod-patch-updates group with 6 updates (#1461)
Bumps the prod-patch-updates group with 6 updates:
| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual) | `3.14.10` | `3.14.13` |
| [compression](https://github.com/expressjs/compression) | `1.8.1` | `1.8.2` |
| [jose](https://github.com/panva/jose) | `6.2.9` | `6.2.12` |
| [jszip](https://github.com/Stuk/jszip) | `3.10.1` | `3.10.2` |
| [socks](https://github.com/JoshGlazebrook/socks) | `2.8.9` | `2.8.10` |
| [undici](https://github.com/nodejs/undici) | `8.10.0` | `8.10.2` |
Updates `@tanstack/react-virtual` from 3.14.10 to 3.14.13
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.13/packages/react-virtual)
Updates `compression` from 1.8.1 to 1.8.2
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/compression/compare/v1.8.1...v1.8.2)
Updates `jose` from 6.2.9 to 6.2.12
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.9...v6.2.12)
Updates `jszip` from 3.10.1 to 3.10.2
- [Changelog](https://github.com/Stuk/jszip/blob/main/CHANGES.md)
- [Commits](https://github.com/Stuk/jszip/compare/v3.10.1...v3.10.2)
Updates `socks` from 2.8.9 to 2.8.10
- [Release notes](https://github.com/JoshGlazebrook/socks/releases)
- [Commits](https://github.com/JoshGlazebrook/socks/compare/2.8.9...2.8.10)
Updates `undici` from 8.10.0 to 8.10.2
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.10.0...v8.10.2)
---
updated-dependencies:
- dependency-name: "@tanstack/react-virtual"
dependency-version: 3.14.13
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: compression
dependency-version: 1.8.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jose
dependency-version: 6.2.12
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jszip
dependency-version: 3.10.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: socks
dependency-version: 2.8.10
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: undici
dependency-version: 8.10.2
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>
* feat: add plugin worker protocol, bootstrap and plugin_storage table
Worker-side ctx proxy plus the message envelope it talks over, and the
per-plugin key/value table behind ctx.storage with migrations for all
three dialects. Rows cascade with the plugin so uninstalling leaves
nothing behind.
* feat: add allowlisted host view for plugins
An allowlist rather than a list of fields to strip. A blocklist rots:
the moment a new secret-bearing column lands on SSHHost it starts
leaking silently. With an allowlist a new field stays invisible to
plugins until someone adds it here on purpose.
* feat: add plugin loader with manifest validation and crash restart
Validates an unpacked plugin, spawns a worker_threads worker and manages
activate/deactivate/crash-restart with backoff, disabling after three
tries.
The crash counter clears only after a plugin stays up past a stability
window, not on activation. Clearing it on activation let a plugin that
activates cleanly and then dies restart forever, because every attempt
looked like the first.
loader.ts documents what the worker boundary does and does not buy you.
Node's permission model is process-wide, not per-worker, so it cannot be
enabled for plugin workers alone and is not claimed here.
* feat: route internal events through a shared plugin event bus
Formalises three ad-hoc publish paths that already existed:
automation-events.ts, metrics/automation-bridge.ts and the
hostSessionStatus singleton, now the host.session.status topic.
Both invariants the old code relied on are preserved. Publishing stays
fire-and-forget, so a failing subscriber cannot disturb a metrics poll or
a host delete. The automations engine subscribes to the bus at scheduler
start instead of being imported ad hoc, which keeps the edge
one-directional: automations may import repositories, hosts modules must
not import automations.
* feat: add plugin ctx broker with permission gate and audit
Every ctx call a plugin makes is checked against its granted
capabilities, performed by the main thread, then audited. A capability
must be both declared in the manifest and granted in the database, so
widening a plugin's reach always needs a new manifest the user can see,
never just a database row.
Audit attribution is set by the broker, not the plugin. The plugin
supplies only the details, so it cannot forge the actor.
fix: stop plugin workers seeing plaintext credentials in error messages
A failing ctx.ssh.connect handed the worker the raw error from deep in
the SSH stack, which embeds the connect config. The worker received, in
full:
Authentication failed for root using password <SECRET>
where <SECRET> was the host's real plaintext password. Any plugin with
ssh.exec could read it by catching the error, defeating the whole point
of the handle-based ssh API.
Errors crossing the boundary are now sanitised. Only PluginFacingError
and PluginPermissionError, both authored here, reach a plugin verbatim;
anything else becomes a generic message and the real one goes to the
log. This uses a marker class rather than matching on message text,
because matching would start leaking again the moment an upstream error
happened to contain a familiar phrase.
Covered by credential-isolation.test.ts, which plants a sentinel in
every secret-bearing field and asserts it appears in no message the
worker ever receives.
* feat: dispatch plugin HTTP routes and wire the runtime into startup
ctx.http.route now registers a real router on the /plugin-api/:pluginId
dispatcher, replacing its stub 404. A plugin never touches the Express
req/res: it gets a plain summary and returns a plain object, so sockets
and session cookies stay on this side of the boundary. Authorization and
cookie headers are not forwarded.
Plugins load last in the start-up sequence, after the server is fully
wired, and are terminated before the database on shutdown so none can
outlive it. A plugin that fails to load cannot stop the backend.
fix: correct the nginx plugin-api location regex
The pattern was ^/plugin-api/(/.*)?$, which needs a double slash and so
never matched /plugin-api/<id>/<route>. Every other block in the file
uses ^/prefix(/.*)?$. Left alone, plugin routes would work in dev and
404 only behind Docker.
* fix: update stale SFTP transfer test for single file destination paths
The test still asserted transferToHost was called with the destination
directory "/srv". Commit 7727fa09 (#1304) deliberately changed a single
file transfer to send the full destination path including the filename,
so the correct expectation is "/srv/remote-1.txt". The code was right
and the test was left behind.
* feat: ship the ssh terminal as a first-party in-process plugin
Adds a hardcoded process:transport-owner tier so the terminal can own its
WebSocket server and ssh2 clients, which cannot cross the worker boundary.
* feat: ship docker as a first-party in-process plugin
Moves docker backend and frontend into plugins/docker/, alongside
ssh-terminal. Adds a host editor tab seam, a dashboard card registry,
and a build step to compile plugin backends into dist.
* feat: ship host metrics as a first-party in-process plugin
* fix: harden plugin auth and add per-request identity for ctx calls
Require auth on /plugin-api, gate plugin enable/grant/revoke routes
behind admin.plugins.manage, and thread the calling user through
worker ctx.hosts/ctx.ssh calls instead of always using the install
owner. Also adds the plugin permissions grant/revoke UI.
* feat: ship the AI assistant as a first-party in-process plugin
Moves src/backend/ai and its routes into plugins/ai, mounted through a
small /ai dispatcher instead of its own port. Moves the ai-enabled and
ai-private-endpoints settings routes out of user-settings-routes.ts
into the plugin too, so it has no leftover core dependents.
* fix: remove compiled plugin JS left behind in plugins/ after build
copy-bundled-plugins.cjs compiled and rewrote plugin backend TS in
place, copied it to dist/plugins, but never deleted the .js it had
just emitted next to the .ts sources. Those files shadowed the real
source for vitest/tsx and had import paths rewritten for the dist
layout, so tests failed with "Cannot find module" once a local build
had run. Affects docker, host-metrics and ai, not just this one.
* feat: add a permission-gated backend service registry for plugins
* feat: add a permission-gated UI action registry and action slots for plugins
Moves the terminal toolbar's hardwired AI button into a slot the ssh-terminal
plugin offers and the ai plugin fills, gated on ai.services.use.
* feat: add cross-plugin secret references gated by the provider's RBAC permission
* feat: move proxmox discovery/import/sync into a first-party plugin
* feat: move remote desktop (RDP/VNC/Telnet) into a first-party plugin
Relocates guacamole backend/frontend source and tests into
plugins/remote-desktop, following the ssh-terminal/docker pattern.
* feat: wire remote-desktop plugin into core and rename i18n keys
Repoints core consumers (dispatcher, collab, session-sharing, tab
utils) at the plugin, adds isTabTypeAvailable gating for rdp/vnc/
telnet, widens hostCapability to accept an array, and renames the
guacamole i18n namespace to remoteDesktop.
* feat: move fleets into a first-party plugin
* feat: move automations into a first-party plugin
* feat: remove legacy alert rules, superseded by automations
Alert rules already auto-migrate into automations on boot and the old
engine stands down after. Deletes the rule/firing tables, routes, UI,
and AI tools; keeps notification channels since automations still use
them. Also fixes automations' notify step, which still called the
deleted repository.
* feat: move network topology into a first-party plugin
* feat: move tailscale device discovery and settings into a plugin
Adds plugins/tailscale/ with a devices sidebar tab, admin settings
section, and re-auth dialog. Host Metrics tailscale manager card and
SSH re-auth banner parsing stay in core.
* feat: move workspaces into a first-party plugin
* feat: move web endpoint into a first-party plugin
* feat: collapse plugin runtime to one tier behind a plugin SDK
* feat: make each plugin an npm workspace built by a termix-plugin CLI
* feat: give plugins their own tables, migrations and sync entities
* feat: serve plugin http and websockets from one mount instead of ports
* feat: namespace plugin permissions and keep them valid when a plugin is off
* feat: give plugins their own settings and one settings screen
* feat: load plugin frontends at runtime and remove plugin ids from the shell
* fix: mock the ai status fetch in its activate test
* feat: add auth extension points and one ssh connect pipeline
* feat: rebuild workspaces as the reference plugin
* fix: gate second factors after SSO/LDAP behind an admin setting
* feat: convert network-topology to a full plugin (B1)
* feat: move snippets backend and tables into a plugin (B2)
* feat: point core frontend snippets pickers at the plugin (B2)
* feat: add the snippets plugin (B2)
* feat: wire ai, automations, fleets and ssh-terminal to snippets (B2)
* feat: finish the tailscale plugin, including SSH auth (B3)
* feat: finish the fleets plugin, add ctx.hosts and host-commands SDK (B4)
* chore: update proxmox and host-metrics locale files for B5
* chore: regenerate drizzle migration journals for B5
* feat: add ctx.hosts create/update to the SDK, host-metrics drops proxmox
* feat: move proxmox node stats and collectors into the proxmox plugin
* feat: drop proxmox host columns, use plugin settings instead
* feat: finish the fleets plugin, add ctx.hosts and host-commands SDK (B4)
* feat: convert file manager into a plugin
* feat: convert ssh tunnels into a plugin
* feat: finish the web-endpoint plugin, add ctx.desktop (B8)
* feat: move the terminal fully into the ssh-terminal plugin (B9)
* feat: convert tmux monitoring into a plugin (B10)
* feat: convert serial console into the serial plugin
* feat: move session sharing and collab rooms into a plugin (B12)
* feat: turn session recording into a plugin (B13)
* feat: finish the remote-desktop plugin (B14)
* feat: finish the host-metrics plugin, move host status into core (B16)
* feat: finish the docker plugin (B15)
* feat: finish the automations plugin (B17)
* feat: finish the ai plugin (B18)
* feat: finish the homepage plugin, move service links and canvas out of core (B19)
* feat: convert wake-on-lan into a plugin (B20)
* feat: convert secret-sources into a plugin (B20)
* feat: move totp and passkeys into plugins (C1)
* feat: move sso and ldap logins into plugins (C2)
* feat: move opkssh and warpgate into plugins (C3)
* feat: move step-ca into a plugin (C4)
* feat: move vault ssh signing into a plugin (C5)
* feat: move termix identity into a plugin (C6)
* feat: move acme certificate automation into a plugin (C7)
* fix: finish 2.9.0 plugin moves and get every check green
* chore: lock the plugin boundary and remove 2.9.0 leftovers
* fix: harden the plugin runtime after the 2.9.0 security review
* feat: add plugin packing, signing, pinned trust and tmxplug bundling
* test: prove 2.8 to 2.9.0 upgrade is lossless and fix what it caught
* fix: give bundled plugin backends a real require for CJS deps
* feat: improve UI loading time and jitter
* feat: show credential sharing inline in the sidebar like host sharing
* feat: tailscale add host and copy ip, fix outbound fetch hang and plugin ESM warning
* feat: unify alerts into one plugin, channels, rules and inbox
* feat: rebuild desktop sync as linked server accounts, fix packaged plugin bundles and UI scaling
* fix: set blacksmith builder cache-key to stop broken gha cache fallback
* fix: link plugin-sdk workspace before npm ci in docker deps stage
* fix: link every plugin workspace before npm ci in docker deps stage
* fix(host-metrics): exclude image and firmware filesystems (#1469)
* fix(host-metrics): exclude image and firmware filesystems
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(hosts): normalize imported jump host references (#1470)
* fix(hosts): normalize imported jump host references
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(ci): build plugin SDK and repair MySQL settings migration (#1471)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): keep inline rename consistent (#1476)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): keep inline rename consistent
* fix(dashboard): reuse translated host status labels (#1475)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(dashboard): reuse translated host status labels
* docs(api): clarify local and remote version fields (#1474)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* docs(api): clarify local and remote version fields
* fix(file-manager): preserve selected backgrounds on hover (#1473)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): preserve selection feedback on hover
* test(ui): await dialog focus cleanup before teardown
* fix(file-manager): restore Ctrl/Cmd+F file search (#1472)
* fix(ci): build plugin SDK before dependent checks
* fix(mysql): keep plugin settings scope indexes within limits
* test(mysql): refresh reviewed plugin upgrade SQL
* fix(file-manager): focus file search with Ctrl or Cmd F
* fix: keep rounded-full circular in production builds
* fix(file-manager): cancel uploads and stop remote writes (#1477)
* fix(file-manager): cancel uploads and stop remote writes
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): apply compact density to directory tree (#1478)
* fix(file-manager): apply compact density to directory tree
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): preserve font sizes in compact mode (#1480)
* fix(file-manager): preserve font sizes in compact mode
* test(css): exercise the configured PostCSS plugin chain
* fix(file-manager): prioritize file names in narrow lists (#1483)
* fix(file-manager): prioritize file names in narrow lists
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* fix(remote-desktop): probe the saved host guacd endpoint (#1481)
* fix(remote-desktop): probe the saved host guacd endpoint
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* fix(homepage): check allowlisted private service reachability (#1479)
* fix(homepage): check allowlisted private service reachability
* test(css): exercise the configured PostCSS plugin chain
* fix(remote-desktop): stop unsolicited clipboard read prompts (#1482)
* fix(remote-desktop): read clipboard only on explicit paste
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* chore: simplify plugin readmes, changelogs and manifest descriptions
* fix: keep plugins loaded after signing in from the login screen
* fix(file-manager): accept OS file drops over existing rows (#1484)
* fix(file-manager): accept OS file drops over existing rows
* test(file-manager): target the file label in drop tests
* test(css): exercise the configured PostCSS plugin chain
* test(identity): preserve full Ed25519 public keys in fixtures
* test(remote-desktop): always mutate ciphertext in tamper test
* fix(file-manager): pipeline SFTP download reads (#1485)
* feat: add plugin sdk build to beta release
* fix: make link-store secret check immune to random base64 matches
* fix(ldap): preserve the directory-provided bind DN (#1486)
* fix: compute beta changes across dev branches and move the beta tag
* chore: untrack plugin sdk ARCHITECTURE.md
* chore: remove references to plugin sdk ARCHITECTURE.md
* feat: merge host editor terminal tabs into one owned by ssh-terminal
* feat: add descriptions to plugin host enable toggles
* fix(file-manager): show download progress in file viewers (#1487)
* fix(electron): signal backend readiness over IPC (#1488)
* fix(split-screen): allow layout creation without randomUUID (#1490)
* fix(split-screen): allow layout creation without randomUUID
* test(credentials): avoid flaky Ed25519 key generation
* fix(sidebar): remeasure visible rows after density changes (#1489)
* fix(sidebar): remeasure visible rows after density changes
* test(credentials): avoid flaky Ed25519 key generation
* feat(split-screen): redesign split screen with free-form panes and pane picker
* feat(sidebar): add host click behavior setting, default to always opening a new tab
* feat(snippets): redesign panel to match the host list
* fix(session-recording): stop session logs panel from refetching in a loop
* feat(snippets): bring back sharing, target hosts and drag reorder
* feat(ssh-terminal): add toolbar position, start state, display and fade host settings
* feat(ssh-terminal): add host setting to turn off password prompt auto-fill
* fix(ai): stop streams getting cut off and keep tool steps across messages
* feat: improve quick connect system using plugin api
* fix: proxmox guest sync, startup snippet sync, drop dead cloudflare ssh transport
* feat: move terminal settings, look and ssh tools into ssh-terminal plugin
* feat: move snippets, keybinding actions and macros out of core into plugins
* feat: store rdp, vnc and telnet logins in a generic per-protocol table
* feat: type the plugin host record, move plugin strings out of core and add lint checks
* feat: replace core homepage widget registry with generic extension points
* feat: plugins only see their own host settings in the browser
* feat: harden plugin api for separate repos, signed updates and generic external login
* fix: keep plugin css below core utilities and version plugin assets by their css
* fix: load and save plugin host settings in the editor and tighten settings row spacing
* feat: redesign user host defaults system
* fix(remote-desktop): release held keys on focus loss (#1498)
* fix(session-sharing): replace local presentation after takeover (#1500)
* fix(file-manager): refresh disk usage with directory listing (#1503)
* fix(ssh-terminal): preserve input order during local echo reconciliation (#1505)
* feat(remote-desktop): expose display zoom controls for RDP (#1514)
* fix(file-manager): restore text selection and copying in Markdown previews (#1518)
* fix(sidebar): keep compact host addresses visible (#1524)
* fix(hosts): preserve plugin settings when reopening the editor (#1499)
* fix(ssh-terminal): close owner sockets when presentations unmount (#1501)
* fix(session-sharing): synchronize shared SSH terminal dimensions (#1502)
* fix(session-sharing): show participant presence in member terminals (#1504)
* fix(ai): sync provider configuration and keys across linked devices (#1506)
* feat(ssh-terminal): add a duplicate tab action (#1510)
* feat(file-manager): allow disabling move-to-trash confirmation (#1511)
* feat(file-manager): default to the remote login directory (#1512)
* feat(homepage): synchronize canvas layouts across devices (#1515)
* fix(shortcuts): prevent accidental double-Shift palette activation (#1519)
* feat(tmux-monitor): add a unified multi-host view (#1520)
* fix(electron): allow explicit quit with active connections (#1522)
* feat: reconnect all disconnected terminal tabs (#1523)
* fix(hosts): preserve recording and sharing settings in the dev editor (#1509)
* fix(database): preserve RDP domain during startup backfill (#1497)
* feat(sidebar): add keyboard type-ahead search for hosts (#1516)
* feat(automations): add per-host maintenance mode and recurring schedules (#1521)
* feat(tmux): add a per-host mouse toggle and preserve remote configuration (#1513)
* fix(session-sharing): use the linked server for desktop meetings and links (#1507)
* fix(tunnels): support TOTP for client tunnels and jump hosts (#1508)
* fix: type errors and lint failures from merged plugin prs
* feat(installer): make the Windows desktop shortcut optional (#1517)
* fix: ship the plugin sdk and serialport in desktop builds
* fix: prefer ipv4 when resolving ssh host names (#1365)
* fix: readable terminal selection with the light theme (#1319)
* fix: build desktop session share links on the linked server (#1350)
* fix: show meeting guests to members and hide the roster from guests (#1353)
* fix: reliable file manager saves with sudo prompt and no base64 guessing (#1335)
* fix: show disk usage for the mount being browsed (#1347)
* fix: load the linux tray icon reliably and log tray failures (#1332)
* fix: cover the gnome close path in the desktop quit test (#1332)
* fix: prompt for jump host TOTP in terminal, file manager and metrics (#1312)
* feat: optional auto reconnect for dropped ssh sessions (#1321)
* feat: tighter compact host rows with inline tags (#1325)
* feat: bring back the alert feed homepage widget
* feat: pick target terminals for snippets again
* feat: share button on connection tabs again
* feat: recent servers list when linking the desktop app
* feat: list the fixed app shortcuts in keyboard shortcuts
* fix: translate hardcoded toasts and drop em dashes from ui text
* fix: translate leftover hardcoded ui text
* fix: toolbar files button opens at the shell directory again
* fix: show the remote desktop note under connection origin again
* chore: openapi docs for host metrics manager routes
* chore: openapi docs for undocumented routes and fix two broken doc blocks
* fix: declare terminal.listSessions use for snippet targets
* revert: drop the alert feed widget, the notification center replaces it
* feat: restore the alert feed homepage widget
* fix: quick connect fills the host address from plugin auth editors like tailscale
* fix: docker container logs use the base background color
* chore: fix dependabot alerts and apply safe npm upgrades
* fix: proxmox stats tab reads its switch from plugin host settings
* fix: count plugin ssh logins toward host status and stop showing reachable as a warning
* fix: hide the built-in source pill in the host editor
* fix: host status shows online when the port answers and only warns on a failed login
* fix: drop the top border on the split layout menu
* fix: host defaults tabs sit where the host editor puts them and skip plugins with nothing to default
* fix: remote desktop auth method labels and notes for vnc and telnet
* fix: add description to the web endpoint enable switch
* fix: crowdin sync and pretranslate for plugin locales, run plugin tests on release
* feat: move telemetry out of core into a usage statistics plugin
* fix: recent activity card no longer cuts off host names early
* fix: remove duplicate top border on split screen menu
* fix: redesign session log player controls, fix tooltip overlap and drop dot separators
* fix: file manager and sftp text showing raw i18n keys
* fix: onboarding rerunning for existing users after upgrading
* fix: fleet detail buttons overflowing the sidebar
* fix: session log player scrolling off screen horizontally
* fix: network graph lag when adding hosts and on every render
* fix: maintenance index name, shared host read only view and badge polling
* fix: meeting tabs, workspace tab merging, rail panels as tabs and general ui lag
* chore: update release notes
* fix: remote desktop tabs reconnecting on every tab switch
* fix: build plugin sdk before linting in release workflow
* chore: lint, format, and bump version to 2.9.0
* chore: skip crowdin pre-translate in release for now
* chore: sync Crowdin translations for 2.9.0
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Koi <38512047+datlt4@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* Fix private AI custom endpoints (#1299)
* Fix Proxmox credential guest imports (#1300)
* Fix Fleet command results layout (#1301)
* Fix synced client tunnel endpoints (#1302)
* Fix Proxmox sync jump host persistence (#1303)
* Fix command palette keyboard navigation (#1304)
* Add accessible interface font choices (#1306)
* Add selectable host temperature sensors (#1307)
* Improve file manager navigation and compact layout (#1308)
* Add configurable global hotkeys (#1305)
* fix: restore split layout selection (#1310)
* fix: support macOS VNC connections (#1311)
* fix: use matching Undici fetch for private AI providers (#1309)
Co-authored-by: Angad Singh <angad@singhangad.in>
* feat: support additional TOTP authenticators (#1312)
* feat: add VNC display zoom controls (#1314)
* feat: add host context menu actions (#1315)
* fix: force classic auth for macOS VNC (#1313)
* fix: harden HTTP trust boundaries (#1316)
* fix: harden application trust boundaries (#1317)
* fix: verify OPKSSH binary integrity (#1318)
* Fix remote desktop connection timeout (#1319)
* chore(deps): bump node in /docker in the docker-major-updates group (#1321)
Bumps the docker-major-updates group in /docker with 1 update: node.
Updates `node` from 24-slim to 26-slim
---
updated-dependencies:
- dependency-name: node
dependency-version: 26-slim
dependency-type: direct:production
dependency-group: docker-major-updates
...
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 15 updates (#1322)
Bumps the dev-patch-updates group with 15 updates:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.7` | `6.43.9` |
| [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) | `21.2.1` | `21.2.2` |
| [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) | `21.2.0` | `21.2.2` |
| [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `7.0.0` | `7.0.1` |
| [@testing-library/user-event](https://github.com/testing-library/user-event) | `14.6.1` | `14.6.5` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.1.10` | `4.1.11` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.10` | `4.1.11` |
| [concurrently](https://github.com/open-cli-tools/concurrently) | `10.0.4` | `10.0.5` |
| [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.34.0` | `3.34.1` |
| [eslint](https://github.com/eslint/eslint) | `10.8.0` | `10.8.1` |
| [eslint-plugin-react-refresh](https://github.com/ArnaudBarre/eslint-plugin-react-refresh) | `0.5.3` | `0.5.4` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.11` | `17.0.12` |
| [sonner](https://github.com/emilkowalski/sonner) | `2.0.7` | `2.0.8` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.2.0` | `8.2.2` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.10` | `4.1.11` |
Updates `@codemirror/view` from 6.43.7 to 6.43.9
- [Changelog](https://github.com/codemirror/view/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/view/commits)
Updates `@commitlint/cli` from 21.2.1 to 21.2.2
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.2/@commitlint/cli)
Updates `@commitlint/config-conventional` from 21.2.0 to 21.2.2
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.2/@commitlint/config-conventional)
Updates `@testing-library/jest-dom` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v7.0.0...v7.0.1)
Updates `@testing-library/user-event` from 14.6.1 to 14.6.5
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.6.1...v14.6.5)
Updates `@vitest/coverage-v8` from 4.1.10 to 4.1.11
- [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.11/packages/coverage-v8)
Updates `@vitest/ui` from 4.1.10 to 4.1.11
- [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.11/packages/ui)
Updates `concurrently` from 10.0.4 to 10.0.5
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v10.0.4...v10.0.5)
Updates `cytoscape` from 3.34.0 to 3.34.1
- [Release notes](https://github.com/cytoscape/cytoscape.js/releases)
- [Commits](https://github.com/cytoscape/cytoscape.js/compare/v3.34.0...v3.34.1)
Updates `eslint` from 10.8.0 to 10.8.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1)
Updates `eslint-plugin-react-refresh` from 0.5.3 to 0.5.4
- [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.3...v0.5.4)
Updates `react-i18next` from 17.0.11 to 17.0.12
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.11...v17.0.12)
Updates `sonner` from 2.0.7 to 2.0.8
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v2.0.7...v2.0.8)
Updates `vite` from 8.2.0 to 8.2.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.2.2/packages/vite)
Updates `vitest` from 4.1.10 to 4.1.11
- [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.11/packages/vitest)
---
updated-dependencies:
- dependency-name: "@codemirror/view"
dependency-version: 6.43.9
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@commitlint/cli"
dependency-version: 21.2.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@commitlint/config-conventional"
dependency-version: 21.2.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/jest-dom"
dependency-version: 7.0.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@testing-library/user-event"
dependency-version: 14.6.5
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@vitest/coverage-v8"
dependency-version: 4.1.11
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@vitest/ui"
dependency-version: 4.1.11
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: concurrently
dependency-version: 10.0.5
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: cytoscape
dependency-version: 3.34.1
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: eslint
dependency-version: 10.8.1
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.4
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react-i18next
dependency-version: 17.0.12
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: sonner
dependency-version: 2.0.8
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: vite
dependency-version: 8.2.2
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: vitest
dependency-version: 4.1.11
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 the prod-patch-updates group with 5 updates (#1324)
Bumps the prod-patch-updates group with 5 updates:
| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual) | `3.14.9` | `3.14.10` |
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | `13.0.2` | `13.0.3` |
| [jose](https://github.com/panva/jose) | `6.2.8` | `6.2.9` |
| [mysql2](https://github.com/sidorares/node-mysql2) | `3.23.2` | `3.23.4` |
| [ws](https://github.com/websockets/ws) | `8.21.1` | `8.21.3` |
Updates `@tanstack/react-virtual` from 3.14.9 to 3.14.10
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.10/packages/react-virtual)
Updates `better-sqlite3` from 13.0.2 to 13.0.3
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v13.0.2...v13.0.3)
Updates `jose` from 6.2.8 to 6.2.9
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.8...v6.2.9)
Updates `mysql2` from 3.23.2 to 3.23.4
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](https://github.com/sidorares/node-mysql2/compare/v3.23.2...v3.23.4)
Updates `ws` from 8.21.1 to 8.21.3
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.1...8.21.3)
---
updated-dependencies:
- dependency-name: "@tanstack/react-virtual"
dependency-version: 3.14.10
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: better-sqlite3
dependency-version: 13.0.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jose
dependency-version: 6.2.9
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: mysql2
dependency-version: 3.23.4
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: ws
dependency-version: 8.21.3
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 motion in the major-updates group (#1326)
Bumps the major-updates group with 1 update: [motion](https://github.com/motiondivision/motion).
Updates `motion` from 12.43.0 to 13.1.1
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v12.43.0...v13.1.1)
---
updated-dependencies:
- dependency-name: motion
dependency-version: 13.1.1
dependency-type: direct:production
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: add semantic motion system (#1320)
* feat: add semantic motion system
* feat: animate session workspace transitions
* feat: refine motion accessibility and transfer feedback
* feat: enforce RBAC and harden collaboration features (#1327)
* feat: enforce RBAC and harden collaboration features
- Mount requirePermission on hosts/snippets/credentials/automations/AI routes
- Seed and backfill system role permissions on every dialect at startup
- Support personal credential overrides for RDP/VNC/Telnet shared hosts
- Broadcast participant presence in shared terminal sessions
- Make audit log forwarding configurable from the admin panel
- Add role members endpoint and snippet folder sharing
* fix: enforce RBAC across split routes
* fix: reject malformed Guacamole tokens safely (#1329)
* fix: allow approved private notification hosts (#1330)
* feat: collaboration rooms with switchable presenter (#1328)
* feat: add collaboration rooms with switchable presenter
Rooms are a group of members watching one stage - the live SSH/RDP/VNC
session the current presenter shares. Any member can take over the
stage; the host can invite, force-stop and end the meeting. Stages
reuse session_shares (new room share type), so gating, recording,
expiry and the global sharing toggle all apply unchanged.
* feat: add stage control handoff to collaboration rooms
The presenter or host can grant any member write access to the live
stage and take it back; members can raise a hand to ask. SSH flips the
participant's permission on the live gate; RDP/VNC re-mint the viewer's
join token. Control clears on every stage switch.
* feat: guest links, role invites and invite awareness for collab rooms
- Anonymous guest link per room (host toggles/rotates), followed by
polling the public resolve endpoint; SSH guests join over the terminal
WS with roomGuestToken, guac guests get read-only join tokens
- Invite by role (expands to current members, snapshot semantics)
- Toast when a room you were invited to appears
- Stale stages are cleared lazily when the presenter is gone
- Telnet presenting, expired-tab fallback, documented single-instance
and guac-kick limits
- Tests for the collab routes, room hub, share access and control flip
* fix: keep remote desktop collaboration read-only
* fix: restore RDP clipboard paste across browsers (#1331)
* fix: show the full command line in the process inspector (#1334)
The CMD column rendered ps's comm field, which the kernel caps at 15
characters, so anything longer looked truncated no matter how wide the
column was. The full args were already collected; show them.
* fix: harden collaboration room access (#1332)
* fix: harden collaboration room access
* fix: confirm guest link lifecycle changes
* fix: make RDP drive redirection writable on the stock deployment (#1333)
* fix: make RDP drive redirection writable on the stock deployment
The default drive-path was /drive on the guacd side, which the official
guacd image cannot create as its non-root user, so every upload was
refused with guacd's raw "FAIL (CANNOT OPEN)" ack. Default to
GUACD_DRIVE_PATH (set to the shared termix-data volume in compose) with
one folder per user, and explain guacd's refusal in the file browser.
* style: format RDP drive settings
* feat: quick connect for RDP and VNC (#1335)
The Quick Connect panel gets a protocol switch. RDP/VNC quick hosts are
built like SSH ones (never saved) and opened as regular remote desktop
tabs; GuacamoleApp mints their token from the typed fields through the
existing /guacamole/token endpoint instead of a host-row lookup.
* fix: authenticate unwatched hosts during the status probe (#1337)
With metrics enabled, the status probe left SSH authentication to the
metrics poll - which only runs while someone is viewing the host. An
unwatched host therefore never left "reachable", while a host with
metrics disabled (whose probe always authenticates) showed online. The
probe now authenticates whenever no metrics poll will.
* feat: compact snippet list option (#1339)
A "Show Commands" toggle in the snippets settings menu hides the command
text under each snippet name, for people who dock the panel on the
narrow right rail and only need the names. Local preference, on by
default.
* fix: guide users to Auto-Tmux when a persisted session expires (#1336)
* fix: guide users to Auto-Tmux when a persisted session expires
A timed-out terminal session silently reconnected to a fresh shell, so
people running long jobs lost them with no explanation and never learned
about Auto-Tmux. Explain the expiry with a one-click Enable Auto-Tmux
action, let admins default it for new hosts and tune the persistence
timeout from the UI, and move the setting up with copy that says what it
does. The global default stays off.
* style: format terminal expiry notice
* feat: improve collaboration rooms (#1338)
* feat: Step CA SSH certificates as a host authentication type (#1340)
* feat: Step CA SSH certificates as a host authentication type
Issue short-lived SSH user certificates from a smallstep CA through its
OIDC provisioner, over the CA's HTTP API rather than the step binary.
Everything after issuance reuses the OPKSSH plumbing: the same encrypted
per-user/host token store, WebSocket dialog and ssh2 certificate
injection, with the connect paths branching on a shared
usesIssuedCertificate() predicate. Instance-wide CA settings live in the
admin panel, with a private-host allowlist for the SSRF guard.
* fix: harden Step CA callback flow
* style: format Step CA changes
* feat: 1Password Connect secret sources for SSH credentials (#1341)
* feat: 1Password Connect secret sources for SSH credentials
Hosts and credentials can hold op://vault/item/field references instead
of secrets; they are resolved at connect time from the user's secret
source (1Password Connect) at the single point where every subsystem
receives plaintext credentials, so terminal, SFTP, Docker, metrics and
tunnels all work without per-subsystem changes. Sources are per user,
optionally shared, with the access token encrypted under the owner's
data key; resolved values are cached briefly in memory.
* style: format secret source changes
* feat: share credentials with users and roles, inherit data on account deletion (#1342)
* feat: share credentials with users and roles, inherit data on account deletion
Credentials can be shared at "use" or "manage" level. Recipients get
a copy re-encrypted under their own data key (shared_credential_secrets),
kept in step with the owner's row through the same lifecycle hooks as
shared host secrets. One gate, findUsableCredential(), replaces the
private-namespace lookups so a shared credential works wherever a
private one does. Deleting a user now hands their hosts and credentials
to a successor (the deleting admin by default) instead of revoking
everything they shared.
* fix: harden credential ownership transfer
* feat: folder shares apply to hosts added later (#1343)
* feat: folder shares apply to hosts added later
Sharing a folder only fanned grants out to the hosts in it at the time.
The share is now also kept as a standing rule on the folder, and a host
created in or moved into it (or a subfolder) inherits the same access
and secret snapshots. Rules follow folder renames and can be stopped
from the share dialog.
* fix: stabilize folder access migrations
* fix: package sharp for both macOS architectures (#1344)
* fix: prompt shared RDP users for credentials (#1345)
* feat: add terminal copy-on-select option (#1346)
* fix: retry protected file reads with sudo (#1349)
* fix: stop SSH-authenticating hosts during routine status polling (#1347)
* fix: preserve omitted host protocol settings (#1350)
* fix: surface remote sync reauthentication failures (#1351)
* fix: harden file reads and timer cleanup (#1352)
* fix: harden file reads and timer cleanup
* fix: preserve literal file path escapes
* fix: enforce SSH pool connection limits (#1353)
* fix: enforce SSH pool connection limits
* fix: discard stale pooled connections
* fix: harden connection, payload, and persisted state handling (#1354)
* fix: clean up Cloudflare tunnel timeouts
* fix: couple tunnel socket lifecycle
* fix: validate Docker console messages
* fix: bound homepage proxy responses
* fix: bound reconnect and response failures
* fix: harden persisted and socket state
* fix: support local connections to shared hosts
* fix: recover expired dashboard metrics sessions (#1355)
* fix: upload files to redirected RDP drives (#1356)
* fix: unify connection toolbar visibility (#1357)
* fix: reset host virtualizer after editing (#1358)
* test: update Guacamole toolbar display mock (#1360)
* fix: reflect live SSH sessions in host status (#1359)
* fix: support Vault auth in file manager (#1361)
* fix: allow exec on shared hosts (#1362)
* fix(file-manager): align chunked upload contract (#1371)
* fix(file-manager): make bulk uploads resilient (#1373)
* fix(terminal): disable local echo on alternate screen (#1372)
* fix(homepage): validate clock widget timezones (#1374)
An invalid timezone in a clock widget's config reached toLocaleTimeString
unchecked, throwing RangeError during render and taking the homepage canvas
down with it. The edit dialog accepted any string, so "America/New York" - a
space where IANA wants an underscore - was easy to save, and the homepage
stayed broken on every later load because the value is reloaded from the
database.
The edit dialog now flags an unusable zone the way FolderMetadataDialog flags
a duplicate folder name: inline message, aria-invalid, and a disabled Save.
Whitespace is normalized to underscores on save, so the space spelling is
stored as America/New_York rather than rejected. ClockWidget falls back to
local time for any config already holding an invalid zone.
Related to Termix-SSH/Support#1238
* fix(hosts): preserve connection origin in editor (#1376)
* fix(remote-sync): expose local login notification (#1375)
* fix(auth): allow passkeys in desktop login (#1378)
* fix(proxmox): elevate guest discovery commands (#1379)
* Fix permanent file-transfer completion toasts (#1383)
* fix(file-manager): expire completed progress toasts
* test(file-manager): use valid transfer status
* fix(hosts): allow clearing SSH key type (#1384)
* fix(terminal): suppress local echo for contextual password prompts (#1387)
* fix(desktop): surface a failed embedded backend start (#1382)
* fix(desktop): surface a failed embedded backend start
When the embedded backend's HTTP port was already taken, the desktop app
sat on the "Loading..." spinner forever with nothing in the UI to say
why. The backend logged the conflict and exited 1, and startBackendServer
resolved false, but that verdict never reached the renderer: the
get-embedded-server-status channel was not exposed in preload and nothing
called it.
The renderer, by design, treats every connection failure as "the embedded
backend is still booting" and retries indefinitely -- in main.tsx's
verifying phase, in Auth's desktop auto-session, and in
FullScreenAppWrapper. That holds while the backend is merely slow to boot,
but not once the process has exited, and nothing distinguished the two.
Classify why the child died from its exit code and a bounded tail of its
stderr, report it through get-embedded-server-status, and have each retry
loop keep waiting only while no failure is reported. A port conflict names
the port, since that is what the user has to act on -- most often a Termix
container on the same ports, or a backend orphaned by a hard-kill that
reapOrphanedBackendProcess could not claim.
A deliberate shutdown is not reported as a crash, and a backend that is
only slow to start still gets retried as before.
FixesTermix-SSH/Support#1254
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(desktop): treat every unrequested backend exit as a failure
The classifier exempted a clean exit and SIGTERM/SIGINT, calling those a
deliberate shutdown. It has no evidence for that: its only caller already
sits behind backendStopRequested, so it is reached only for exits
stopBackendServer() did not ask for. A backend that exited 0 on its own,
or was terminated by the OS or an external signal, therefore reported no
failure at all -- leaving all three renderer loops waiting forever for a
process that is gone, which is the exact hang this change set exists to
remove.
Every exit reaching the classifier is now a failure, port-in-use when
stderr carries EADDRINUSE and crashed otherwise, with backendStopRequested
left as the sole deliberate-shutdown guard. That makes the exit code and
signal irrelevant to the verdict, so the classifier now takes only the
stderr tail rather than carrying two parameters it no longer reads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Add white label branding settings (#1386)
* feat(admin): add white label branding settings
Admins can configure a custom app name, logo and tagline from the
Admin Settings panel. The values apply to the login screen and the
browser tab title/favicon at runtime.
* fix(admin): restore default icons on logo reset, validate upload MIME type
Resetting the logo now restores the bundled favicon/apple-touch-icon
instead of leaving the previous custom one until a reload. The admin
upload also validates file.type up front instead of only failing
later on save.
* fix(admin): restore default favicons and reject invalid logo types
Capture bundled icon hrefs before the first custom logo is applied so a
reset does not leave the previous upload in the tab, and reject
non-image uploads before they are read into branding state.
* test(admin): cover branding routes and logo validation
Add backend tests for the public GET, admin-only PATCH, unknown-field
rejection, and parseBrandingLogoDataUrl so invalid images cannot land
in settings without a failing test.
* fix(backend): report port conflicts on the service ports (#1388)
Express's app.listen(port, host, callback) registers that callback as the
server's error handler as well as its listening handler:
if (typeof args[args.length - 1] === 'function') {
var done = args[args.length - 1] = once(args[args.length - 1])
server.once('error', done)
}
so a bind failure invoked the service's "started" callback with an
EADDRINUSE error as its only argument and emitted nothing. None of the
services read that argument, which made a failed bind indistinguishable
from a successful one: the service logged that it had started, ran its
initialisation, and served nothing. With one of 30003-30012 occupied the
backend still reported backend_init_complete, the app loaded, and the
affected feature was dead for the session with no error anywhere in the
logs -- searching the backend log, the Electron main log and stdout for
EADDRINUSE returned nothing, as did uncaughtException and
unhandledRejection probes.
This is also why only the main port behaved sensibly: database.ts does
not use app.listen(), it builds the server and attaches a real error
handler, so 30001 was the one port whose conflict was ever detected.
listenOnServicePort() builds the server so that listening and error stay
separate, and treats a conflict the way database.ts already does -- name
the port, then exit -- rather than running on with one feature missing.
Exiting also hands the desktop app the classified failure it already
surfaces, so the user is told which port to free. Services that must own
their server, such as the tunnel service with its WebSocket upgrade
handler, get the same handler via attachServicePortConflictHandler().
Note that adding .on("error") to the services would not have worked:
express consumes the event through once('error', done) before any later
handler runs. The callback has to stop being passed to listen() at all.
FixesTermix-SSH/Support#1260
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(desktop): allow RDP/VNC/Telnet to originate from this device (#1389)
The desktop pinned rdp/vnc/telnet to the remote server, so a host the
user's own machine could reach but the Termix server could not was
impossible to open: the app answered "Remote server required" even when
no remote server was wanted. The embedded backend already runs the
Guacamole websocket server and guacd's address is already configurable
globally and per host, so what was missing was the choice.
resolveConnectionOrigin now honours an explicit per-host origin for
these protocols. Left on Default they still resolve to remote: they need
a guacd, which the desktop does not ship, so originating locally only
works once the user has pointed Termix at one of their own. Keeping that
opt-in means an upgrade never moves a working connection onto a guacd
that is not there. Serial and non-Electron behaviour are unchanged.
Three call paths had the same assumption baked in and would have quietly
ignored the setting:
- The Guacamole call sites resolved the origin without passing the
host's own override, so it could never take effect.
- guacamole-api sent every token, connect-host and status call to the
remote server whenever running under Electron, and remapped the host
id onto the remote server's id -- which would address the wrong row,
or fail outright with no server configured.
- GuacamoleDisplay minted its token before resolving the origin, so the
token could come from a different backend than the socket dialled.
The origin is a required parameter on those API functions rather than a
defaulted one. A default silently sent a missed call site to the remote
server: the guacd status check in fetchToken was one, and it failed with
a bare "Network Error" on a desktop with no server configured. Making it
required means the compiler names every caller instead. That also
covers CollabRoomTab, which now resolves from the host it already holds,
so a locally-originated host stays local when presented into a room.
Finally, the Connection Origin control was gated on SSH alone, so a host
enabling only these protocols could never reach the setting. That gate
is now a named predicate covering every protocol the control applies to.
Verified end to end on Linux against a local guacd: with a host set to
"This device", guacd accepted the connection and reached the target,
which answered for itself. With guacd stopped, the guacd status check
that has run before every connection since v2.3.0 -- now asking the
backend the session will actually use -- reports it clearly before a
socket is opened.
Refs Termix-SSH/Support#1240
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(local-terminal): add copy/paste support (#1391)
* feat(local-terminal): add copy/paste support
Local terminal had no clipboard wiring, so selected text couldn't be
copied. Extracted the SSH terminal's copy/paste shortcut and
right-click handling into a shared terminal-clipboard module and wired
it into both terminals, removing duplicated logic in the process.
* fix(local-terminal): stop language changes from restarting the shell session
The session effect that starts the local PTY depended on `t`, whose
identity changes on every language switch (react-i18next). That tore
down the running shell and spawned a new one just from changing the UI
language. Read translations through a ref instead so localization
stays decoupled from the PTY lifecycle.
Also adds regression coverage for the extracted terminal-clipboard.ts
helpers: copy with/without selection, explicit vs native paste,
right-click preference, and Ctrl+right-click passthrough.
* fix(file-manager): keep name column visible on narrow viewports in list view (#1402)
Rebased onto dev-2.8.0 (the density refactor kept the same 3
list-view grid patterns). Use minmax(140px, 1fr) for the name track
so it cannot collapse to 0px on narrow viewports; the table scrolls
horizontally instead of hiding names and icons.
Co-authored-by: inontz <inontz@users.noreply.github.com>
* feat(desktop): local filesystem and transfer bridge for the file manager (#1392)
* feat(desktop): local filesystem and transfer bridge for the file manager
Adds the Electron main-process side of the upcoming Local | Remote dual-pane
file manager, with no UI yet:
- electron/local-files.cjs: IPC handlers to browse the local disk (home,
list, mkdir, createFile, rename, trash, ensureDir, walk, reveal, open) and
to stream files between the local disk and the file-manager backend
(uploadLocalFile / downloadToLocal with progress events and cancellation).
Streams go through Electron's `net` so the session cookie / remembered JWT
is attached the same way as the renderer's own requests.
- electron/preload.js: exposes them as `window.electronAPI.localFs` and
`window.electronAPI.localTransfer`; the existing `invoke` allowlist is
untouched.
- src/types/electron.d.ts: typings for the new surface.
- electron/main.cjs: registers the handlers.
Follow-up PRs add the renderer side (local pane, drag-and-drop transfers,
context menu).
* fix(desktop): harden the local transfer bridge (origin allowlist, collision policy)
Addresses the review on the transfer boundary:
- The renderer no longer supplies a URL or headers. It sends
`{ origin: "local" | "remote", route, deviceId }` and the main process
resolves the target itself: fixed route allowlist (`uploadFileStream`,
`downloadFileStream`), local = the embedded backend base, remote = the
remote-sync config's URL (http/https only) with the stored JWT. Anything
else is rejected before a request is made. `deviceId` is validated.
- Downloads never rename or replace silently. The destination is checked
first and `EEXIST` is returned unless the caller passes `overwrite: true`.
Each transfer writes to its own `<dest>.<transferId>.termix-part` opened
with `wx`, and publishes with `fs.link` / `COPYFILE_EXCL` (rename only when
overwriting), so concurrent transfers to the same path cannot share or
clobber a partial (`EBUSY` for the second). Partials are removed on
failure or cancel.
- New `local-fs:exists` handler so the renderer can ask before starting.
- `createLocalFileHandlers` / `createTargetResolver` take their
dependencies (net, shell, remote-sync getters) as parameters so the
boundary is unit-testable without Electron.
- src/backend/tests/electron/local-files.test.ts: target resolution and
off-origin refusal, EEXIST / EBUSY / overwrite paths, unique partials and
cleanup, upload multipart integrity (parsed with Busboy).
* fix(desktop): Windows-safe replace for overwrite downloads
`publishDownload()` used `rename(partial, dest)` for the explicit overwrite
path. POSIX replaces the destination, but on Windows rename() onto an
existing name commonly fails (EEXIST / EPERM, always while the file is
open), so "Replace" did not actually work there.
The overwrite path no longer renames onto an occupied name:
1. the current file is moved aside to a transfer-unique sibling
(`<dest>.<transferId>.termix-replaced`) - renaming to a fresh name is
safe on every platform;
2. the partial is published exclusively under the now-free name (the same
`link` / `COPYFILE_EXCL` primitive the non-overwrite path uses);
3. the aside copy is deleted (retried once; if another process still holds
it open on Windows it is left in place and logged rather than failing
the transfer).
Failure handling preserves the original: if step 1 fails (file in use)
nothing has changed and the caller gets `EBUSY`; if step 2 fails the aside
copy is moved back under its name and the error propagates. Replacing a
folder with a file is refused with `EISDIR`; a destination that vanished
mid-transfer falls back to the exclusive publish.
The publish primitives take their filesystem operations as an injectable
`publishFs` (default: real fs), and the tests drive them with a
Windows-like fs whose rename() refuses to overwrite - so the strategy is
verified without relying on POSIX rename-over-existing semantics:
successful swap with no rename ever targeting an occupied name, original
restored byte-for-byte when publishing fails, EBUSY with nothing touched
when the file cannot be moved aside, EISDIR for folders, vanished
destination, and the end-to-end overwrite through the download handler.
---------
Co-authored-by: Max <maxim@cogitate.ai>
* chore: update package lock
* feat(file-manager): Termius-style Local | Remote dual pane with drag-and-drop transfers (desktop)
Renderer side of the dual-pane file manager, built on the local filesystem
bridge added in the previous PR. Desktop app only; the web build is
unchanged (the toggle is hidden when `window.electronAPI.localFs` is absent).
- New Local pane (LocalFilePane) next to the remote grid, toggled from the
toolbar (Laptop icon); path, visibility and width are remembered in
localStorage (`termix:file-manager:local-pane:*`). Grid and list views,
hidden files toggle, breadcrumb navigation, New Folder.
- Drag files/folders from the Local pane onto the remote grid to upload,
and from the remote grid onto the Local pane to download. Both directions
stream through the main process (`useLocalTransfers`) with a single
progress toast per batch (speed, ETA, cancel). Finder drops onto the
remote grid keep working as before.
- Collision policy for downloads: destinations are checked first; if any
exist the user is asked Replace / Skip for the batch. Skip, dismiss and
timeout all mean skip - nothing is ever replaced without an explicit
click, and the main process enforces the same rule (`EEXIST` unless
`overwrite` is set).
- Drag MIME contract: `application/x-termix-local-files` for local drags,
`application/x-termix-remote-files` marker on the remote grid's internal
drags, so each pane can tell the two apart from Finder drops.
- Transfer targets are described as `{ origin, route, deviceId }`
(`getSessionOrigin` in main-axios) - the renderer never hands the main
process a URL.
- i18n: new `fileManager.local*` keys in en.json only (other locales via
Crowdin).
- Tests: LocalFilePane rendering/navigation, local-transfer-utils
(relative-path planning, size formatting).
- Modified column uses the same `Mon DD HH:MM` / `Mon DD YYYY` (ls -l style)
format as the remote grid, so both panes read alike.
* docs: point security note to the docs root (old /security page is gone) (#1400)
* chore: sync Crowdin translations
* chore(deps): bump the prod-minor-updates group across 1 directory with 3 updates (#1325)
Bumps the prod-minor-updates group with 3 updates in the / directory: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [js-yaml](https://github.com/nodeca/js-yaml) and [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg).
Updates `@anthropic-ai/sdk` from 0.116.0 to 0.120.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.116.0...sdk-v0.120.0)
Updates `js-yaml` from 5.2.3 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0)
Updates `pg` from 8.22.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)
---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
dependency-version: 0.120.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: js-yaml
dependency-version: 5.3.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: pg
dependency-version: 8.23.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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-minor-updates group across 1 directory with 10 updates (#1323)
Bumps the dev-minor-updates group with 10 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.5` | `6.1.0` |
| [electron](https://github.com/electron/electron) | `43.2.0` | `43.4.1` |
| [globals](https://github.com/sindresorhus/globals) | `17.9.0` | `17.11.0` |
| [i18next](https://github.com/i18next/i18next) | `26.3.6` | `26.4.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.28.0` | `1.33.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.84.0` | `7.85.0` |
| [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) | `10.4.1` | `10.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` |
Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react)
Updates `electron` from 43.2.0 to 43.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.4.1)
Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)
Updates `i18next` from 26.3.6 to 26.4.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.6...v26.4.0)
Updates `lucide-react` from 1.28.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)
Updates `react-hook-form` from 7.84.0 to 7.85.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.84.0...v7.85.0)
Updates `react-pdf` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v10.5.0/packages/react-pdf)
Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@types/node"
dependency-version: 26.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: electron
dependency-version: 43.4.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: globals
dependency-version: 17.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: i18next
dependency-version: 26.4.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: lucide-react
dependency-version: 1.33.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-hook-form
dependency-version: 7.85.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-pdf
dependency-version: 10.5.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.67.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: sync Crowdin translations
* Update redirect-issues workflow configuration
Updated the workflow to change the runner and modify the issue response message.
* chore: sync Crowdin translations
* Add Hetzner logo and referral link to README
Added Hetzner logo with a referral link to README.
* Fix Hetzner logo URL in README.md
* docs: point security note to the feature security page
The old /security page is gone and the docs root redirects to /install/,
which loses the encryption context the sentence promises. Point the
per-user secret and database encryption note at the canonical
docs.termix.site/features/authentication/security/ page instead.
Applied across all README locales.
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: ssmurfgg04-gif <232103099+ssmurfgg04-gif@users.noreply.github.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
* fix: use i18n for host status tooltip labels (#1403)
* chore: sync Crowdin translations
* chore(deps): bump the prod-minor-updates group across 1 directory with 3 updates (#1325)
Bumps the prod-minor-updates group with 3 updates in the / directory: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [js-yaml](https://github.com/nodeca/js-yaml) and [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg).
Updates `@anthropic-ai/sdk` from 0.116.0 to 0.120.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.116.0...sdk-v0.120.0)
Updates `js-yaml` from 5.2.3 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0)
Updates `pg` from 8.22.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)
---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
dependency-version: 0.120.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: js-yaml
dependency-version: 5.3.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: pg
dependency-version: 8.23.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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-minor-updates group across 1 directory with 10 updates (#1323)
Bumps the dev-minor-updates group with 10 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.5` | `6.1.0` |
| [electron](https://github.com/electron/electron) | `43.2.0` | `43.4.1` |
| [globals](https://github.com/sindresorhus/globals) | `17.9.0` | `17.11.0` |
| [i18next](https://github.com/i18next/i18next) | `26.3.6` | `26.4.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.28.0` | `1.33.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.84.0` | `7.85.0` |
| [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) | `10.4.1` | `10.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` |
Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react)
Updates `electron` from 43.2.0 to 43.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.4.1)
Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)
Updates `i18next` from 26.3.6 to 26.4.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.6...v26.4.0)
Updates `lucide-react` from 1.28.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)
Updates `react-hook-form` from 7.84.0 to 7.85.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.84.0...v7.85.0)
Updates `react-pdf` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v10.5.0/packages/react-pdf)
Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@types/node"
dependency-version: 26.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: electron
dependency-version: 43.4.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: globals
dependency-version: 17.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: i18next
dependency-version: 26.4.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: lucide-react
dependency-version: 1.33.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-hook-form
dependency-version: 7.85.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-pdf
dependency-version: 10.5.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.67.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: sync Crowdin translations
* Update redirect-issues workflow configuration
Updated the workflow to change the runner and modify the issue response message.
* chore: sync Crowdin translations
* fix: use i18n for host status tooltip labels (#1265)
* fix: add missing hosts.status.* locale keys (fixes#1265)
* fix: add hosts.status.* translation keys for i18n tooltip
The buildStatusTooltip function calls t("hosts.status.available"), t("hosts.status.reachable"), t("hosts.status.offline"), and t("hosts.status.monitoringDisabled"), but the locale file only had "status": "Status" as a flat string.
Replaced with a status object containing all four keys plus a "label" key preserving the original "Status" string.
* fix: restore final newline in en.json
Requested by ZacharyZcR in review feedback.
* test: assert translated labels in buildStatusTooltip
Per review feedback from ZacharyZcR: add focused tests that exercise
buildStatusTooltip with a translator and assert the rendered labels
rather than key paths. Covers all three status values, monitoring
disabled, protocol list, and the no-key-path regression guard.
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
* fix(workspaces): fall back when randomUUID is unavailable (#1397)
* fix(terminal): preserve macOS Alt digit characters (#1398)
* fix(ssh): verify resolved server host identity (#1419)
* fix: add principals to Termix ID certificates (#1421)
* chore: sync Crowdin translations
* chore(deps): bump the prod-minor-updates group across 1 directory with 3 updates (#1325)
Bumps the prod-minor-updates group with 3 updates in the / directory: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [js-yaml](https://github.com/nodeca/js-yaml) and [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg).
Updates `@anthropic-ai/sdk` from 0.116.0 to 0.120.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.116.0...sdk-v0.120.0)
Updates `js-yaml` from 5.2.3 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0)
Updates `pg` from 8.22.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)
---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
dependency-version: 0.120.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: js-yaml
dependency-version: 5.3.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: pg
dependency-version: 8.23.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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-minor-updates group across 1 directory with 10 updates (#1323)
Bumps the dev-minor-updates group with 10 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.5` | `6.1.0` |
| [electron](https://github.com/electron/electron) | `43.2.0` | `43.4.1` |
| [globals](https://github.com/sindresorhus/globals) | `17.9.0` | `17.11.0` |
| [i18next](https://github.com/i18next/i18next) | `26.3.6` | `26.4.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.28.0` | `1.33.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.84.0` | `7.85.0` |
| [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) | `10.4.1` | `10.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` |
Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react)
Updates `electron` from 43.2.0 to 43.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.4.1)
Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)
Updates `i18next` from 26.3.6 to 26.4.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.6...v26.4.0)
Updates `lucide-react` from 1.28.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)
Updates `react-hook-form` from 7.84.0 to 7.85.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.84.0...v7.85.0)
Updates `react-pdf` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v10.5.0/packages/react-pdf)
Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@types/node"
dependency-version: 26.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: electron
dependency-version: 43.4.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: globals
dependency-version: 17.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: i18next
dependency-version: 26.4.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: lucide-react
dependency-version: 1.33.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-hook-form
dependency-version: 7.85.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-pdf
dependency-version: 10.5.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.67.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: sync Crowdin translations
* Update redirect-issues workflow configuration
Updated the workflow to change the runner and modify the issue response message.
* chore: sync Crowdin translations
* Add Hetzner logo and referral link to README
Added Hetzner logo with a referral link to README.
* Fix Hetzner logo URL in README.md
* chore: pull sponsor logos from the docs site
* fix: add principals to Termix ID certificates
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
* fix(deps): resolve transitive security advisories (#1412)
* chore: sync Crowdin translations
* chore(deps): bump the prod-minor-updates group across 1 directory with 3 updates (#1325)
Bumps the prod-minor-updates group with 3 updates in the / directory: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [js-yaml](https://github.com/nodeca/js-yaml) and [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg).
Updates `@anthropic-ai/sdk` from 0.116.0 to 0.120.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.116.0...sdk-v0.120.0)
Updates `js-yaml` from 5.2.3 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0)
Updates `pg` from 8.22.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)
---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
dependency-version: 0.120.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: js-yaml
dependency-version: 5.3.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: pg
dependency-version: 8.23.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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-minor-updates group across 1 directory with 10 updates (#1323)
Bumps the dev-minor-updates group with 10 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.5` | `6.1.0` |
| [electron](https://github.com/electron/electron) | `43.2.0` | `43.4.1` |
| [globals](https://github.com/sindresorhus/globals) | `17.9.0` | `17.11.0` |
| [i18next](https://github.com/i18next/i18next) | `26.3.6` | `26.4.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.28.0` | `1.33.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.84.0` | `7.85.0` |
| [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) | `10.4.1` | `10.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` |
Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react)
Updates `electron` from 43.2.0 to 43.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.4.1)
Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)
Updates `i18next` from 26.3.6 to 26.4.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.6...v26.4.0)
Updates `lucide-react` from 1.28.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)
Updates `react-hook-form` from 7.84.0 to 7.85.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.84.0...v7.85.0)
Updates `react-pdf` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v10.5.0/packages/react-pdf)
Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@types/node"
dependency-version: 26.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: electron
dependency-version: 43.4.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: globals
dependency-version: 17.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: i18next
dependency-version: 26.4.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: lucide-react
dependency-version: 1.33.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-hook-form
dependency-version: 7.85.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-pdf
dependency-version: 10.5.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.67.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: sync Crowdin translations
* Update redirect-issues workflow configuration
Updated the workflow to change the runner and modify the issue response message.
* chore: sync Crowdin translations
* Add Hetzner logo and referral link to README
Added Hetzner logo with a referral link to README.
* Fix Hetzner logo URL in README.md
* chore: pull sponsor logos from the docs site
* fix(deps): resolve transitive security advisories
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
* fix(sync): map nested host parent references (#1418)
* fix(metrics): surface SSH host key changes (#1404)
* fix(guacamole): coalesce VNC wheel floods and label meta keys by device (#1385)
High-latency proxies like Cloudflare queue guacamole-common-js scroll
clicks so the desktop keeps crawling after the user stops. Cap in-flight
wheel buttons, and show Super/Cmd in the VNC toolbar on non-Windows clients.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix(file-manager): keep downloaded files inside the selected local folder
Remote file names were turned into local destinations by splitting the
relative remote path on "/" and concatenating each name with the platform
separator. A POSIX file name may contain "\", ":" or end in a dot, so on
Windows a remote "..\outside.txt" downloaded into C:\Downloads\selected was
normalised to C:\Downloads\outside.txt - outside the folder the user picked,
even with overwrite disabled. The main process only normalised destPath and
never checked it against the selected root.
Two independent layers now enforce containment:
Renderer (local-transfer-utils.ts)
- `assertSafeLocalComponent(name, separator)` validates each remote path
component for the destination platform: never empty, "." or "..", never
"/" or NUL; on Windows (separator "\") additionally no "\ : * ? " < > |",
no control characters, no trailing dot/space, no reserved device names
(CON, NUL, COM1...). Backslashes stay legal on macOS/Linux, where they are
ordinary file-name characters and the result remains inside the folder.
- `buildLocalDestination(localDir, relativePath, separator)` joins the
validated components and asserts the result is strictly under localDir
(case-insensitive on Windows).
- useLocalTransfers builds every file and directory destination through it.
Items that fail are skipped before any filesystem call and reported
("Skipped N item(s) whose names cannot be used on this computer").
Main process (electron/local-files.cjs)
- `assertWithinRoot(rootPath, candidate, pathImpl)` resolves + normalises
both paths and refuses anything that is the root itself, escapes it
(".." after normalisation), is absolute relative to it (another drive,
UNC) or contains empty/".." segments. `pathImpl` is injectable so the
Windows rules run in tests on any OS.
- downloadToLocal requires `rootPath` (the selected folder) and checks the
destination before the request is made or any file is created; the
ensure-dir handler applies the same check when a root is passed (used for
the directory skeleton of downloaded trees).
- LocalDownloadRequest / downloadSessionFileToLocal / ensureLocalDirectory
carry the root.
Tests
- Renderer: ordinary nested folders on Windows; backslash traversal in a
POSIX name; absolute, drive-qualified and UNC names; reserved names,
trailing dots/spaces, control chars; POSIX traversal rejected while a
POSIX-legal backslash name is kept inside the folder.
- Main process (path.win32): nested destinations accepted incl. case
differences; "selected\..\outside.txt" refused; other drive / UNC /
sibling folder refused; missing root refused; end-to-end handler test that
a POSIX "../outside.txt" download and an escaping ensure-dir are refused
before any network traffic or disk write.
* feat(file-manager): right-click context menu for the local pane
Mirrors the remote grid's menu for the user's own disk. On an entry (or
the current multi-selection): Open / Open folder, Upload to server,
Reveal in Finder/Explorer, Rename (inline, F2), Copy Path, Move to Trash
(confirmation toast, Del). On the background: New Folder, New File,
Reveal, Show/Hide hidden files, Refresh (F5). Enter opens, Cmd/Ctrl+A
selects all, Escape clears the selection.
Deletion goes through shell.trashItem so it lands in the OS Trash and is
recoverable; rename and create refuse names containing path separators
and never overwrite an existing entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* feat(file-manager): resizable list-view columns in both panes
Drag the boundary at the left edge of a column header (Modified, Owner,
Size, Permissions on the remote grid; Modified, Size, Kind in the local
pane) to change its width; the Name column takes whatever is left.
Double-click a handle to reset that column. Widths are remembered per
pane in localStorage. Finishing a drag over a sortable header no longer
toggles the sort.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* feat(file-manager): pinned ".." parent entry in both panes
Both the remote grid (list, grid and empty-folder states) and the local
pane show a Termius-style ".." row pinned above the entries whenever the
current folder has a parent. Double-clicking it goes up one level. It is
also a drop target: local files dropped on it upload into the parent
folder, remote rows dragged onto it move there, and remote items dropped
on the local pane's ".." download into the parent folder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* feat(file-manager): show/hide list-view columns from the header
Right-click a list-view header (remote grid or local pane) to open a
Columns menu and tick/untick Modified, Owner, Size, Permissions (remote)
or Modified, Size, Kind (local). Hidden columns leave the grid template
entirely so the Name column gets the space back; at least one optional
column always stays on. The choice is remembered per pane alongside the
column widths.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* feat(file-manager): sidebar toggle works on desktop too
The toolbar's sidebar button now shows/hides the directories panel on
desktop (persisted), while below md it still opens the mobile overlay.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* feat(file-manager): resizable directories sidebar
The Trash/Directories sidebar can now be resized by dragging its right
edge (160px minimum, up to 40% of the row); double-click the handle to
restore the default 224px. The width is remembered. The same handle and
persisted-width hook now also drive the local pane divider, replacing the
inline implementation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bn6K6xNAihgWZ5fMVWt1W
* fix(metrics): support macOS/WIN hosts in stats collection (#1430)
* fix(metrics): support macOS hosts in stats collection
Host metrics collectors only ever ran Linux commands (/proc/*, ip,
GNU df flags), so any macOS host always reported 0%/null for
cpu, memory, uptime, network and disk. Detect the host platform
once per poll and use vm_stat, sysctl, top, ifconfig and netstat
on Darwin hosts instead.
* fix(metrics): support Windows hosts in stats collection
RDP/VNC hosts running Windows hit the same problem as macOS did:
no /proc, no BSD tools, not even uname. Route CPU, memory, uptime,
network and disk through PowerShell (WMI/CIM) when the host is
detected as Windows, using -EncodedCommand so there's no cmd.exe
quoting to fight with.
* fix(metrics): use real macOS df/mount for disk collection
BSD df's -T flag filters by filesystem type instead of printing one
like GNU df does, so the previous macOS disk path was still running
a GNU-only command and coming back empty. Now uses df -Pk plus mount
output to build the filesystem type/percent list, filtering out the
noisy APFS system volumes and devfs/autofs mounts. Verified on an
M4 MacBook running the latest macOS - disk usage now reports correctly.
* feat(file-manager): Termius-style Local | Remote dual pane with drag-and-drop transfers (desktop) (#1413)
Integrate the reviewed change and its maintainer fixes into dev-2.8.0.
Validated with focused regression tests, type-check, lint, formatting, build, and the PR Check database jobs.
* fix(terminal): persist Ctrl+/- font zoom so it survives option refreshes (#1399)
Integrate the reviewed change and its maintainer fixes into dev-2.8.0.
Validated with focused regression tests, type-check, lint, formatting, build, and the PR Check database jobs.
* Fix macOS terminal packaging and keyboard shortcuts. (#1417)
Integrate the reviewed change and its maintainer fixes into dev-2.8.0.
Validated with focused regression tests, type-check, lint, formatting, build, and the PR Check database jobs.
* fix(auth): skip TOTP only after verified WebAuthn user verification (#1420)
Integrate the reviewed change and its maintainer fixes into dev-2.8.0.
Validated with focused regression tests, type-check, lint, formatting, build, and the PR Check database jobs.
* feat(hosts): add per-host Web Endpoints (direct or SSH-tunnelled web UIs) (#1416)
* test: provide an in-memory localStorage for the vitest environment
jsdom in this project ships without a localStorage global, so every suite
that touched storage threw "Cannot read properties of undefined" during
setup and failed wholesale -- 119 tests across 15 files, none of them
actual product defects.
Node only supplies localStorage with --localstorage-file, which persists to
disk and is shared across test files. A per-process in-memory Storage is
what tests want, so define one (plus sessionStorage) when the global is
absent, alongside the existing matchMedia shim.
Full suite goes from 15 failed files / 119 failed tests to 400 files and
2888 tests passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fdjjsdaz6aFMyJr4h3Y6qj
* feat(web-endpoints): add endpoint types and URL resolution
First step of per-host Web Endpoints: a host declares web UIs it serves and
opens them either directly at the host's address or through an SSH forward
Termix establishes on demand.
This commit is pure logic with no I/O, so it needs no mocks to test.
resolveWebEndpointUrl expects an already-normalized path -- normalization is
the storage boundary's job (a later commit), and duplicating it here would
invite the two copies to drift.
separatedTunnelHost is the one part that is a security control rather than a
convenience. Cookies are keyed by host and ignore the port, and SameSite
computes "site" as scheme + registrable domain -- also ignoring the port. A
forward reached at the host string serving Termix is therefore same-site with
Termix, which leaks the session both ways: the framed service receives the jwt
cookie, and a Set-Cookie it returns lands in the jar Termix's own API calls
read from. So a tunnel URL resolves to a different loopback spelling than the
page's, and refuses outright when no alias exists. Only loopback literals
qualify -- a same-registrable-domain alias could still answer Set-Cookie with
a Domain attribute that reaches Termix.
The cookie-separation tests were verified to fail against a resolver that
returns the page host (4 failures), so they cannot pass vacuously.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(web-endpoints): validate and normalize endpoint config
normalizeWebEndpoints is the enforcement point for values that reach an href
and an iframe src, so the editor's constraints are UX rather than security.
It drops any row it refuses instead of rejecting the whole host -- one bad
endpoint must not make a host unsaveable or unlistable.
Paths are checked for C0 controls and DEL by codepoint rather than by regex
character class: "\t//evil.example" defeats a startsWith("//") guard, because
the browser strips the control character and then follows the authority.
bindHost lands both in a TCP listener and in a URL authority, so it is
restricted to a bare host literal -- nothing carrying a scheme, port, path or
credentials survives. bindHost and localPort are ignored on a direct endpoint,
which never creates a forward.
parseWebUiConfig never throws. A malformed stored value yields an empty
endpoint list, so a half-written config cannot take out the whole host
listing -- which is what dockerConfig's bare JSON.parse does today.
The editor validator is a second implementation in a layer that cannot import
backend route modules. Its accompanying test runs BOTH implementations over
the same path and port samples, because drift here is invisible in the worst
way: the editor accepts a row, the normalizer silently drops it, and the
endpoint disappears on reload with no error. That agreement test was verified
to fail (4 cases) when the editor's control-character check is removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(db): migrate the shared_credential_secrets foreign key rename
Running `npm run schema:migrations` on an UNCHANGED schema emits these, so
they are pre-existing drift between schema.ts and drizzle/, not something this
branch introduced. Landing them alone, before the web-endpoint columns, so the
feature's own migration is reviewable without an unrelated table rebuild
sitting in the middle of it.
Both forms are a foreign-key constraint rename only. Postgres drops and
re-adds the constraint under a shorter name. SQLite has no ALTER for that, so
drizzle emits the standard table rebuild -- verified data-preserving: CREATE
lists 14 columns, INSERT...SELECT copies the same 14 in the same order, and
both indexes are recreated.
The SQLite file is in practice dead code: runRemoteMigrations throws for
sqlite, which builds its schema from db/index.ts instead. It is kept rather
than hand-trimmed because drizzle derives the meta snapshot from schema.ts
regardless, so trimming the .sql while the snapshot advances would make the
drift permanently invisible -- no future `generate` would re-emit it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(db): add enable_web_ui and web_ui_config columns
Two columns on ssh_data mirroring the Docker pair. No show_web_ui_in_sidebar:
every existing showXInSidebar column is vestigial -- they appear only in
defaults, the export payload and tests, and gate no rendering -- so a fourth
dead column plus a migration buys nothing.
web_ui_config stores an object rather than a bare array so host-level web
settings can be added later without a second migration.
The column has to be declared in FOUR places, not one. SQLite never runs the
drizzle migrations at all (runRemoteMigrations throws for sqlite), so the live
default-dialect schema comes from hand-written DDL:
1. db/schema.ts, then the two generated dialect schemas and the migrations
2. db/index.ts CREATE TABLE -- fresh databases
3. db/index.ts addColumnIfNotExists -- existing databases
4. database.ts CREATE TABLE and its positional INSERT -- encrypted export
Missing 2 or 3 breaks every host write on the default dialect while the
migration file sits there looking correct.
Two guards, both watched failing first. bootstrap-matches-schema-columns boots
a real database and compares PRAGMA table_info against the drizzle definition
for every table -- it reported ssh_data.enable_web_ui and ssh_data.web_ui_config
missing before item 2/3 landed. A grep-based guard could not: db/index.ts
names columns as snake_case strings, never the camelCase the schema uses.
export-ddl-matches-schema-columns covers database.ts, which no booted database
reaches. It caught a real defect while being written: the two new columns went
into the INSERT list but the VALUES list still held 53 placeholders for 55
columns. Being positional, that shifts every later value by one. The
placeholder-count assertion is now permanent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(hosts): thread web endpoint config through host read and write paths
A host field has to be enumerated by hand in nine places here, and "added a
column, missed one update path" is this codebase's most repeated bug -- during
the first attempt at this feature a field was missed at one of these points
six separate times, each surfacing as a different mystery: endpoints that
saved but never appeared, config that vanished on reload.
Backend: host-normalizers (input type, CONNECT_LEVEL_FIELDS, boolean
normalization, parse block), host.ts (create, update, both read paths,
quick-connect defaults), host-bulk-routes (bulk update, import, reset).
Renderer: host-export-payload, HostManagerData's sshHostToHost, tabUtils'
hostToSSHHost, HostEditorData's form seed and payload builder. The three
renderer mappers copy field by field, so an unlisted field is silently
dropped -- which is how endpoints saved correctly and never appeared.
webUiConfig is parsed with parseWebUiConfig rather than a bare JSON.parse.
dockerConfig on the adjacent line uses a bare one, so a single malformed value
takes out the whole host listing; that is a bug to avoid copying, not a
convention to follow.
All three write paths now clear webUiConfig when the feature is disabled. They
disagreed before, and the export payload ships the config unconditionally -- so
a host disabled without clearing still exported its endpoint list (internal
hostnames, ports, paths) while the UI read as off, and re-enabling resurrected
stale endpoints.
webUiConfig is shared with connect-level recipients, unlike dockerConfig. A
connect-level recipient is already authorized to open these tunnels, so
withholding the config only breaks discovery while the flag advertises the
feature. Docker's precedent does not transfer: its tab works without its
config, whereas a web endpoint IS its config.
The enumeration guard asserts every one of the nine files mentions the web
endpoint field beside its Docker counterpart, and checks the Docker anchor is
present too so it cannot pass vacuously if a file is restructured.
The clear-on-disable assertion was rewritten after the first version passed
with the guard deleted -- a loose regex matched unrelated sites. Both halves
are now anchored on exact text and were watched failing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(tunnel): report the real bound port, support idle close, reserve web:
Three changes the web endpoint open route depends on, plus the name
reservation that makes the scheme safe.
Real bound port. Under sourcePort 0 the kernel assigns the port, but the
runtime recorded the requested value -- so it advertised 0 and any caller
reusing it failed. It now records tcpServer.address().port.
Idle close, behind the new tunnelConfig.idleTimeoutMs. Web endpoint tunnels
are opened on demand and must not outlive their use. The timer lives inside
establishDirectTunnel because the socket set is only visible from that
closure, polls at min(30s, timeout) so "empty for N" means roughly that rather
than "empty at the instant of one N-spaced tick", and unrefs so it cannot hold
the process open. It calls cleanupTunnelResources rather than close(): close()
stops the listener but leaves the entry in activeTunnelRuntimes, so the open
route would keep handing out a port nothing is listening on.
Reserved names skip retry. handleDisconnect returns early for a "web:" name
after a forced cleanup. maxRetries: 0 would NOT achieve this -- the retry path
reads `maxRetries || 3`, so 0 falls through to 3. The cleanup is forced
because cleanupTunnelResources no-ops while tunnelConnecting holds the name,
and a web tunnel has no retry pass to self-heal a leaked runtime.
That early return carries an identity guard, which is the subtle one.
sourceClient.end() only STARTS an async teardown, so the SSH "close" event
lands after a reopen may already have registered a NEW runtime under the same
name. Acting by name alone would tear down the successor rather than the stale
tunnel -- surfacing as a 200 with a good port followed by a silent connection
reset and no error anywhere. handleDisconnect now takes the closing Client and
returns early when the registered runtime belongs to someone else. The idle
timer carries the same guard for the same reason.
/ssh/tunnel/connect now rejects a user-supplied "web:" name with 400.
validateTunnelConfig is no defence here: it returns true unconditionally for
any name that is not the legacy 6-part format, so an authenticated user could
otherwise create a real retry-configured tunnel that collides with a live web
endpoint forward and silently loses its own reconnect behaviour.
The manager tests run against a real TCP listener and real timers -- mixing
fake timers with real socket I/O is a known route to a hanging test. Both were
watched failing: reverting the bound port fails the port test, and swapping
cleanupTunnelResources for close() fails the idle test, which asserts the
ENTRY is gone rather than merely that the listener stopped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(tunnel): add the web endpoint open route
POST /ssh/tunnel/web-endpoint/open { hostId, endpointId } -> { port }.
The host is resolved through the user-scoped resolveHostById, so an
authenticated user cannot obtain a forward to a host id they do not own. The
endpoint is re-normalized out of storage rather than trusted: the stored value
predates any later tightening of the rules, and this is what a forward gets
built from. An enabled flag is required as well as a present endpoint -- a
bulk update that sends only { webUiConfig } can leave a configured endpoint on
a host whose UI reads as off everywhere.
Not gated to the desktop. The forward binds wherever this backend runs, as the
server tunnels feature does, and the endpoint's own bindHost decides whether
that is reachable from a browser.
endpointHost: "127.0.0.1" is load-bearing, not cosmetic. connectSSHTunnel
picks its strategy via shouldEstablishDirectTunnel -> isSingleHostTunnel, which
keys on endpointHost -- setting only targetHost selects a different path and
the forward never binds locally.
connectSSHTunnel never rejects, and its promise resolves right after
conn.connect() -- long before the SSH "ready" event populates
activeTunnelRuntimes. Awaited is not connected, so waitForTunnelSettled polls
the maps the manager already exports. Polling rather than adding a
promise-returning variant: every existing caller is fire-and-forget by design,
and changing that contract for one new caller is the larger blast radius.
A probe forwardOut runs before returning 200. Without it the route succeeds the
moment listen() does, and forwardOut is only attempted per inbound socket where
failure is swallowed -- so the likeliest real error, nothing listening on the
endpoint's port, reached the user as a blank frame and no message.
Staleness is validated at open, never on save: host save lives in the database
service and the runtimes live in the tunnel service, with no push between them.
On reuse the route compares a fingerprint covering the endpoint's target AND
the host's SSH identity, and reopens when it differs. An absent fingerprint
means reuse, not staleness -- a reserved-prefixed runtime can only have been
created here. Deletion, disabling and host removal then need no handling:
nothing reopens the tunnel, so it idles out.
Also closes an authorization gap this feature would otherwise have created.
/ssh/tunnel/disconnect and /ssh/tunnel/cancel checked ownership only inside
`if (config && config.sourceHostId)`, and web tunnels are deliberately absent
from tunnelConfigs -- so the check never ran for them. Host ids are small
sequential integers and endpoint ids are client-supplied, so the name is
guessable and any authenticated user could force-close another user's tunnel.
authorizeTunnelAction recovers the host id from the name and fails closed when
it cannot; the tests were watched failing against the old behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(hosts): add the web endpoint client and per-origin certificate allowance
The client posts a path relative to tunnelApi's base, which already includes
/ssh -- a leading "/ssh" here resolves to /ssh/ssh/... and 404s on every call.
The test asserts that on the captured runtime argument rather than by scanning
source, so quoting style and indirection cannot fool it.
Backend error messages are preserved rather than collapsed. 502 is this
route's likeliest real failure and carries the actionable cause -- SSH auth
rejected, host unreachable, nothing listening on the target port -- and
handleApiError would replace all three with "Server error occurred". It stays
as the fallback for bodies that carry no string reason, so a body shaped
{ error: <object> } cannot reach the user as "[object Object]".
openWebEndpointExternally applies the same refusal gate as the embedded tab.
Opening in the real browser is not the safer path: the cookie jar is the
browser's either way, so a tunnel URL on the page's own host string leaks the
session exactly as a frame would. The refusal happens before the forward is
opened -- binding the port and then declining to navigate would be no
protection.
Certificate allowance covers the one case Electron's existing handling misses:
direct access to a HOSTNAME over https. isPrivateNetworkHost matches IP
literals only and sshData.ip has no IP validation, so https://nas.local:8006
fails the check and renders blank inside an iframe with no click-through --
Chromium offers no proceed option for subframes.
Three properties of that allowance are deliberate and now pinned by tests,
because each would otherwise be undone by a reasonable-looking simplification:
- it is NOT wired into isInvalidCertificateAllowedForUrl, which also governs
this process's own outbound TLS via getTlsVerificationOptions;
- it is https-only, in both the check and the IPC handler, since a non-TLS
origin in a TLS-error allowlist is meaningless and file:/ftp: must never
be storable; and
- entries carry a five-minute TTL refreshed on each registration. Main has
no host-database access and cannot verify the renderer's claim that an
origin is a configured endpoint, so the residual risk has to be a
momentary window rather than one lasting until the app restarts.
The handler stores the parsed origin, never the caller's string, so a path or
wildcard cannot widen the allowance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(hosts): add the Web UI editor tab
Mounted as a Web UI sub-tab inside the SSH group, beside Docker, Tunnels,
Files and Host Metrics, and laid out as two SectionCards following the tunnels
editor: settings first, then the list it governs.
There is a deliberate asymmetry with the sidebar here. The sidebar entry will
appear on enableWebUi alone, because a direct endpoint needs no SSH -- but Web
UI is an SSH sub-tab, so a host with SSH disabled cannot configure endpoints at
all. That is the accepted behaviour and both halves are pinned by tests, so
neither gets "fixed" into agreement later.
tunnelAvailable is enableSsh && originIsLocal, resolved inside HostWebUiTab
rather than in HostEditor.tsx, which is already ~2600 lines. It is NOT gated on
isElectron(): the forward binds wherever the backend runs, exactly as the
server tunnels feature does. resolveConnectionOrigin is called with
connectionType "ssh" deliberately -- a tunnel endpoint always rides SSH, and
"ssh" avoids the guacamole special case that forces "remote" for
RDP/VNC/Telnet.
Three input details that each prevent a silent data loss:
- the id generator falls back off crypto.randomUUID, which is undefined
outside a secure context -- a plain-http web deployment is a first-class
target for direct+external and would otherwise throw on "Add endpoint";
- a new row gets a de-duplicated label, since labels are what identify an
endpoint in the sidebar picker; and
- the port field commits only a value the normalizer would keep. Number("")
is 0, which the normalizer rejects, so clearing the field would otherwise
write an endpoint that vanishes on save with no error.
The tunnel fields carry three warnings: a non-loopback bind exposes the
target's web UI unauthenticated to anyone who can reach the port; a loopback
bind on a remote backend cannot be reached from a browser at all; and a tunnel
reached at Termix's own hostname would hand the tunnelled service this
session. All three are shown while configuring rather than only when the tab
fails to load.
The unavailable-tunnel reason is shown whenever tunnelling is unavailable, not
only once a row asks for it -- a disabled dropdown option with no stated reason
reads as the control being broken.
Copy is reused verbatim from the previous iteration's reviewed strings rather
than reworded.
One test was written and then removed rather than kept: its name promised that
switching to tunnel access clears ignoreCert, but Radix Select needs pointer
APIs jsdom lacks, so the body only asserted the control existed. The clearing
is covered where it is enforced, in the normalizer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(hosts): open web endpoints from the sidebar and in a tab
One sidebar entry per host, never one per endpoint: a host may declare up to
16, and a row of 16 identical globes is unusable. With a single endpoint the
entry wears its label and acts directly; with several it carries no endpointId
and the click opens a picker -- a DropdownMenu in the tray, a DropdownMenuSub
in the Connect submenu.
The entry is gated on enableWebUi ALONE, unlike every neighbouring action,
which requires enableSsh. A direct endpoint needs no SSH; SSH matters only
per-endpoint, for tunnel access, which the open route enforces.
openTab gains a fourth optional parameter rather than overloading `restore`,
which means something else. What was actually broken for two endpoints on one
host was the LABEL -- tab ids are already `${name}-${type}-${Date.now()}`, so
they never collided. All existing call sites keep working.
The tab is passed the endpoint ID, not the endpoint object, so one deleted
while its tab is open renders a plain message instead of throwing.
Reload re-resolves and REMOUNTS. A direct endpoint's URL never changes and a
live tunnel returns the same port on every open, so setUrl(resolved) would be
a same-value setState that React bails out of, leaving the frame untouched --
Reload would silently do nothing in the two most common cases. A generation
counter folded into the iframe key fixes that and doubles as the staleness
guard for two resolutions landing out of order. The test captures the DOM node
before and after, and was watched failing against a url-only key.
PERSISTENT_TAB_TYPES is hoisted to module scope and exported so it can be
asserted on -- it was a local const inside the component and therefore
untestable. web-endpoint is deliberately excluded: an idle tunnel re-binds a
fresh kernel-assigned port, so a restored tab could never hold a valid URL.
Two notes on verification. `tsc -p tsconfig.json` passes vacuously in this
repo -- tsconfig.json is a solution file with project references and does
nothing without -b -- so `npm run type-check` (tsc -b --force) is the real
check; it caught two insertions that had landed in the wrong function. And the
one full-suite failure seen along the way was vault-signer-core, which passes
in isolation and on re-run, is untouched by this branch, and is a flake under
parallel load.
Lint: 0 errors, 102 warnings, identical to the baseline on a clean tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(tunnel): register the web endpoint route under its /ssh path
Every route in routes.ts registers the full "/ssh/tunnel/..." path -- nginx
proxies /ssh through with the path intact -- and the client resolves
"/tunnel/web-endpoint/open" against a baseURL that already ends in /ssh. The
unprefixed registration therefore 404'd every call.
No handler unit test could catch this: they call handleWebEndpointOpen
directly and never touch registration. It surfaced only on opening a real
tunnel against a deployed build, which is why that step exists.
Added a registration test that captures the paths passed to app.post, so the
invariant is checked without needing a server.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(hosts): apply session-cookie isolation to direct web endpoints
The cookie guard only covered tunnel access. `unreachableTunnelReason`
returned null for every `access: direct` endpoint, so WebEndpointTab framed
the host's own address unconditionally. With Termix served at
https://termix.example/ and a direct endpoint at https://termix.example:8443/,
the browser attaches Termix's `jwt` to the framed request -- cookies are keyed
by host and ignore the port -- and a `Set-Cookie` from that service lands in
the jar both auth middlewares read before the Authorization header.
Renames the helper to `webEndpointRefusalReason` so its scope is no longer
tunnel-only, and gives it the endpoint's target host. A direct endpoint is now
refused in a browser when that host shares Termix's cookie site, checked
BEFORE any navigation in both open paths (embedded tab and external window) --
refusing after framing would leak on the first request.
`sharesCookieSiteWithPage` treats an equal host as same-site (the jwt is set
host-only, so the port is irrelevant) and also a parent/sub domain, which a
`Set-Cookie: Domain=` from the target crosses. The suffix test is anchored on a
dot boundary so `eviltermix.example` does not pass as `termix.example`. A
precise eTLD+1 test would also catch sibling subdomains, but needs a public
suffix list this app does not bundle.
The desktop stays exempt: its session is Bearer-only with no `jwt` in the jar.
Also drops "or change this endpoint to Direct" from the tunnel refusal copy,
which pointed at the path that had this same leak.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
* fix(tunnel): preserve local status when remote names collide (#1431)
* fix(desktop): honor remote sync certificate setting (#1432)
* fix(file-manager): interpolate preview download filename (#1433)
* fix(automations): dispatch SSH login events to triggers (#1434)
* fix(hosts): preserve imported settings and remap jump hosts (#1435)
* fix(hosts): preserve terminal switches across JSON transfer
* fix(hosts): remap imported jump-host dependencies
* fix(ssh): preserve agent framing after unsupported extensions (#1436)
* fix(guacamole): return tokens before awaiting the client handshake (#1437)
* fix(sidebar): unmount the host tree while its panel is hidden (#1438)
* fix(terminal): leave macOS Option characters enabled by default (#1439)
* fix(dashboard): open the enabled remote desktop protocol (#1440)
* fix(version): preserve local version when update checks fail (#1441)
* fix(sftp): create uploaded directories without shell access (#1442)
* fix(desktop): honor explicit GPU acceleration opt-out (#1443)
* fix(desktop): exclude remote-only host IDs from local tab persistence (#1444)
* Enhance SFTP and terminal flows, stabilize desktop sync (#1377)
* feat: add electron sftp c2s and terminal ai flows
* fix: stabilize desktop sync and c2s tunnels
* Improve remote tunnels and SFTP transfer feedback
* chore: sync Crowdin translations
* chore(deps): bump the prod-minor-updates group across 1 directory with 3 updates (#1325)
Bumps the prod-minor-updates group with 3 updates in the / directory: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), [js-yaml](https://github.com/nodeca/js-yaml) and [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg).
Updates `@anthropic-ai/sdk` from 0.116.0 to 0.120.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-typescript/compare/sdk-v0.116.0...sdk-v0.120.0)
Updates `js-yaml` from 5.2.3 to 5.3.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.3...5.3.0)
Updates `pg` from 8.22.0 to 8.23.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.23.0/packages/pg)
---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
dependency-version: 0.120.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: js-yaml
dependency-version: 5.3.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: pg
dependency-version: 8.23.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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-minor-updates group across 1 directory with 10 updates (#1323)
Bumps the dev-minor-updates group with 10 updates in the / directory:
| Package | From | To |
| --- | --- | --- |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.4` | `6.11.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.1.2` | `26.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.5` | `6.1.0` |
| [electron](https://github.com/electron/electron) | `43.2.0` | `43.4.1` |
| [globals](https://github.com/sindresorhus/globals) | `17.9.0` | `17.11.0` |
| [i18next](https://github.com/i18next/i18next) | `26.3.6` | `26.4.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.28.0` | `1.33.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.84.0` | `7.85.0` |
| [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) | `10.4.1` | `10.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.66.0` | `8.67.0` |
Updates `@codemirror/commands` from 6.10.4 to 6.11.0
- [Changelog](https://github.com/codemirror/commands/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/commands/commits)
Updates `@types/node` from 26.1.2 to 26.2.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `@vitejs/plugin-react` from 6.0.5 to 6.1.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.1.0/packages/plugin-react)
Updates `electron` from 43.2.0 to 43.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v43.2.0...v43.4.1)
Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)
Updates `i18next` from 26.3.6 to 26.4.0
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v26.3.6...v26.4.0)
Updates `lucide-react` from 1.28.0 to 1.33.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.33.0/packages/lucide-react)
Updates `react-hook-form` from 7.84.0 to 7.85.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.84.0...v7.85.0)
Updates `react-pdf` from 10.4.1 to 10.5.0
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v10.5.0/packages/react-pdf)
Updates `typescript-eslint` from 8.66.0 to 8.67.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.67.0/packages/typescript-eslint)
---
updated-dependencies:
- dependency-name: "@codemirror/commands"
dependency-version: 6.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@types/node"
dependency-version: 26.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.1.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: electron
dependency-version: 43.4.1
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: globals
dependency-version: 17.11.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: i18next
dependency-version: 26.4.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: lucide-react
dependency-version: 1.33.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-hook-form
dependency-version: 7.85.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-pdf
dependency-version: 10.5.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.67.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: sync Crowdin translations
* Update redirect-issues workflow configuration
Updated the workflow to change the runner and modify the issue response message.
* test(sftp): verify local collection limit boundaries
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
* chore: increment ver
* fix: allow any origin by default when CORS_ALLOWED_ORIGINS is unset
* feat(file-manager): parallel local transfers (#1446)
Uploads from the Local pane (and downloads to it) ran strictly one file
after another. Batches of many small files were bound by per-file latency
rather than bandwidth.
- `runWithConcurrency(items, limit, worker, shouldStop)`: a bounded worker
pool that dispatches in order, keeps at most `limit` transfers in flight,
and stops dispatching once cancelled while letting in-flight items
finish. Worker errors are handled by the worker (one failure never aborts
the batch).
- useLocalTransfers: both directions go through the pool. Remote / local
directory skeletons are still created up front, so file order does not
matter. The batch runner now tracks a set of in-flight transfer ids
(Cancel cancels all of them), sums bytes across in-flight transfers plus
settled ones for the progress bar / speed, and shows how many files are
moving at once.
- New preference "Simultaneous File Transfers" (1-8, default 4) in the
profile settings, stored in localStorage
(`termix:file-manager:transfer-concurrency`) and part of the storage
snapshot. 1 restores the previous sequential behaviour; the hint suggests
lowering it for servers that limit SFTP channels.
- Remote directories are created with the now-idempotent mkdir (#1442)
without swallowing its errors, so a real failure stops the batch up
front instead of surfacing as N per-file failures. When files do fail,
the summary toast carries the first underlying reason (e.g. the
backend's 401 message) instead of a bare "Upload failed".
- Tests: pool never exceeds the limit and drains everything, stops on
cancel, tolerates a failing item, handles empty input; preference
clamping and persistence; bridge-level concurrent uploads/downloads
with a mid-flight cancel leave every other file intact and no partials.
Co-authored-by: Max <maxim@cogitate.ai>
* fix(sftp): complete streamed uploads and stop pre-parsing chunk bodies
* fix(desktop): stop setting Content-Length on local download requests
* fix(file-manager): keep drop overlays in view when the list is scrolled
* fix(file-manager): let remote rows be dropped on the local pane
* fix(desktop): authenticate local transfers with the renderer's token
* chore: update release notes
* chore: remove unused gitlab ci and argocd deploy configs
* chore: remove unused files
* feat: improve loading animation
* fix(shell): stop tab reorder from resetting terminals and tab indicator
Keep the tab-content portal order stable so reordering tabs doesn't
retrigger React's offscreen pass and dispose live terminals. Also stop
the active tab indicator from teleporting during a drag, and fix the
drag ghost's size/border mismatch.
* fix(shell): hand-roll the tab indicator instead of a shared layoutId
Framer's layoutId could measure the wrong tab's rect during a reorder
and land on an inactive tab, or leave a stray line visible mid-drag.
Now it reads the active tab's own DOM rect directly and hides while
dragging.
* fix(hosts): fix right-click menu position and unwanted tray expand
* chore: update release notes
* fix(terminal): stop autosuggestion ghost text overlapping typed input
Position was computed from cursorX before server echo landed, causing
a stale read that overlapped just-typed characters on fast local input.
* chore: update release notes for v2.8.0
* fix(sftp): rebuild transfer tab for web, fix menu offset bug
Drop the electron-only local mode and dead code, i18n all strings,
fix double progress toasts and a wrong pane refresh on move.
Fix context menus rendering offset due to a lingering CSS transform
from the tab-switch animation. Move SFTP next to SSH Tools in the rail.
* fix(file-manager): shrink oversized .. entry, navigate on single click
* fix: make select2 component use rounded corners
* fix: rebuild terminal AI assistant on the real backend
Removes the Electron-only Agent Mode/AI Command Helper that bypassed
admin/user gating and ran commands with no approval step. Replaces it
with a terminal-docked panel on the same gated backend as the AI tab,
adds a per-host toggle (off by default), moves it into the toolbar,
and fixes proposal ordering and run-in-terminal execution.
* fix(file-manager): remove duplicate borders in toolbar view toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: separate registration toggle from step ca settings
* fix: collab room bugs and add room deletion
Accent-colored new room button, fix fullscreen guest view layout and
duplicate view-only badges, self-heal stale presenter state, add
delete room for persistent rooms.
* refactor: move host-identity helpers to hosts core
Used by docker, file-manager, and terminal alike, so it belongs
at the hosts root instead of under terminal/.
* refactor: move host-session-status singleton to hosts core
metrics subscribed to terminal's private online/offline pub-sub
module. Moved to hosts core so both sides depend on a shared
module instead of metrics reaching into terminal. Marked the
subscribe call with PLUGIN-EVENT for the future event bus.
* refactor: split generic automation event notify out of metrics
notifyAutomationInternalEvent was in hosts/metrics/automation-bridge.ts
but is used by tunnel, host routes, user routes, and automations
engine itself, not just metrics. Moved it to hosts/automation-events.ts
so those callers depend on a shared module instead of metrics
internals. Left the metrics-specific notifiers in place.
* feat: add plugin system schema and repositories
Adds plugins, plugin_permission_grants, plugin_registries, and
plugin_install_counts tables plus their repositories. No routes or UI yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add runtime rail item and tab component registries for plugins
* feat: support runtime-registered permission groups
Adds register/unregister for plugin permission groups so the RBAC
catalog and validation can grow beyond the static list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add plugin-api nginx route and dispatcher stub
* feat: add plugin manifest schema and validator
* chore: update release notes
* chore: update i18n for local/remote file manager
* feat: add toggle to show/hide app rail pin button
* feat(host-metrics): add NVIDIA GPU metrics card (#1452)
* feat(host-metrics): add NVIDIA GPU metrics card
Collect per-GPU utilization, VRAM, temperature, power, fan speed and
the processes using each GPU over SSH with nvidia-smi. The collector
exits immediately when nvidia-smi is missing, so hosts without a GPU
pay almost nothing per poll.
The GPU card is opt-in: it is available from the Add card tray but is
not part of new-host defaults or any UI preset. It shows live
sparklines for utilization and VRAM per GPU.
Adaptive polling now also watches GPU readings and the GPU process
count, so it no longer backs off on hosts where only the GPU is busy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F2ctuPwiesLzhF6XQiozm8
* fix(host-metrics): draw sparklines from the first metrics sample
The first sample fetched when the tab connects was shown but never
added to the sparkline history, so every sparkline (CPU, memory, disk,
GPU) stayed empty until the second poll, a full metrics interval later
(about 35 s with the default 30 s interval). Record the first sample
like every later one, so the lines appear as soon as the tab loads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F2ctuPwiesLzhF6XQiozm8
* Fix logical condition for memory percentage calculation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: annotate local transfer plan type to fix CI type-check
* fix: move connection toolbar settings to bottom of host editor general tab
* chore: add debug logging for macOS sharp packaging failure
Temporary diagnostic to find which phase drops @img/sharp-darwin-x64
from the packaged app. Revert once root cause is found.
* fix: correct macOS sharp verify script for universal build layout
electron-builder uses mergeASARs: false, so universal builds ship
app-x64.asar.unpacked and app-arm64.asar.unpacked side by side instead
of a single app.asar.unpacked. The verify script only checked the
single-asar path, so it always failed on universal/mas artifacts even
when packaging was correct.
* fix: update sharp verify test fixtures for universal build layout
The test built fake apps using the single app.asar.unpacked layout,
which doesn't match how universal builds are actually packaged now
(app-x64.asar.unpacked / app-arm64.asar.unpacked side by side).
* chore: update readme
* fix: guard against missing rooms array in collab list response
* fix: distinct guac connection log stages, RDP black screen before failure
* fix: proxy /collab routes in nginx configs
* fix: stop tab-switch flash and host metrics/proxmox reconnect
Tab DOM visibility now syncs before paint instead of after, and
switching tabs no longer tears down live metrics connections.
* fix: tab bar flashing, growing, and merged separators
Terminal tabs no longer flash when switching, the enter animation no
longer nudges layout size, and a stray transform on resting tabs no
longer causes the border between tabs to vanish at some zoom levels.
* fix: match select trigger height to inputs in web UI editor
* chore: lint, format, and bump version to 2.8.0
* chore: sync Crowdin translations for 2.8.0
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: Angad Singh <7099405+singhangadin@users.noreply.github.com>
Co-authored-by: Angad Singh <angad@singhangad.in>
Co-authored-by: Neo <54811660+neooriginal@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Wali Lambert <98448225+YatoVoid@users.noreply.github.com>
Co-authored-by: Nasif Rahman <44437976+nasif-naseef@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: vietanhtwdk <vietanhtruongwdk@gmail.com>
Co-authored-by: inontz <inontz@icloud.com>
Co-authored-by: inontz <inontz@users.noreply.github.com>
Co-authored-by: Max Serov <133950708+maxser0v@users.noreply.github.com>
Co-authored-by: Max <maxim@cogitate.ai>
Co-authored-by: ssmurfgg04-gif <ssmurfgg04@gmail.com>
Co-authored-by: ssmurfgg04-gif <232103099+ssmurfgg04-gif@users.noreply.github.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
Co-authored-by: slsgzs-cloud <slsgzs@gmail.com>
Co-authored-by: anshtsolanki-sketch <anshtsolanki@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: tcezarl <tomescu.cezar.laurentiu@gmail.com>
Co-authored-by: Anton Priestley <anton@priestley.me>
Co-authored-by: Hakim M <hakimmarsudi@outlook.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Adel Alzubeir <14052842+Adelzu@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Add Helm and GitOps deployment setup
* fix: build better-sqlite3 from source in Docker (#1267)
* fix: preserve runtime SSL settings (#1268)
* fix: support forwarding from the memory SSH agent (#1269)
* fix: support forwarding from the memory agent
* style: format memory agent test
* fix: prompt for encrypted SFTP key passphrases (#1270)
* fix: prompt for SFTP key passphrases
* style: format SSH key utility test
* fix: include host context in automation notifications (#1271)
* fix: include host context in automation notifications
* style: format automation notification changes
* fix: reserve sidebar height for host tags (#1272)
* fix: keep host action rows stable at large font sizes (#1273)
* fix: honor certificate setting during server probe (#1274)
* fix: package standard Linux icon sizes (#1275)
* fix: avoid duplicate Docker HTTPS listener (#1276)
* Fix host status without metrics collection (#1277)
* fix: allow eight-digit secure auth codes (#1263)
Allow TOTP prompts to accept secure auth codes longer than six digits without blocking valid authentication attempts.
Generated with Codebuff 🤖
Co-authored-by: Chetan <chetan.development@gmail.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
* Harden Helm deployment defaults
* Update Helm workflow action
* Exclude Helm templates from Prettier
* Fix browser RDP file drops (#1279)
* Fix Proxmox guest credential usernames (#1280)
* Add WSL local terminal option (#1281)
* refactor: split the transfer engine into focused modules (#1282)
* refactor: extract SFTP promisify helpers into sftp-promisify module
* refactor: extract transfer timing and rate stats into transfer-stats module
* refactor: extract transfer error classes and recovery checks into transfer-errors module
* refactor: extract host/path utility helpers into transfer-host-utils module
* refactor: extract SFTP directory tree helpers into transfer-sftp-dir module
* refactor: extract segment copy job builder into transfer-segment-copy module
* refactor: extract file scan and sample helpers into transfer-scan module
* refactor: move throttled progress helper into transfer-stats module
* style: format transfer modules
* perf: optimize tmux monitor aggregation (#1283)
* fix: reserve credential tag row height (#1284)
* feat: edit AI provider model settings (#1285)
* fix: clarify click-to-expand host setting (#1286)
* fix: allow portable imports on remote databases (#1287)
* fix: allow HTTPS to share the configured port (#1288)
* fix: resolve synced jump hosts on the server (#1289)
* fix: make terminal clipboard shortcuts layout independent (#1290)
* fix: use compatible fetch dispatcher for Tailscale (#1291)
* fix: add OIDC environment recovery override (#1292)
* fix: coalesce rapid mobile terminal input (#1293)
* fix: coalesce rapid mobile terminal input
* fix: support clean xterm patch installs
* fix: resolve synced remote desktop host IDs (#1295)
* feat: make the SFTP file manager path bar editable (#1294)
Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* feat: add passkey sign in to the login screen
* fix: remove rounded corners from the host list search bar
* fix: stop image storage settings text wrapping to one word per line
* fix: prevent malformed websocket messages from crashing the server
* chore: increment version
* fix: remove gaps between host rows in the sidebar list
Keep sub-pixel row measurements and stop wiping the size cache on hover.
* fix: Failed to connect through jump hosts (#1180)
https://github.com/Termix-SSH/Support/issues/1180
* feat: Progress bar for file downloads in the file manager (#1158)
https://github.com/Termix-SSH/Support/issues/1158
* feat: Allow setting Silent OIDC Login via ENV var (#1174)
https://github.com/Termix-SSH/Support/issues/1174
* feat: `IdentityFile` to limit the number of attempts by agents (#1165)
https://github.com/Termix-SSH/Support/issues/1165
* feat: Credentials clone (#1159)
https://github.com/Termix-SSH/Support/issues/1159
* chore: update release notes
* docs: move helm setup guide to the docs site
* fix: type errors in FilteredAgent agent identity handling
* fix: remove stale better-sqlite3 prebuilds so the source build is used
* fix: actually build better-sqlite3 from source so arm64 docker images work
* fix: credential edit pencil in host editor and add clone action to credential list
* fix: clear editingHost so the credential pencil actually opens the editor
* chore: run format and lint
* fix: folder drag and drop upload failing in the file manager
* chore: sync Crowdin translations for 2.7.1
---------
Co-authored-by: alex-ctms <alex-ctms@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: Chetan Kumar <74929596+ckloop@users.noreply.github.com>
Co-authored-by: Chetan <chetan.development@gmail.com>
Co-authored-by: Codebuff <noreply@codebuff.com>
Co-authored-by: ZacharyZcR <payasonorahc@protonmail.com>
Co-authored-by: dropafterfree <maxime.bonillo@gmail.com>
Co-authored-by: Maxime Bonillo <257463937+dropafterfree@users.noreply.github.com>
* feat: redesign host/credential sidebars with synced preferences and manual drag-to-reorder
* chore: run format
* chore(deps-dev): bump @types/pg in the dev-patch-updates group (#1162)
Bumps the dev-patch-updates group with 1 update: [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg).
Updates `@types/pg` from 8.20.0 to 8.20.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)
---
updated-dependencies:
- dependency-name: "@types/pg"
dependency-version: 8.20.3
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-dev): bump the dev-minor-updates group with 4 updates (#1163)
Bumps the dev-minor-updates group with 4 updates: [react-hook-form](https://github.com/react-hook-form/react-hook-form), [react-icons](https://github.com/react-icons/react-icons), [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Updates `react-hook-form` from 7.79.0 to 7.84.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.79.0...v7.84.0)
Updates `react-icons` from 5.6.0 to 5.7.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.6.0...v5.7.0)
Updates `typescript-eslint` from 8.61.1 to 8.66.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/typescript-eslint)
Updates `vite` from 8.0.16 to 8.2.0
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.2.0/packages/vite)
---
updated-dependencies:
- dependency-name: react-hook-form
dependency-version: 7.84.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-icons
dependency-version: 5.7.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.66.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: vite
dependency-version: 8.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-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 prod-patch-updates group with 3 updates (#1164)
Bumps the prod-patch-updates group with 3 updates: [jose](https://github.com/panva/jose), [js-yaml](https://github.com/nodeca/js-yaml) and [nanoid](https://github.com/ai/nanoid).
Updates `jose` from 6.2.7 to 6.2.8
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.7...v6.2.8)
Updates `js-yaml` from 5.2.2 to 5.2.3
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.2...5.2.3)
Updates `nanoid` from 6.0.0 to 6.0.1
- [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/6.0.0...6.0.1)
---
updated-dependencies:
- dependency-name: jose
dependency-version: 6.2.8
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: js-yaml
dependency-version: 5.2.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: nanoid
dependency-version: 6.0.1
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 undici in the prod-minor-updates group (#1165)
Bumps the prod-minor-updates group with 1 update: [undici](https://github.com/nodejs/undici).
Updates `undici` from 8.9.0 to 8.10.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.9.0...v8.10.0)
---
updated-dependencies:
- dependency-name: undici
dependency-version: 8.10.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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 major-updates group with 2 updates (#1166)
Bumps the major-updates group with 2 updates: [@types/better-sqlite3](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/better-sqlite3) and [jsdom](https://github.com/jsdom/jsdom).
Updates `@types/better-sqlite3` from 7.6.13 to 9.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/better-sqlite3)
Updates `jsdom` from 29.1.1 to 30.0.1
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.1.1...v30.0.1)
---
updated-dependencies:
- dependency-name: "@types/better-sqlite3"
dependency-version: 9.6.0
dependency-type: direct:development
update-type: version-update:semver-major
dependency-group: major-updates
- dependency-name: jsdom
dependency-version: 30.0.1
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>
* fix: stop resyncAutoIncrement failing on tables without an id column (#1173)
The Postgres branch asked pg_get_serial_sequence(table, 'id') about every
table a fixture had inserted into. That function raises 42703 when the
column does not exist, rather than returning null, so any seed touching a
table keyed on something else took down the fixture.
host_sidebar_preferences is keyed on user_id and has no id at all, which
is why the Postgres job on dev-2.7.0 fails for every pull request.
Drive the lookup from information_schema so a missing id column yields no
row instead of an error. A text primary key still returns a null sequence
and is still skipped, as before.
* chore: install the git hooks that were already configured (#1174)
husky, lint-staged, commitlint and their config have been in the repo
since v1.8.0 (#429): .husky/pre-commit runs lint-staged, .husky/commit-msg
runs commitlint, the lint-staged globs are in package.json and the
commitlint rules in .commitlintrc.json.
None of it has ever run. husky only takes effect once it sets
core.hooksPath, and that happens in the prepare lifecycle script, which
the package did not define -- so every clone installed the tooling and
left the hooks unwired.
That is why formatting keeps failing in CI rather than locally: three of
the four open pull requests fail lint-and-build on prettier alone,
touching between one and five files each, and the check is the first place
anyone finds out.
prepare falls back to true so a checkout without a .git directory cannot
break installation. The Docker build passes --ignore-scripts, so it never
runs this at all.
Also pin the Prettier extension to the repo's own copy via
prettier.prettierPath, and let .vscode/settings.json out of .gitignore so
it applies to everyone. The extension bundles its own prettier otherwise,
which formats to a different version's rules than the one CI enforces.
* fix: derive the ssh_credentials rebuild from the live schema (#1172)
The startup rebuild that drops the old username NOT NULL constraint
restated the table's columns as a literal and then copied rows with
INSERT INTO temp SELECT <every live column>. The table has gained columns
since that literal was written — cert_public_key, pin, sort_order and
sync_id are all added by addColumnIfNotExists before the rebuild runs —
so the destination was narrower than the source. SQLite rejected the
INSERT on a column count mismatch, the error was swallowed as a warning,
and the constraint survived every restart.
Read the CREATE TABLE statement back from sqlite_master and rewrite just
the table name and the username constraint, so the replacement table
cannot fall behind the real one. Copy rows by explicit column name rather
than positionally, and replay the table's indexes, which DROP TABLE would
otherwise take with it along with the sync_id uniqueness.
* fix: make audit_logs.user_id nullable on fresh SQLite installs (#1171)
The audit trail is meant to outlive the account it belongs to: deleting a
user nulls user_id and keeps username for attribution. schema.ts, the
Drizzle migrations and AuditLogRepository.anonymizeByUserId were all
written against that, but the runtime bootstrap still created
user_id TEXT NOT NULL.
A second CREATE TABLE IF NOT EXISTS further down migrateSchema() had the
correct nullable column, but it can never run — the primary bootstrap has
already created the table, so IF NOT EXISTS is a no-op. Every fresh
install therefore got the old constraint, and user deletion failed with
"NOT NULL constraint failed: audit_logs.user_id" for any account that had
logged in at least once, via both the admin delete path and the OIDC
account-link cleanup.
Fix the primary bootstrap, and rebuild the table on existing databases
using the same pattern already used for ssh_credentials.username, since
SQLite cannot ALTER a column.
* fix: key the sync upsert on the row it just looked up (#1175)
A sync push locates the stored row twice -- once to decide insert vs
update, once to write it -- and the two lookups were spelled out
separately. Only the read knew about singleton entities; the write always
keyed on table.id.
userPreferences is the only singleton, and user_preferences is the one
synced table with no id column: its primary key is user_id. table.id was
therefore undefined, and drizzle emitted a comparison with nothing on its
left:
( = ? and "user_preferences"."user_id" = ?)
The insert branch was unaffected, so the first push of preferences
succeeded and every push after it -- the steady state -- failed with
SqliteError: near "=": syntax error. Preference sync never converged, and
both sides ship the same handler, so the desktop's embedded backend failed
identically.
Extract the lookup into locateSyncRow() and use it for the read, the
update and the tombstone delete, so the three cannot drift apart again.
The tombstone path already handled singletons correctly; it now shares the
one expression rather than keeping a third copy of it.
* fix: refuse an SSH connection whose host id resolves elsewhere (#1176)
A client identifies a host by the numeric row id of the database it is
displaying. With the desktop connection origin set to "Remote server",
that id is resolved against the sync server's ssh_data instead, and the
two autoincrement sequences need not line up -- they diverge as soon as
each side accumulates inserts and deletes in a different order.
resolveHostById() then returns whichever row owns that id here, and the
handler takes the address, the credentials, the jump hosts and the stored
host key from it. The session opens on a machine the user did not pick,
while the host list, host details and export all keep showing the right
one. Commands run on the wrong server, a host key mismatch is reported
for the wrong reason, and anything typed at the prompt goes to the wrong
place.
Compare the resolved address against the one the client sent, and refuse
when they disagree. Checking at the point the row is loaded covers every
use of it rather than each site separately. Addresses are compared with
brackets stripped and casing folded, so an IPv6 literal or a hostname
written differently is not treated as a different machine; when the
server has no address stored, the client's own details are used as
before.
This stops the wrong-machine session. It does not make delegated
connections work when the ids have drifted -- that needs the host to be
addressed by syncId across the boundary, which the connection protocol
does not currently carry.
* fix: refuse SFTP and Docker console on a mismatched host id too (#1177)
The wrong-machine guard added for SSH covered one of the paths that
resolve a client-supplied host id against this server's ssh_data. The
file manager and the Docker console take the same id from the same client
and dial whatever row owns it here.
The file manager then browses, edits and deletes files on that machine,
and the Docker console attaches to its daemon -- both while the UI shows
the host the user actually picked.
Reuse hostAddressMismatch at each point the row is loaded. The two file
manager sites sit inside "failed to resolve credentials, carry on"
handlers, so the refusal is a distinct error type those catches rethrow;
swallowing it would resume the connection this is meant to stop. The
Docker console reports over its socket, as it does for every other
refusal.
The user-facing wording now lives next to the check instead of being
written out at each site.
Still uncovered, and not fixable this way: file-manager's transfer
session, jump-host-chain and the proxmox routes resolve an id with no
client-supplied address to compare it against. Those need the host to be
addressed by syncId across the boundary.
* feat: address hosts by syncId when a connection is delegated (#1178)
A numeric host id belongs to the database that produced it. The desktop
app lists hosts from its embedded database and names them by row id, so
when a connection is delegated to a sync server that id is resolved
against a different table, whose autoincrement sequence has no reason to
agree. The row it lands on is a different machine, and it supplies the
address, the credentials, the jump hosts and the stored host key.
#1176 and #1177 made that refuse rather than connect. Refusing is right,
but it leaves "Remote server" unusable once the ids have drifted, which
is the state the reporter was in.
syncId already names a host identically on both sides -- remote sync
relies on it, ssh_data.sync_id is unique, and the API already returns it.
It just never reached the backend: hostToSSHHost() builds its result field
by field and dropped it.
Carry it through, and resolve with it when it is present:
resolveHostBySyncId(syncId, userId) // translate, then reuse
-> findHostIdBySyncId(syncId) // this database's own row id
-> resolveHostById(hostId, userId) // permissions, decryption, audit
The translation is deliberately not scoped to a user -- sync_id is unique
across the table and a shared host belongs to someone else -- so access
stays with the permission check in the id-based path, which the new tests
cover.
An unknown syncId resolves to nothing rather than falling back to the
numeric id: an unknown host is precisely where guessing picks the wrong
machine. Clients that send no syncId are unchanged, address comparison
included, so an older desktop keeps its safety net instead of breaking.
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: keep already-shared hosts sharing their SSH authentication (#1179)
Sharing a host used to hand the owner's SSH authentication to the
recipient unconditionally. 2.6.1 put that behind ssh_data.share_ssh_auth,
added as NOT NULL DEFAULT 0.
Existing rows took the default, so every host shared before the upgrade
stopped supplying credentials the moment the column appeared. The snapshot
in collectProtocolSnapshots() is guarded by host.shareSshAuth, so nothing
was captured; resolveRecipientSharedHostAuthentication() then fell through
to "required" and the recipient got "No valid authentication method
provided" on a host that had worked the day before. Downgrading to 2.6.0
restored it, since that code has no such column to consult.
Backfill the flag for hosts that already appear in host_access. That is
where the previous behaviour was in effect and where the owner had already
agreed to share; hosts nobody has shared keep the new default and stay off
until their owner shares them.
Guarded by a settings key so it runs once. Without that, an owner who
turns sharing back off would have it turned on again by the next restart.
* fix: let a single credential disable 2FA again (#1180)
The disable dialog has one field, labelled "Enter TOTP code or password",
and its caller passes that value as disableTOTP(input) -- so it arrives as
`password` with `totp_code` undefined. That call has been unchanged since
v2.3.0.
2.5.1 changed the route to require both:
if (!totp_code || (!userRecord.isOidc && !password)) -> 400
replacing `const credential = password || totp_code`. The first check has
rejected every attempt since, whatever the user typed, so nobody has been
able to turn 2FA off -- the client reports the generic "Failed to disable
2FA", which hides which check failed.
Take one credential again and try it as a TOTP code, a backup code, then
the account password. verifyTotpReauth still refuses the password itself,
so that comparison stays in the route; an OIDC user has no password hash
and reaches neither.
The backup-codes route has the same shape but no caller in the UI -- its
codes are returned when TOTP is enabled -- so it is left alone rather than
changed blind.
* fix: attach user-managed CA certificates over SFTP too (#1181)
opkssh-cert-auth.ts exports two helpers that end in the same
_applyCertToConnection: setupOPKSSHCertAuth, and setupCACertAuth for
user-managed CA-signed -cert.pub files. The file manager called the first
one twice and the second one never.
So a host whose key is paired with a CA-signed certificate authenticated
in a terminal and failed over SFTP, while OPKSSH certificates -- going
through the other helper -- worked in both. The file manager was not
missing certificate support in general; it was missing one of the two
paths into it.
The connection also never carried the certificate to begin with:
cert_public_key was not among the fields copied into resolvedCredentials,
so both places that build an SFTP connection now read it and attach it
where the private key is prepared -- the dedicated transfer session and
the main connect route.
An unusable certificate is logged and skipped rather than failing the
connection. The key alone may still be accepted, which is what happened
while this was not wired up at all, and turning that into a hard failure
would break setups that currently work.
Reported in #1160 with the call-site asymmetry already traced; the
reporter noted they could not confirm the link to their failure, having
moved off SSH CAs. The asymmetry is real either way and reproduces the
symptom exactly.
* fix: authenticate the desktop Docker console WebSocket (#1182)
The console WS opted out of the query token:
buildOriginWsUrl({ ..., includeLocalJwt: false })
leaving it with no credential at all on the desktop. The browser
WebSocket API cannot set an Authorization header, and while Electron's
main process injects a remembered JWT cookie, it requires an exact origin
match -- the cookie belongs to the API origin (localhost:30001) while the
console connects to 127.0.0.1:30009, so nothing is attached.
The backend then closes the handshake with 1008 before it logs anything,
which is why the log has no docker-console entries while stats and logs
polling keep succeeding on the same host. The web build is unaffected: it
connects same-origin and its cookie is sent normally.
Drop the opt-out so the console carries the local JWT like the SSH
terminal does -- the same token, the same query parameter, and the
backend already reads it there.
Guacamole passes includeLocalJwt: false too, but rdp/vnc/telnet always
resolve to "remote", so that call never reaches the local branch.
* fix: use getClientIp in getRequestMeta for correct audit-log IPs (#1183)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: use getClientIp in getRequestMeta for correct audit-log IPs
getRequestMeta had near-duplicate, strictly worse forwarded-header
logic: the array branch didn't split/trim, there was no socket-peer
fallback, and it returned "" instead of "unknown". Delegate to
getClientIp so the audit trail gets the same correctness as the
terminal login-alert path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add terminal image handoff (#1170)
* chore: sync Crowdin translations
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add terminal image handoff
Add authenticated browser upload and clipboard image handoff for terminal agents. Normalize images through Sharp, enforce storage and request limits, preserve host-visible paths, and provide a stable three-button terminal toolbar.
* docs: document terminal image handoff deployment
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(desktop): stop suppressing the update prompt, and make the version badge reachable (#1167)
* fix(desktop): stop suppressing the update prompt for users who need it
The startup update modal stored its dismissal under the local app version
rather than the remote version being offered, and the up-to-date branch
wrote that key with no user interaction at all. A user who launched while
current had their own version recorded; once the next release shipped,
`dismissedVersion === currentVersion` still held and the modal was skipped
on every launch. It reappeared only after the user had already updated --
the inverse of what it is for. Present since v2.3.0.
Key the dismissal on the offered remote version instead. The change is
backward compatible: an existing key holding 2.6.0 compares unequal against
a remote 2.6.1, so affected installs are prompted on their next launch. When
the check itself fails there is no remote version, so nothing is recorded and
no future prompt is suppressed.
That left the version badge as the only remaining signal, and it was an inert
span on both surfaces that render it -- the profile panel and the dashboard
stats bar -- even though the `getVersionInfo()` response it is built from
already carries `latest_release.html_url`. Extract the duplicated badge into
`components/version-badge.tsx` and make the update case a link to the release,
with an accessible name that says where it goes. The beta and stable cases
stay inert.
`getVersionInfo()` returned `Record<string, unknown>`, so the release URL was
unreachable without a cast; give it a `VersionInfo` type that keeps an index
signature, since `SystemOverviewWidget` reads `updateAvailable` off the same
response.
* test: cover the read that actually reaches the badge
The extracted VersionBadge is unit-tested, but the line that decides whether
it ever receives a URL -- pulling `latest_release.html_url` out of the version
response -- was duplicated at both call sites and asserted nowhere. A wrong
property there compiles (the response type keeps an index signature) and every
existing test still passes.
Give it a name, `releaseUrlFrom`, use it from both surfaces, and test it: the
happy path, a response with no release, a release with no URL, and a missing
response, since the caller's fetch can reject. Empty string is the contract the
badge reads as "nothing to link to", so it stays an inert span rather than
rendering a dead anchor.
* docs: state the index signature's real reason
The comment claimed the version endpoint carries fields beyond the typed ones,
citing `updateAvailable`. It does not -- `GET /version` returns status,
localVersion, version, remoteVersion, latest_release, cached and cache_age, and
nothing else. SystemOverviewWidget reads `updateAvailable` off it regardless,
which is why the permissive index signature has to stay, but that is a stale
read rather than an undocumented field. Say so accurately.
* Send alerts in Discord channels with Webhooks (#1158)
* feat(utils): add discord webhook sender
Add a utility to send alert embeds to Discord webhooks.
* fix(utils): validate DNS and use global fetch for outbound requests
Prevent private destination access and rely on global fetch after DNS validation.
* chore(logger): include extra context in logs
Show additional sanitized context entries for clearer diagnostics.
* feat(alerts): support discord channel type in routes and engine
Accept discord channels and route alerts to the Discord sender.
* feat(ui): add Discord option to notification channel dialog
Allow creating/editing Discord webhook channels with username/avatar.
* fix(ui/api): accept structured config payload for notification channels
Allow the client to pass structured config objects (or strings) when creating/updating channels.
* chore: sync Crowdin translations
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* chore: add url to SENSITIVE_FIELDS for discord url
* fix: enforce SSRF protection on outbound fetches
Use `undici.fetch` with the custom DNS lookup hook to ensure the validated
DNS resolution is the one used for the connection. Fix DNS lookup/address
validation bugs and add coverage for private, public and invalid addresses,
including the resolution issue affecting Discord endpoints.
* chore: prettier format
* fix: validate all DNS addresses and close dispatcher
* fix DNS lookup validation and callback handling
* update safe outbound fetch tests
* ensure created dispatcher is properly closed
* chore: remode url from SENSITIVE_FIELDS for other logs
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix tmux UTF-8 path handling (#1157)
Co-authored-by: Carl <scarlettme@qq.com>
* chore: update package lock
* chore: update gitnore
* fix: [BUG] (#1049)
https://github.com/Termix-SSH/Support/issues/1049
* fix: test commitlint path fix (#1021)
* fix: SGR mouse-tracking escape codes printed as text (#1023)
* fix: quote $1 in commit-msg hook so it works from git worktrees
* fix: [BUG] could not connect to the database (#1057)
https://github.com/Termix-SSH/Support/issues/1057
* fix: [BUG] VNC connect macOS screen sharing failed (#1063)
https://github.com/Termix-SSH/Support/issues/1063
* fix: [BUG] Meta key (#1075)
https://github.com/Termix-SSH/Support/issues/1075
* fix: [BUG] Remote sync doesn't work with Termix behind nginx proxy (#1085)
https://github.com/Termix-SSH/Support/issues/1085
* fix: [BUG] webhook not working (#1080)
https://github.com/Termix-SSH/Support/issues/1080
* fix: [BUG] First server sync doesn't refresh UI (#1084)
https://github.com/Termix-SSH/Support/issues/1084
* fix: [BUG] How to enable SSL using custom certificate (#1083)
https://github.com/Termix-SSH/Support/issues/1083
* fix: [BUG] Sudo Password Auto-fill Persistance (#1098)
https://github.com/Termix-SSH/Support/issues/1098
* feat: [FEATURE] Expand Snippets Function (#1031)
https://github.com/Termix-SSH/Support/issues/1031
* feat: [FEATURE] (#1055)
https://github.com/Termix-SSH/Support/issues/1055
* feat: [FEATURE] Support for Headscale API Keys (hskey prefix) and Custom API Endpoints (#1013)
https://github.com/Termix-SSH/Support/issues/1013
* feat: [FEATURE] Allow paste on non https (#1026)
https://github.com/Termix-SSH/Support/issues/1026
* feat: be-azerty layout (#1073)
https://github.com/Termix-SSH/Support/issues/1073
* feat: Keyboard shortcuts to move between open tabs (#1069)
https://github.com/Termix-SSH/Support/issues/1069
* feat: Session Logs as a downloadable text file (#1058)
https://github.com/Termix-SSH/Support/issues/1058
* fix: persist and auto-fill saved SSH and sudo passwords
* fix: persist docker runtime selection and docker manager UI issues
* feat: Allow excluding specific mounts from disk usage metrics (#1046)
https://github.com/Termix-SSH/Support/issues/1046
* feat: Expand Snippets Function (#1031)
https://github.com/Termix-SSH/Support/issues/1031
* chore: restore the prettier baseline on dev-2.7.0 (#1185)
Five files on dev-2.7.0 do not match prettier, so `npx prettier --check .`
fails and takes lint-and-build with it — on every pull request, whatever
it changes.
Formatting only, produced by `npx prettier --write` on exactly the files
the check names. No logic touched: tsc passes for both configs, backend
148 files / 1106 tests and UI 71 files / 479 tests all pass.
* test: keep the tmux escaping test runnable on Windows (#1184)
The escaping check ran its command through /bin/sh. That binary does not
exist on Windows, and Windows is a supported platform for the desktop
app, so `npm test` fails there on a test about string quoting. CI is
ubuntu-only and would never see it.
Assert the escaped string directly, which covers the rule on every
platform, and keep the round trip through a real shell as a separate case
guarded by platform -- it is the stronger evidence where a shell exists.
* chore: drop the unreachable table probes from migrateSchema (#1186)
Eleven blocks in migrateSchema() guarded a CREATE TABLE IF NOT EXISTS
behind SELECT id FROM <table> LIMIT 1, for tables the primary bootstrap
had already created earlier in the same startup. The probe could not
throw, so the catch never ran.
Two of those unreachable copies had drifted from the definition actually
in use. sessions had lost ON DELETE CASCADE, and session_recordings still
carried user_id TEXT NOT NULL with ON DELETE CASCADE and no username --
the shape from before audit trails were made to outlive the account. They
would have taken effect had anything ever reordered startup.
Kept, because they are not the same thing:
- blocks whose catch runs ALTER TABLE ADD COLUMN. CREATE TABLE IF NOT
EXISTS is a no-op on a table that exists, so a database created before
a column was added still needs the ALTER. Those probe a column, not a
table.
- blocks that are a table's only creation point.
- the user_open_tabs block, which is a data migration; its SELECT is a
precondition, not a probe.
Deletion only, no behaviour change.
* fix: repair the frontend type-check and clear the 299 errors behind it (#1189)
* fix: repair frontend type-check configuration and the errors it exposed
The root tsconfig.json is solution-style with "files": [], so the
`npx tsc --noEmit` that CI runs compiles nothing at all. Frontend types
have therefore never been checked, and 299 errors had accumulated behind
that no-op. This clears just over half of them; nothing here changes
runtime behaviour.
Configuration:
- "@/types" resolved through the "@/*" fallback to src/ui/types, which
does not exist. Added an explicit mapping to src/types/index.ts.
- src/vite-env.d.ts sits outside the include list, so import.meta.env and
the ?url import suffix were unknown. Added.
- src/ui/types/ held a single file, keybindings.ts, while every other
shared type lives in src/types/. Six modules imported it as
"@/types/keybindings" and silently resolved to nothing. Moved.
Type definitions that had fallen behind the code:
- guacamoleConfig and terminalConfig were Record<string, unknown> in
ui-types while the editor read concrete fields off them. Both now use
the real interfaces; GuacamoleConfig is extracted from its inline
definition in guacamole-api.ts so the two cannot drift again.
- customThemeColors and TerminalTheme["colors"] described the same object
with different optionality. Aligned.
- FileWindow declared its own SSHHost whose authType was "password" | "key",
which no longer matches the eight the app supports.
- connectSSH and listSSHFiles returned Record<string, unknown>, so every
field the callers destructured arrived as unknown.
- AxiosRequestConfig and AxiosResponse were used without being imported.
Also adds asHttpError() for the handful of catch blocks that reached into
an unknown binding, and narrows the Host | HostFolder comparator and the
RailItem union at the points where the discriminant was not carrying.
Note: dbHealthMonitor.reportDatabaseError was being called with a second
argument it does not accept, so the authenticated-or-not flag was already
being discarded at runtime. Dropped the argument to match the signature;
whether that flag was meant to gate the report is worth a separate look.
* fix: clear more of the frontend type-check baseline
Continues the previous commit; 140 errors down to 70. Three of these were
real defects rather than missing annotations.
Defects:
- DashboardTab counted active tunnels by comparing status to "CONNECTED",
but CONNECTION_STATES.CONNECTED is "connected" and that is what the
tunnel manager emits, so the count was always zero. Now compares against
the constant.
- QuickActionsCard requires isAdmin and gates a block of admin-only actions
on it, but neither call site passed it — those actions never rendered.
Both call sites also passed isAdmin to HostStatusCard, which does not
accept it; the prop had evidently been moved and the call sites missed.
- The host editor stores jump host ids as strings and sent them straight to
an API typed for numbers. Backend host lookups compare against an integer
column, which a string does not match on Postgres or MySQL. Converted.
Types brought in line with the data:
- Host and HostData were missing hasPassword, hasSudoPassword, sortOrder,
instanceId, connectionOrigin, vaultProfileId, syncId, and the "vault"
authType; TabContextTab was missing the "tunnel" tab, which TabContext
already branched on.
- statsConfig and terminalConfig used inline shapes that had drifted from
StatsConfig and TerminalConfig. Both now reference the real interfaces;
excludedMounts, which the editor reads, was added to StatsConfig.
- downloadSSHFile, generateKeyPair and generatePublicKeyFromPrivate all
returned Record<string, unknown> while callers read named fields.
- The Guacamole declarations were missing Keyboard.reset, Client.onfile,
InputStream.sendAck, Status.Code and BlobReader, all already in use.
- NetworkTopologyNode/Edge could not be discriminated, though the graph
code tells them apart by testing for source/target.
ProxyNode.type is now 4 | 5 | "http" | "socks4" | "socks5". The editor
writes the string spellings while proxy-helper.ts tests for "http" and
casts everything else to 4|5 before handing it to the socks client, so a
chained proxy reaches it as "socks5" rather than 5. Typed as what is
actually stored; reconciling the two spellings needs a migration decision
and is left alone here.
* fix: continue clearing the frontend type-check baseline
70 errors down to 44.
Dead configuration removed:
- Terminal set terminal.options.bellStyle on xterm, which dropped the
option in v5. The host editor still exposes the setting and stores it;
it has simply had no effect on the terminal since that upgrade. Making
the bell work again means handling the onBell event and is left alone.
- CodeEditor passed scrollPastEnd to basicSetup, which has no such option.
- FileManager passed an id to openWindow, which assigns its own and
discards what it is given — the component was already being rendered
under a different id than the one the caller held.
Widgets that were registered but unreachable:
- DockerActivityWidget and SshQuickConnectWidget register under
"docker_activity" and "ssh_quick_connect", neither of which was in
WidgetTypeId, and both referenced config interfaces that did not exist.
Added the ids and the two interfaces, inferred from their edit forms and
defaultConfig.
More endpoints given their real return types: getRecentFiles,
getPinnedFiles, getFolderShortcuts (arrays, not records), downloadSSHFile,
copySSHItem, generateKeyPair, generatePublicKeyFromPrivate and getSnippets.
parseGuacamoleConfig() handles the host row carrying guacamoleConfig either
parsed or as raw JSON, which GuacamoleApp was reading fields off directly.
TerminalHostConfig was missing name, which it reads for the activity log.
* fix: continue clearing the frontend type-check baseline
44 errors down to 17.
Host and AuditLog are now type aliases rather than interfaces. An
interface has no implicit index signature, so neither could be assigned
to the `[key: string]: unknown` shapes that TerminalHostConfig,
HostMetricsTab's HostConfig and several helpers declare — eight errors
came from that alone.
More dead configuration:
- i18n passed checkWhitelist to the language detector, which no longer
has that option; supportedLngs already covers it.
- SSHAuthDialog passed scrollPastEnd to basicSetup, same as CodeEditor.
- AudioPreview's onLoadedMetadata never fired: react-h5-audio-player
spells the prop onLoadedMetaData.
- MarkdownRenderer destructured `inline` from code(), which react-markdown
removed in v9, so the flag was always undefined and every inline span
took the block branch when it happened to carry a language class. Now
derived from whether a className is present at all.
- SnippetsPanel put a title prop on a lucide icon, which does not forward
it; changed to aria-label so the hint is actually reachable.
updateHostConfig in TabContext replaced tab.hostConfig wholesale with the
six-field literal it receives, dropping everything else the tab held about
the host. It now merges onto the existing config.
Also: getReleasesRSS, getUserAlerts and getVersionInfo have real return
types (UpdateLog kept private copies of two of them, and VersionInfo was
missing `version`, which the endpoint sends and the panel renders);
wakeOnLan and vncCredentialId get the numeric ids they are typed for; and
the tmux formatter takes i18next's TFunction instead of a hand-written
signature it does not satisfy.
* fix: clear the last frontend type errors and make CI actually run the check
Baseline is now zero, so the check can be turned on.
`npx tsc --noEmit` — what CI ran and what `npm run type-check` was — compiles
nothing: the root tsconfig.json is solution-style with "files": [], and
plain tsc does not follow project references. Both are now `tsc -b`, which
builds tsconfig.app.json and tsconfig.node.json. Verified by planting a type
error and watching the command fail.
Last defects in this batch:
- patchOpenTab could not carry hostId, so quick-connect's "save this host
and attach the tab to it" call was passing a field excluded from the
type all the way down. The column exists and updateForUser spreads
whatever it receives, so the write worked; only the types disagreed.
Widened front to back.
- The file-comparison window opened without x, y, width or height — every
other openWindow call passes them — and sent a `type` field WindowInstance
does not have.
- HostEditor gated a block on authType === "warpgate", which is not one of
the eight authType values. Unreachable, and it held only a label and a
description. Removed.
- FileManager passed onLoadDirectory to a sidebar that neither declares nor
reads it, and FileManagerApp passed embedded to a FileManager that has no
such prop.
- TunnelApp's minimal Host was missing three required flags.
The remainder were assertions at boundaries that are genuinely loose: bulk
host import takes rows assembled from untyped input and validates them
server-side, and a vi.fn() whose body only throws infers never.
* feat: add drive file browser and drag-and-drop upload for RDP (#1187)
Drive redirection could already be enabled per host, but the redirected
drive lived inside guacd with no way to reach it from the browser: the
client never handled onfilesystem, so the mounted volume was writable
from Windows and invisible from Termix.
Add a file browser panel that lists the drive, downloads files, and
uploads them, plus drag-and-drop onto the display which opens the panel
and uploads into the directory currently shown. The disable-upload and
disable-download connection settings are honoured by the UI, not just
passed to guacd.
A rejected upload stops the BlobWriter without firing onerror or
oncomplete, so the error ack is watched explicitly; otherwise the
transfer would hang forever. Directory reads carry a deadline for the
same reason.
Also declares Guacamole.Object, Client.onfilesystem, BlobReader and
BlobWriter in the local type definitions, which previously omitted them.
* fix: keep the mouse working on touch-capable devices in RDP/VNC (#1190)
Reported as "mouse input broken, keyboard fine" after 2.5.1 (#1102).
2.5.1 bound Guacamole.Mouse unconditionally. 2.6.0 replaced that with a
three-way branch on touchMode, and the touch branches replace the mouse
binding instead of adding to it:
if (touchMode === "touchscreen") new Guacamole.Mouse.Touchscreen(el)
else if (touchMode === "touchpad") new Guacamole.Mouse.Touchpad(el)
else new Guacamole.Mouse(el)
The two do not overlap. Guacamole.Mouse listens for mousedown/mousemove/
mouseup; Touchscreen and Touchpad listen only for touchstart/touchmove/
touchend. So in a touch mode nothing is listening for the mouse at all.
touchMode defaults to "touchscreen" whenever navigator.maxTouchPoints > 0,
which is true of every laptop with a touchscreen — machines that are still
driven by a mouse. Those users lost the pointer entirely while the keyboard
kept working, because Guacamole.Keyboard is bound independently.
The physical pointer is now always bound and a touch emulator is layered on
top when one is selected. Extracted to bindPointerInput() so the binding is
testable; the test fails against the old branch.
Note the issue also carries a second, unrelated report where well-formed
mouse frames do reach guacd and the VNC leg ignores them. That one is not
this, and the guacd image is pinned to 1.6.0 in both 2.5.1 and 2.6.1, so it
is not an upgrade either.
* fix: deduplicate /api/folders requests to prevent intermittent folder disappearance (#1191)
* chore: sync Crowdin translations
* fix: deduplicate /api/folders requests to prevent intermittent folder disappearance
getSSHFolders() had no request deduplication while getSSHHosts() used a TTL
cache with in-flight dedupe. When loadHosts() fired multiple times during
rapid navigation between Credentials and Hosts panels, the folder response
could arrive after the hosts response, causing the sidebar tree to render
without folder metadata.
- Add foldersCache (10s TTL) in hosts-request-cache.ts
- Wrap getSSHFolders() API call in getCachedSSHFolders()
- Invalidate folders cache on renameFolder, updateFolderMetadata,
deleteAllHostsInFolder, and renameCredentialFolder
- Include foldersCache in invalidateHostsAndStatusCaches()
ClosesTermix-SSH/Support#1103
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
---------
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
* chore(deps): bump undici from 8.9.0 to 8.10.0 in the prod-minor-updates group (#1195)
* chore: sync Crowdin translations
* chore(deps): bump undici in the prod-minor-updates group
Bumps the prod-minor-updates group with 1 update: [undici](https://github.com/nodejs/undici).
Updates `undici` from 8.9.0 to 8.10.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.9.0...v8.10.0)
---
updated-dependencies:
- dependency-name: undici
dependency-version: 8.10.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: proxmox metrics integration
* feat: add folder select to the host multi select feature
* feat: implement context aware terminal toolbar with quick links, host info, image pasting, etc
* feat: made toolbar open file manager at path
* fix: delete folder route not invalidating host list cache
* fix: match host list icons with tab bar iconfix
* fix: change sidebar reset button icon to seperate against fullscreen button
* feat: unify connection system and add connection logs to guacd hosts
* fix: make mobile terminal scrollback match xterm wheel behavior (#1198)
* fix: route mobile terminal scrolling through xterm viewport
* docs: document mobile terminal touch scrolling
* chore: add a note to not place files in docs
* chore: remove touch imput from docs
* feat: improve snippet system with variable snippets and collapse settings
* feat: new fleet system with snippet, packages, files, and inventory features
* fix: command pallete not loading new activity and made enter load first item
* feat: add subhost from parent host organization feature
* feat: add workspaces feature to save tab layout
* perf: greatly improved performance across metrics polling and host management for enterprise users
* feat: add a onboarding system with a new interface simplicity system
* feat: finalize the multi dialect database system
* fix: bind trusted MFA devices to client installs (#1202)
* fix: merge OIDC group claims across sources (#1203)
* fix: allow disabling SSH keepalives (#1204)
* fix: distinguish reachable and available hosts (#1206)
* fix: throttle session activity persistence (#1207)
* fix: preserve saved RDP connection settings (#1208)
* fix: authenticate tunnel status stream (#1209)
* fix: select quick-created credentials (#1210)
* fix: stagger initial metrics collection (#1211)
* fix: stagger initial metrics collection
* fix: admit reachable hosts to initial metrics
* fix: prevent long host names shifting dashboard metrics (#1205)
* feat: add global touch input settings (#1201)
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: keep host list row sizing stable (#1213)
* fix(guacamole): correct Windows key mapping (#1216)
* fix: normalize OIDC discovery issuer URLs (#1218)
* fix: prompt for RDP domain credentials (#1212)
* fix: route status checks by connection origin (#1214)
* fix: restore desktop Tailscale configuration (#1215)
* fix(docker): restore Node 24 for ssh2 native crypto (#1217)
* feat: added new automations feature with events, channels, and steps
* feat: allowed some tabs in the app rail to be opened as its own tab or in a new right sidebar
* feat: expand onboarding process with more customization/features
* feat: initial implementation of the termix ai feature
* chore: run linter
* fix: issue #424 (#424)
https://github.com/Termix-SSH/Support/issues/424
* fix: Not working without internet connection. Missing OPKSSH binary in pre-built image. (#1133)
https://github.com/Termix-SSH/Support/issues/1133
* fix: SQLite forceSave on telemetry writes causes periodic SSH terminal stalls in 2.6.x (#1109)
https://github.com/Termix-SSH/Support/issues/1109
* feat: How to enable SSL using custom certificate (#1083)
https://github.com/Termix-SSH/Support/issues/1083
* fix: show profile API key after creation (#1221)
* feat: add trusted proxy authentication (#1222)
* fix: clarify SSH agent authentication (#1224)
* feat: add first-class split screen tabs (#1226)
* feat: add split tab data model
* feat: make split screens top-level tabs
* feat: persist and manage split layouts
* feat: launch native RDP on Windows desktop (#1223)
* feat: launch native RDP on Windows
* style: format native RDP launcher
* feat: enhance custom disk and network metrics (#1220)
* feat: enhance host disk and network metrics
* fix: align enhanced metrics types
* fix: preserve Proxmox guest identity on edit (#1219)
* fix: preserve Proxmox guest identity on edit
* fix: type Proxmox guest source metadata
* chore: dead-code cleanup and small refactors (#1225)
* chore: remove dead code and unused exports
* chore: remove unused api client functions
* chore: remove unused backend helpers
* refactor: extract getErrorMessage helper for repeated error extraction
* refactor: unify error message extraction across backend with getErrorMessage
* refactor: unify error message extraction in frontend with getErrorMessage
* refactor: merge duplicate imports from the same module
* refactor: use Array.includes in TabBar
* chore: drop biome, keep prettier as the single formatter
* style: apply prettier formatting to refactored files
* fix: close active tab with Ctrl+W on Windows
* fix: make tray Quit terminate the desktop app
* feat: verify host transfer integrity
* fix: reuse transfer sessions during verification
* feat: select the fastest host transfer route
* feat: tune host transfers adaptively
* feat: adapt background polling to activity (#1233)
* feat: adapt background polling to activity
* feat: extend adaptive polling coverage
* feat: make polling cost and network aware (#1234)
* feat: make repeat navigation feel instant (#1235)
* feat: make file operations feel immediate (#1236)
* feat: preload likely user actions (#1237)
* feat: preload likely file previews
* feat: preload likely host tools
* feat: preload likely file viewers
* fix: replace stale terminal input listeners
* feat: add links to docs for all new features
* chore: update readme
* fix: warn before discarding host changes (#1229)
* feat: learn local host action preferences (#1238)
* feat(terminal-toolbar): add bounded movable desktop toolbar (#1239)
* feat: add local adaptive decision engine (#1240)
* feat: adapt speculative resource usage (#1241)
* feat: persist adaptive transfer profiles (#1242)
* Fix .preferred_username when using LDAP login. (#1243)
* chore: sync Crowdin translations
* Fix .preferred_username when using LDAP login. Strips internal LDAP prefix from username.
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
* feat: learn direct transfer routes (#1244)
* feat: learn speculative preload usefulness (#1245)
* fix: - Adjusting the SSH Authentication from Vault to something else fails (#1152)
https://github.com/Termix-SSH/Support/issues/1152
* fix: terminal graphical display, special characters inserted, distorted - `midnight comma... (#1145)
https://github.com/Termix-SSH/Support/issues/1145
* feat: single click on host in list opens session - should be only on double click (#1146)
https://github.com/Termix-SSH/Support/issues/1146
* feat: Terminal: custom font/ font selection/ how-to for adding a font - `MesloLGS NF` (#1140)
https://github.com/Termix-SSH/Support/issues/1140
* fix: revert host single click to open session, make double click an option (#1146)
Single click opens a session again by default. The old double click
behavior can be turned on in Customize Sidebar.
* chore: drop prettier check from beta release workflow, run formatter
* chore: patch dependabot vulnerabilities via npm overrides
* fix: reset adaptive resource state between tests to stop cross-test leaks
* feat: replace terminal toolbar density popover with a native select
* fix: pin hardwareConcurrency in adaptive budget tests so CI cores don't change the tier
* fix: allow dylib files in mac universal arch rules so mas build packages sharp
* feat: add file manager trash (#1250)
* feat: add inheritable connection defaults (#1246)
* feat: add desktop local terminal (#1247)
* feat: add interactive terminal macros (#1248)
* feat: add adaptive SSH local echo (#1249)
* fix: sync desktop host changes immediately (#1252)
* fix: route desktop sharing through synced server (#1253)
* Fix terminal image uploads and add safe diagnostics (#1254)
* feat: add configurable terminal image storage backends
* feat: add admin image storage settings
* fix: preserve native clipboard PNG uploads
* fix: quote terminal image paths safely
* docs: record image storage security remediation plan
* fix: close remote image SFTP channels
* fix: restrict remote image SFTP permissions
* fix: bound remote image SFTP writes
* fix: add best effort remote image retention
* fix: cap normalized image output size
* fix: bound concurrent image processing
* fix: fail closed on local image inspection errors
* test: cover fail closed image storage and atomic settings
* fix: enforce remote image quota and upload admission
* fix: serialize remote quota and verify existing paths
* fix: use synchronous sqlite settings transaction
* fix: keep settings transactions portable across dialects
* fix: bound image processing admission queue
* fix: serialize remote image quota across processes
* fix: recover stale remote image locks safely
* fix: preserve remote storage errors during unlock
* fix: fail closed when stale lock removal fails
* fix: harden image upload resource and storage cleanup
* fix: bound SFTP operations and lock lifetime
* fix: bound SFTP acquisition and cleanup callbacks
* fix: close late SFTP channels and test cleanup stalls
* fix: preserve SFTP inspection client context
* feat: add image upload source metadata
* fix: expose image upload metadata in logs
* chore: exclude internal plan from pull request
* style: apply prettier formatting
---------
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: batch of security hardening fixes (#1255)
* fix: bind desktop auto-session loopback check to the TCP peer address
* fix: escape HTML entities in Vault OIDC callback responses
* fix: route homepage ping and rss through the SSRF-safe outbound fetch
* fix: scope tunnel status endpoints to hosts the caller can access
* chore: update release notes
* chore: update release notes to write more about the ai integration
* fix: unbreak windows and macos electron builds after node-pty
Install Spectre-mitigated MSVC libs on the Windows runner and cover
node-pty's spawn-helper in the macOS universal arch rules.
* fix: rework connection defaults ui into a dialog and add missing i18n keys
* fix: rework macros panel with i18n, plain text matching, and list layout
* feat: add docs links for trash, connection defaults, and local echo
* fix: make image storage and trash tests pass on windows
* fix: stop docs links squeezing sidebar panel headers
* fix: put automations docs link back on the tabs row
* fix(desktop): keep Linux credential storage working on unrecognised desktops (#1261)
Chromium resolves safeStorage's backend from XDG_CURRENT_DESKTOP and falls
back to the basic_text store for any desktop it has no mapping for, which
covers every wlroots-style compositor (Hyprland, sway, niri, river).
isEncryptionAvailable() reports false for that store, so saveRemoteSyncJwt
refused every write and the OIDC sign-in it was storing appeared to succeed.
The sync engine then found no JWT and reported the session as expired, which
sent users looking at their OIDC provider for a fault that was never there.
Name the libsecret backend explicitly on those desktops. They run an ordinary
Secret Service, so that is enough to make encryption available again. KWallet
desktops keep their auto-detected backend, an explicit --password-store still
wins, and no stored secret can be orphaned by the switch because
isEncryptionAvailable() gated every write that would have created one.
Also stop discarding the {success: false} the main process returns when it
cannot store a credential: on a machine with no Secret Service at all, the
sign-in now says so instead of silently completing.
Co-authored-by: alexandre-vl <rafaelsenchais@gmail.com>
* chore: update release notes
* chore: update release notes
* fix(file-manager): widen trash dialog so names and paths are not cut off
* fix(sidebar): stop hover action tray overlapping the row below it
* fix(hosts): make real status colors toggle actually apply
* feat(local-terminal): add rail button and fix hardcoded tab label
* chore: update release notes
* fix(ai): hide assistant everywhere when admin disables it globally
* fix(automations): fix concurrency race, wire docker and internal event triggers
Claim the in-flight slot in the same tick it is checked, poll container
state for docker_event triggers, emit the internal events, apply the
schedule time zone, and expose the concurrency policy in the editor.
* fix(sidebar): rework host and credential drag-to-reorder
Adds a lock toggle in the sort menu and fixes reorder positioning,
cross-folder drops, and the duplicate drop indicator.
* chore(sidebar): drop unused sortKey prop from host and credential trees
* fix(sidebar): fix row height in click tray mode so status stripes stop overlapping
* fix(onboarding): remove add-first-host step that closed onboarding mid-flow
* fix(release): upload release notes so Mac App Store review submission stops failing
* chore: sync Crowdin translations for 2.7.0
---------
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: T3rM1nAt0-R <niraj.sangani91@gmail.com>
Co-authored-by: Horziox <horziox.dev@gmail.com>
Co-authored-by: William Shi <184219650@qq.com>
Co-authored-by: Carl <scarlettme@qq.com>
Co-authored-by: Raw_Nuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: njz-cvm <njz@cvm.com>
Co-authored-by: Alexandre VARGAS <alexandre.vargas.lopez@gmail.com>
Co-authored-by: alexandre-vl <rafaelsenchais@gmail.com>
* feat: redesign host/credential sidebars with synced preferences and manual drag-to-reorder
* chore: run format
* chore(deps-dev): bump @types/pg in the dev-patch-updates group (#1162)
Bumps the dev-patch-updates group with 1 update: [@types/pg](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pg).
Updates `@types/pg` from 8.20.0 to 8.20.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pg)
---
updated-dependencies:
- dependency-name: "@types/pg"
dependency-version: 8.20.3
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-dev): bump the dev-minor-updates group with 4 updates (#1163)
Bumps the dev-minor-updates group with 4 updates: [react-hook-form](https://github.com/react-hook-form/react-hook-form), [react-icons](https://github.com/react-icons/react-icons), [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Updates `react-hook-form` from 7.79.0 to 7.84.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.79.0...v7.84.0)
Updates `react-icons` from 5.6.0 to 5.7.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.6.0...v5.7.0)
Updates `typescript-eslint` from 8.61.1 to 8.66.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/typescript-eslint)
Updates `vite` from 8.0.16 to 8.2.0
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.2.0/packages/vite)
---
updated-dependencies:
- dependency-name: react-hook-form
dependency-version: 7.84.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: react-icons
dependency-version: 5.7.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
dependency-version: 8.66.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-updates
- dependency-name: vite
dependency-version: 8.2.0
dependency-type: direct:development
update-type: version-update:semver-minor
dependency-group: dev-minor-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 prod-patch-updates group with 3 updates (#1164)
Bumps the prod-patch-updates group with 3 updates: [jose](https://github.com/panva/jose), [js-yaml](https://github.com/nodeca/js-yaml) and [nanoid](https://github.com/ai/nanoid).
Updates `jose` from 6.2.7 to 6.2.8
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.7...v6.2.8)
Updates `js-yaml` from 5.2.2 to 5.2.3
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.2...5.2.3)
Updates `nanoid` from 6.0.0 to 6.0.1
- [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/6.0.0...6.0.1)
---
updated-dependencies:
- dependency-name: jose
dependency-version: 6.2.8
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: js-yaml
dependency-version: 5.2.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: nanoid
dependency-version: 6.0.1
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 undici in the prod-minor-updates group (#1165)
Bumps the prod-minor-updates group with 1 update: [undici](https://github.com/nodejs/undici).
Updates `undici` from 8.9.0 to 8.10.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.9.0...v8.10.0)
---
updated-dependencies:
- dependency-name: undici
dependency-version: 8.10.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
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 major-updates group with 2 updates (#1166)
Bumps the major-updates group with 2 updates: [@types/better-sqlite3](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/better-sqlite3) and [jsdom](https://github.com/jsdom/jsdom).
Updates `@types/better-sqlite3` from 7.6.13 to 9.6.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/better-sqlite3)
Updates `jsdom` from 29.1.1 to 30.0.1
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.1.1...v30.0.1)
---
updated-dependencies:
- dependency-name: "@types/better-sqlite3"
dependency-version: 9.6.0
dependency-type: direct:development
update-type: version-update:semver-major
dependency-group: major-updates
- dependency-name: jsdom
dependency-version: 30.0.1
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>
* fix: stop resyncAutoIncrement failing on tables without an id column (#1173)
The Postgres branch asked pg_get_serial_sequence(table, 'id') about every
table a fixture had inserted into. That function raises 42703 when the
column does not exist, rather than returning null, so any seed touching a
table keyed on something else took down the fixture.
host_sidebar_preferences is keyed on user_id and has no id at all, which
is why the Postgres job on dev-2.7.0 fails for every pull request.
Drive the lookup from information_schema so a missing id column yields no
row instead of an error. A text primary key still returns a null sequence
and is still skipped, as before.
* chore: install the git hooks that were already configured (#1174)
husky, lint-staged, commitlint and their config have been in the repo
since v1.8.0 (#429): .husky/pre-commit runs lint-staged, .husky/commit-msg
runs commitlint, the lint-staged globs are in package.json and the
commitlint rules in .commitlintrc.json.
None of it has ever run. husky only takes effect once it sets
core.hooksPath, and that happens in the prepare lifecycle script, which
the package did not define -- so every clone installed the tooling and
left the hooks unwired.
That is why formatting keeps failing in CI rather than locally: three of
the four open pull requests fail lint-and-build on prettier alone,
touching between one and five files each, and the check is the first place
anyone finds out.
prepare falls back to true so a checkout without a .git directory cannot
break installation. The Docker build passes --ignore-scripts, so it never
runs this at all.
Also pin the Prettier extension to the repo's own copy via
prettier.prettierPath, and let .vscode/settings.json out of .gitignore so
it applies to everyone. The extension bundles its own prettier otherwise,
which formats to a different version's rules than the one CI enforces.
* fix: derive the ssh_credentials rebuild from the live schema (#1172)
The startup rebuild that drops the old username NOT NULL constraint
restated the table's columns as a literal and then copied rows with
INSERT INTO temp SELECT <every live column>. The table has gained columns
since that literal was written — cert_public_key, pin, sort_order and
sync_id are all added by addColumnIfNotExists before the rebuild runs —
so the destination was narrower than the source. SQLite rejected the
INSERT on a column count mismatch, the error was swallowed as a warning,
and the constraint survived every restart.
Read the CREATE TABLE statement back from sqlite_master and rewrite just
the table name and the username constraint, so the replacement table
cannot fall behind the real one. Copy rows by explicit column name rather
than positionally, and replay the table's indexes, which DROP TABLE would
otherwise take with it along with the sync_id uniqueness.
* fix: make audit_logs.user_id nullable on fresh SQLite installs (#1171)
The audit trail is meant to outlive the account it belongs to: deleting a
user nulls user_id and keeps username for attribution. schema.ts, the
Drizzle migrations and AuditLogRepository.anonymizeByUserId were all
written against that, but the runtime bootstrap still created
user_id TEXT NOT NULL.
A second CREATE TABLE IF NOT EXISTS further down migrateSchema() had the
correct nullable column, but it can never run — the primary bootstrap has
already created the table, so IF NOT EXISTS is a no-op. Every fresh
install therefore got the old constraint, and user deletion failed with
"NOT NULL constraint failed: audit_logs.user_id" for any account that had
logged in at least once, via both the admin delete path and the OIDC
account-link cleanup.
Fix the primary bootstrap, and rebuild the table on existing databases
using the same pattern already used for ssh_credentials.username, since
SQLite cannot ALTER a column.
* fix: key the sync upsert on the row it just looked up (#1175)
A sync push locates the stored row twice -- once to decide insert vs
update, once to write it -- and the two lookups were spelled out
separately. Only the read knew about singleton entities; the write always
keyed on table.id.
userPreferences is the only singleton, and user_preferences is the one
synced table with no id column: its primary key is user_id. table.id was
therefore undefined, and drizzle emitted a comparison with nothing on its
left:
( = ? and "user_preferences"."user_id" = ?)
The insert branch was unaffected, so the first push of preferences
succeeded and every push after it -- the steady state -- failed with
SqliteError: near "=": syntax error. Preference sync never converged, and
both sides ship the same handler, so the desktop's embedded backend failed
identically.
Extract the lookup into locateSyncRow() and use it for the read, the
update and the tombstone delete, so the three cannot drift apart again.
The tombstone path already handled singletons correctly; it now shares the
one expression rather than keeping a third copy of it.
* fix: refuse an SSH connection whose host id resolves elsewhere (#1176)
A client identifies a host by the numeric row id of the database it is
displaying. With the desktop connection origin set to "Remote server",
that id is resolved against the sync server's ssh_data instead, and the
two autoincrement sequences need not line up -- they diverge as soon as
each side accumulates inserts and deletes in a different order.
resolveHostById() then returns whichever row owns that id here, and the
handler takes the address, the credentials, the jump hosts and the stored
host key from it. The session opens on a machine the user did not pick,
while the host list, host details and export all keep showing the right
one. Commands run on the wrong server, a host key mismatch is reported
for the wrong reason, and anything typed at the prompt goes to the wrong
place.
Compare the resolved address against the one the client sent, and refuse
when they disagree. Checking at the point the row is loaded covers every
use of it rather than each site separately. Addresses are compared with
brackets stripped and casing folded, so an IPv6 literal or a hostname
written differently is not treated as a different machine; when the
server has no address stored, the client's own details are used as
before.
This stops the wrong-machine session. It does not make delegated
connections work when the ids have drifted -- that needs the host to be
addressed by syncId across the boundary, which the connection protocol
does not currently carry.
* fix: refuse SFTP and Docker console on a mismatched host id too (#1177)
The wrong-machine guard added for SSH covered one of the paths that
resolve a client-supplied host id against this server's ssh_data. The
file manager and the Docker console take the same id from the same client
and dial whatever row owns it here.
The file manager then browses, edits and deletes files on that machine,
and the Docker console attaches to its daemon -- both while the UI shows
the host the user actually picked.
Reuse hostAddressMismatch at each point the row is loaded. The two file
manager sites sit inside "failed to resolve credentials, carry on"
handlers, so the refusal is a distinct error type those catches rethrow;
swallowing it would resume the connection this is meant to stop. The
Docker console reports over its socket, as it does for every other
refusal.
The user-facing wording now lives next to the check instead of being
written out at each site.
Still uncovered, and not fixable this way: file-manager's transfer
session, jump-host-chain and the proxmox routes resolve an id with no
client-supplied address to compare it against. Those need the host to be
addressed by syncId across the boundary.
* feat: address hosts by syncId when a connection is delegated (#1178)
A numeric host id belongs to the database that produced it. The desktop
app lists hosts from its embedded database and names them by row id, so
when a connection is delegated to a sync server that id is resolved
against a different table, whose autoincrement sequence has no reason to
agree. The row it lands on is a different machine, and it supplies the
address, the credentials, the jump hosts and the stored host key.
#1176 and #1177 made that refuse rather than connect. Refusing is right,
but it leaves "Remote server" unusable once the ids have drifted, which
is the state the reporter was in.
syncId already names a host identically on both sides -- remote sync
relies on it, ssh_data.sync_id is unique, and the API already returns it.
It just never reached the backend: hostToSSHHost() builds its result field
by field and dropped it.
Carry it through, and resolve with it when it is present:
resolveHostBySyncId(syncId, userId) // translate, then reuse
-> findHostIdBySyncId(syncId) // this database's own row id
-> resolveHostById(hostId, userId) // permissions, decryption, audit
The translation is deliberately not scoped to a user -- sync_id is unique
across the table and a shared host belongs to someone else -- so access
stays with the permission check in the id-based path, which the new tests
cover.
An unknown syncId resolves to nothing rather than falling back to the
numeric id: an unknown host is precisely where guessing picks the wrong
machine. Clients that send no syncId are unchanged, address comparison
included, so an older desktop keeps its safety net instead of breaking.
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix: keep already-shared hosts sharing their SSH authentication (#1179)
Sharing a host used to hand the owner's SSH authentication to the
recipient unconditionally. 2.6.1 put that behind ssh_data.share_ssh_auth,
added as NOT NULL DEFAULT 0.
Existing rows took the default, so every host shared before the upgrade
stopped supplying credentials the moment the column appeared. The snapshot
in collectProtocolSnapshots() is guarded by host.shareSshAuth, so nothing
was captured; resolveRecipientSharedHostAuthentication() then fell through
to "required" and the recipient got "No valid authentication method
provided" on a host that had worked the day before. Downgrading to 2.6.0
restored it, since that code has no such column to consult.
Backfill the flag for hosts that already appear in host_access. That is
where the previous behaviour was in effect and where the owner had already
agreed to share; hosts nobody has shared keep the new default and stay off
until their owner shares them.
Guarded by a settings key so it runs once. Without that, an owner who
turns sharing back off would have it turned on again by the next restart.
* fix: let a single credential disable 2FA again (#1180)
The disable dialog has one field, labelled "Enter TOTP code or password",
and its caller passes that value as disableTOTP(input) -- so it arrives as
`password` with `totp_code` undefined. That call has been unchanged since
v2.3.0.
2.5.1 changed the route to require both:
if (!totp_code || (!userRecord.isOidc && !password)) -> 400
replacing `const credential = password || totp_code`. The first check has
rejected every attempt since, whatever the user typed, so nobody has been
able to turn 2FA off -- the client reports the generic "Failed to disable
2FA", which hides which check failed.
Take one credential again and try it as a TOTP code, a backup code, then
the account password. verifyTotpReauth still refuses the password itself,
so that comparison stays in the route; an OIDC user has no password hash
and reaches neither.
The backup-codes route has the same shape but no caller in the UI -- its
codes are returned when TOTP is enabled -- so it is left alone rather than
changed blind.
* fix: attach user-managed CA certificates over SFTP too (#1181)
opkssh-cert-auth.ts exports two helpers that end in the same
_applyCertToConnection: setupOPKSSHCertAuth, and setupCACertAuth for
user-managed CA-signed -cert.pub files. The file manager called the first
one twice and the second one never.
So a host whose key is paired with a CA-signed certificate authenticated
in a terminal and failed over SFTP, while OPKSSH certificates -- going
through the other helper -- worked in both. The file manager was not
missing certificate support in general; it was missing one of the two
paths into it.
The connection also never carried the certificate to begin with:
cert_public_key was not among the fields copied into resolvedCredentials,
so both places that build an SFTP connection now read it and attach it
where the private key is prepared -- the dedicated transfer session and
the main connect route.
An unusable certificate is logged and skipped rather than failing the
connection. The key alone may still be accepted, which is what happened
while this was not wired up at all, and turning that into a hard failure
would break setups that currently work.
Reported in #1160 with the call-site asymmetry already traced; the
reporter noted they could not confirm the link to their failure, having
moved off SSH CAs. The asymmetry is real either way and reproduces the
symptom exactly.
* fix: authenticate the desktop Docker console WebSocket (#1182)
The console WS opted out of the query token:
buildOriginWsUrl({ ..., includeLocalJwt: false })
leaving it with no credential at all on the desktop. The browser
WebSocket API cannot set an Authorization header, and while Electron's
main process injects a remembered JWT cookie, it requires an exact origin
match -- the cookie belongs to the API origin (localhost:30001) while the
console connects to 127.0.0.1:30009, so nothing is attached.
The backend then closes the handshake with 1008 before it logs anything,
which is why the log has no docker-console entries while stats and logs
polling keep succeeding on the same host. The web build is unaffected: it
connects same-origin and its cookie is sent normally.
Drop the opt-out so the console carries the local JWT like the SSH
terminal does -- the same token, the same query parameter, and the
backend already reads it there.
Guacamole passes includeLocalJwt: false too, but rdp/vnc/telnet always
resolve to "remote", so that call never reaches the local branch.
* fix: use getClientIp in getRequestMeta for correct audit-log IPs (#1183)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: use getClientIp in getRequestMeta for correct audit-log IPs
getRequestMeta had near-duplicate, strictly worse forwarded-header
logic: the array branch didn't split/trim, there was no socket-peer
fallback, and it returned "" instead of "unknown". Delegate to
getClientIp so the audit trail gets the same correctness as the
terminal login-alert path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add terminal image handoff (#1170)
* chore: sync Crowdin translations
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* feat: add terminal image handoff
Add authenticated browser upload and clipboard image handoff for terminal agents. Normalize images through Sharp, enforce storage and request limits, preserve host-visible paths, and provide a stable three-button terminal toolbar.
* docs: document terminal image handoff deployment
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(desktop): stop suppressing the update prompt, and make the version badge reachable (#1167)
* fix(desktop): stop suppressing the update prompt for users who need it
The startup update modal stored its dismissal under the local app version
rather than the remote version being offered, and the up-to-date branch
wrote that key with no user interaction at all. A user who launched while
current had their own version recorded; once the next release shipped,
`dismissedVersion === currentVersion` still held and the modal was skipped
on every launch. It reappeared only after the user had already updated --
the inverse of what it is for. Present since v2.3.0.
Key the dismissal on the offered remote version instead. The change is
backward compatible: an existing key holding 2.6.0 compares unequal against
a remote 2.6.1, so affected installs are prompted on their next launch. When
the check itself fails there is no remote version, so nothing is recorded and
no future prompt is suppressed.
That left the version badge as the only remaining signal, and it was an inert
span on both surfaces that render it -- the profile panel and the dashboard
stats bar -- even though the `getVersionInfo()` response it is built from
already carries `latest_release.html_url`. Extract the duplicated badge into
`components/version-badge.tsx` and make the update case a link to the release,
with an accessible name that says where it goes. The beta and stable cases
stay inert.
`getVersionInfo()` returned `Record<string, unknown>`, so the release URL was
unreachable without a cast; give it a `VersionInfo` type that keeps an index
signature, since `SystemOverviewWidget` reads `updateAvailable` off the same
response.
* test: cover the read that actually reaches the badge
The extracted VersionBadge is unit-tested, but the line that decides whether
it ever receives a URL -- pulling `latest_release.html_url` out of the version
response -- was duplicated at both call sites and asserted nowhere. A wrong
property there compiles (the response type keeps an index signature) and every
existing test still passes.
Give it a name, `releaseUrlFrom`, use it from both surfaces, and test it: the
happy path, a response with no release, a release with no URL, and a missing
response, since the caller's fetch can reject. Empty string is the contract the
badge reads as "nothing to link to", so it stays an inert span rather than
rendering a dead anchor.
* docs: state the index signature's real reason
The comment claimed the version endpoint carries fields beyond the typed ones,
citing `updateAvailable`. It does not -- `GET /version` returns status,
localVersion, version, remoteVersion, latest_release, cached and cache_age, and
nothing else. SystemOverviewWidget reads `updateAvailable` off it regardless,
which is why the permissive index signature has to stay, but that is a stale
read rather than an undocumented field. Say so accurately.
* Send alerts in Discord channels with Webhooks (#1158)
* feat(utils): add discord webhook sender
Add a utility to send alert embeds to Discord webhooks.
* fix(utils): validate DNS and use global fetch for outbound requests
Prevent private destination access and rely on global fetch after DNS validation.
* chore(logger): include extra context in logs
Show additional sanitized context entries for clearer diagnostics.
* feat(alerts): support discord channel type in routes and engine
Accept discord channels and route alerts to the Discord sender.
* feat(ui): add Discord option to notification channel dialog
Allow creating/editing Discord webhook channels with username/avatar.
* fix(ui/api): accept structured config payload for notification channels
Allow the client to pass structured config objects (or strings) when creating/updating channels.
* chore: sync Crowdin translations
* fix(homepage): make the System Overview update indicator able to fire (#1168)
The widget's "Update available" row and orange version text were unreachable,
for two independent reasons that each alone would have been enough.
It called `getVersionInfo(false)`, and `checkRemote=false` makes /version return
early with `{localVersion, status: "update_check_disabled"}` -- no GitHub fetch,
no remote version, nothing to compare. It then read `info.updateAvailable`, a
field the route does not return in either mode; the success response carries
status, localVersion, version, remoteVersion, latest_release, cached and
cache_age. `Boolean(undefined)` is false, always. The read type-checked only
because `getVersionInfo()` is declared as `Record<string, unknown>`, so a
property name that does not exist is indistinguishable from one that does.
Let the endpoint do the comparison and read `status === "requires_update"`,
which is what the dashboard stats bar and the profile panel badge already do.
The row's label was `homepage.overviewUpdate`, whose English string is "Up to
date" -- as the label of an update-available row it read "Up to date / Update
available". Nobody has seen that, because the row has never rendered; fixing
the indicator without the label would have shipped it. Give it its own key.
That leaves `homepage.overviewUpdate` unused; it is left in place rather than
removed, since it would be the natural value for an always-visible row and that
is a product decision, not part of this fix.
* fix: capture real client IP for SSH login alerts behind reverse proxy (#1169)
* fix: capture real client IP for SSH login alerts behind reverse proxy
The WebSocket terminal handler used req.socket.remoteAddress for the
"user logged in" alert message, which is the immediate TCP peer (the
reverse proxy) rather than the actual client IP forwarded via
X-Forwarded-For. This made trust-proxy config on Traefik irrelevant
since Termix never read the header for this code path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: cover getClientIp forwarded-header and socket fallback paths
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* chore: add url to SENSITIVE_FIELDS for discord url
* fix: enforce SSRF protection on outbound fetches
Use `undici.fetch` with the custom DNS lookup hook to ensure the validated
DNS resolution is the one used for the connection. Fix DNS lookup/address
validation bugs and add coverage for private, public and invalid addresses,
including the resolution issue affecting Discord endpoints.
* chore: prettier format
* fix: validate all DNS addresses and close dispatcher
* fix DNS lookup validation and callback handling
* update safe outbound fetch tests
* ensure created dispatcher is properly closed
* chore: remode url from SENSITIVE_FIELDS for other logs
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix tmux UTF-8 path handling (#1157)
Co-authored-by: Carl <scarlettme@qq.com>
* chore: update package lock
* chore: update gitnore
* fix: [BUG] (#1049)
https://github.com/Termix-SSH/Support/issues/1049
* fix: test commitlint path fix (#1021)
* fix: SGR mouse-tracking escape codes printed as text (#1023)
* fix: quote $1 in commit-msg hook so it works from git worktrees
* fix: [BUG] could not connect to the database (#1057)
https://github.com/Termix-SSH/Support/issues/1057
* fix: [BUG] VNC connect macOS screen sharing failed (#1063)
https://github.com/Termix-SSH/Support/issues/1063
* fix: [BUG] Meta key (#1075)
https://github.com/Termix-SSH/Support/issues/1075
* fix: [BUG] Remote sync doesn't work with Termix behind nginx proxy (#1085)
https://github.com/Termix-SSH/Support/issues/1085
* fix: [BUG] webhook not working (#1080)
https://github.com/Termix-SSH/Support/issues/1080
* fix: [BUG] First server sync doesn't refresh UI (#1084)
https://github.com/Termix-SSH/Support/issues/1084
* fix: [BUG] How to enable SSL using custom certificate (#1083)
https://github.com/Termix-SSH/Support/issues/1083
* fix: [BUG] Sudo Password Auto-fill Persistance (#1098)
https://github.com/Termix-SSH/Support/issues/1098
* feat: [FEATURE] Expand Snippets Function (#1031)
https://github.com/Termix-SSH/Support/issues/1031
* feat: [FEATURE] (#1055)
https://github.com/Termix-SSH/Support/issues/1055
* feat: [FEATURE] Support for Headscale API Keys (hskey prefix) and Custom API Endpoints (#1013)
https://github.com/Termix-SSH/Support/issues/1013
* feat: [FEATURE] Allow paste on non https (#1026)
https://github.com/Termix-SSH/Support/issues/1026
* feat: be-azerty layout (#1073)
https://github.com/Termix-SSH/Support/issues/1073
* feat: Keyboard shortcuts to move between open tabs (#1069)
https://github.com/Termix-SSH/Support/issues/1069
* feat: Session Logs as a downloadable text file (#1058)
https://github.com/Termix-SSH/Support/issues/1058
* fix: persist and auto-fill saved SSH and sudo passwords
* fix: persist docker runtime selection and docker manager UI issues
* feat: Allow excluding specific mounts from disk usage metrics (#1046)
https://github.com/Termix-SSH/Support/issues/1046
* feat: Expand Snippets Function (#1031)
https://github.com/Termix-SSH/Support/issues/1031
* chore: restore the prettier baseline on dev-2.7.0 (#1185)
Five files on dev-2.7.0 do not match prettier, so `npx prettier --check .`
fails and takes lint-and-build with it — on every pull request, whatever
it changes.
Formatting only, produced by `npx prettier --write` on exactly the files
the check names. No logic touched: tsc passes for both configs, backend
148 files / 1106 tests and UI 71 files / 479 tests all pass.
* test: keep the tmux escaping test runnable on Windows (#1184)
The escaping check ran its command through /bin/sh. That binary does not
exist on Windows, and Windows is a supported platform for the desktop
app, so `npm test` fails there on a test about string quoting. CI is
ubuntu-only and would never see it.
Assert the escaped string directly, which covers the rule on every
platform, and keep the round trip through a real shell as a separate case
guarded by platform -- it is the stronger evidence where a shell exists.
* chore: drop the unreachable table probes from migrateSchema (#1186)
Eleven blocks in migrateSchema() guarded a CREATE TABLE IF NOT EXISTS
behind SELECT id FROM <table> LIMIT 1, for tables the primary bootstrap
had already created earlier in the same startup. The probe could not
throw, so the catch never ran.
Two of those unreachable copies had drifted from the definition actually
in use. sessions had lost ON DELETE CASCADE, and session_recordings still
carried user_id TEXT NOT NULL with ON DELETE CASCADE and no username --
the shape from before audit trails were made to outlive the account. They
would have taken effect had anything ever reordered startup.
Kept, because they are not the same thing:
- blocks whose catch runs ALTER TABLE ADD COLUMN. CREATE TABLE IF NOT
EXISTS is a no-op on a table that exists, so a database created before
a column was added still needs the ALTER. Those probe a column, not a
table.
- blocks that are a table's only creation point.
- the user_open_tabs block, which is a data migration; its SELECT is a
precondition, not a probe.
Deletion only, no behaviour change.
* fix: repair the frontend type-check and clear the 299 errors behind it (#1189)
* fix: repair frontend type-check configuration and the errors it exposed
The root tsconfig.json is solution-style with "files": [], so the
`npx tsc --noEmit` that CI runs compiles nothing at all. Frontend types
have therefore never been checked, and 299 errors had accumulated behind
that no-op. This clears just over half of them; nothing here changes
runtime behaviour.
Configuration:
- "@/types" resolved through the "@/*" fallback to src/ui/types, which
does not exist. Added an explicit mapping to src/types/index.ts.
- src/vite-env.d.ts sits outside the include list, so import.meta.env and
the ?url import suffix were unknown. Added.
- src/ui/types/ held a single file, keybindings.ts, while every other
shared type lives in src/types/. Six modules imported it as
"@/types/keybindings" and silently resolved to nothing. Moved.
Type definitions that had fallen behind the code:
- guacamoleConfig and terminalConfig were Record<string, unknown> in
ui-types while the editor read concrete fields off them. Both now use
the real interfaces; GuacamoleConfig is extracted from its inline
definition in guacamole-api.ts so the two cannot drift again.
- customThemeColors and TerminalTheme["colors"] described the same object
with different optionality. Aligned.
- FileWindow declared its own SSHHost whose authType was "password" | "key",
which no longer matches the eight the app supports.
- connectSSH and listSSHFiles returned Record<string, unknown>, so every
field the callers destructured arrived as unknown.
- AxiosRequestConfig and AxiosResponse were used without being imported.
Also adds asHttpError() for the handful of catch blocks that reached into
an unknown binding, and narrows the Host | HostFolder comparator and the
RailItem union at the points where the discriminant was not carrying.
Note: dbHealthMonitor.reportDatabaseError was being called with a second
argument it does not accept, so the authenticated-or-not flag was already
being discarded at runtime. Dropped the argument to match the signature;
whether that flag was meant to gate the report is worth a separate look.
* fix: clear more of the frontend type-check baseline
Continues the previous commit; 140 errors down to 70. Three of these were
real defects rather than missing annotations.
Defects:
- DashboardTab counted active tunnels by comparing status to "CONNECTED",
but CONNECTION_STATES.CONNECTED is "connected" and that is what the
tunnel manager emits, so the count was always zero. Now compares against
the constant.
- QuickActionsCard requires isAdmin and gates a block of admin-only actions
on it, but neither call site passed it — those actions never rendered.
Both call sites also passed isAdmin to HostStatusCard, which does not
accept it; the prop had evidently been moved and the call sites missed.
- The host editor stores jump host ids as strings and sent them straight to
an API typed for numbers. Backend host lookups compare against an integer
column, which a string does not match on Postgres or MySQL. Converted.
Types brought in line with the data:
- Host and HostData were missing hasPassword, hasSudoPassword, sortOrder,
instanceId, connectionOrigin, vaultProfileId, syncId, and the "vault"
authType; TabContextTab was missing the "tunnel" tab, which TabContext
already branched on.
- statsConfig and terminalConfig used inline shapes that had drifted from
StatsConfig and TerminalConfig. Both now reference the real interfaces;
excludedMounts, which the editor reads, was added to StatsConfig.
- downloadSSHFile, generateKeyPair and generatePublicKeyFromPrivate all
returned Record<string, unknown> while callers read named fields.
- The Guacamole declarations were missing Keyboard.reset, Client.onfile,
InputStream.sendAck, Status.Code and BlobReader, all already in use.
- NetworkTopologyNode/Edge could not be discriminated, though the graph
code tells them apart by testing for source/target.
ProxyNode.type is now 4 | 5 | "http" | "socks4" | "socks5". The editor
writes the string spellings while proxy-helper.ts tests for "http" and
casts everything else to 4|5 before handing it to the socks client, so a
chained proxy reaches it as "socks5" rather than 5. Typed as what is
actually stored; reconciling the two spellings needs a migration decision
and is left alone here.
* fix: continue clearing the frontend type-check baseline
70 errors down to 44.
Dead configuration removed:
- Terminal set terminal.options.bellStyle on xterm, which dropped the
option in v5. The host editor still exposes the setting and stores it;
it has simply had no effect on the terminal since that upgrade. Making
the bell work again means handling the onBell event and is left alone.
- CodeEditor passed scrollPastEnd to basicSetup, which has no such option.
- FileManager passed an id to openWindow, which assigns its own and
discards what it is given — the component was already being rendered
under a different id than the one the caller held.
Widgets that were registered but unreachable:
- DockerActivityWidget and SshQuickConnectWidget register under
"docker_activity" and "ssh_quick_connect", neither of which was in
WidgetTypeId, and both referenced config interfaces that did not exist.
Added the ids and the two interfaces, inferred from their edit forms and
defaultConfig.
More endpoints given their real return types: getRecentFiles,
getPinnedFiles, getFolderShortcuts (arrays, not records), downloadSSHFile,
copySSHItem, generateKeyPair, generatePublicKeyFromPrivate and getSnippets.
parseGuacamoleConfig() handles the host row carrying guacamoleConfig either
parsed or as raw JSON, which GuacamoleApp was reading fields off directly.
TerminalHostConfig was missing name, which it reads for the activity log.
* fix: continue clearing the frontend type-check baseline
44 errors down to 17.
Host and AuditLog are now type aliases rather than interfaces. An
interface has no implicit index signature, so neither could be assigned
to the `[key: string]: unknown` shapes that TerminalHostConfig,
HostMetricsTab's HostConfig and several helpers declare — eight errors
came from that alone.
More dead configuration:
- i18n passed checkWhitelist to the language detector, which no longer
has that option; supportedLngs already covers it.
- SSHAuthDialog passed scrollPastEnd to basicSetup, same as CodeEditor.
- AudioPreview's onLoadedMetadata never fired: react-h5-audio-player
spells the prop onLoadedMetaData.
- MarkdownRenderer destructured `inline` from code(), which react-markdown
removed in v9, so the flag was always undefined and every inline span
took the block branch when it happened to carry a language class. Now
derived from whether a className is present at all.
- SnippetsPanel put a title prop on a lucide icon, which does not forward
it; changed to aria-label so the hint is actually reachable.
updateHostConfig in TabContext replaced tab.hostConfig wholesale with the
six-field literal it receives, dropping everything else the tab held about
the host. It now merges onto the existing config.
Also: getReleasesRSS, getUserAlerts and getVersionInfo have real return
types (UpdateLog kept private copies of two of them, and VersionInfo was
missing `version`, which the endpoint sends and the panel renders);
wakeOnLan and vncCredentialId get the numeric ids they are typed for; and
the tmux formatter takes i18next's TFunction instead of a hand-written
signature it does not satisfy.
* fix: clear the last frontend type errors and make CI actually run the check
Baseline is now zero, so the check can be turned on.
`npx tsc --noEmit` — what CI ran and what `npm run type-check` was — compiles
nothing: the root tsconfig.json is solution-style with "files": [], and
plain tsc does not follow project references. Both are now `tsc -b`, which
builds tsconfig.app.json and tsconfig.node.json. Verified by planting a type
error and watching the command fail.
Last defects in this batch:
- patchOpenTab could not carry hostId, so quick-connect's "save this host
and attach the tab to it" call was passing a field excluded from the
type all the way down. The column exists and updateForUser spreads
whatever it receives, so the write worked; only the types disagreed.
Widened front to back.
- The file-comparison window opened without x, y, width or height — every
other openWindow call passes them — and sent a `type` field WindowInstance
does not have.
- HostEditor gated a block on authType === "warpgate", which is not one of
the eight authType values. Unreachable, and it held only a label and a
description. Removed.
- FileManager passed onLoadDirectory to a sidebar that neither declares nor
reads it, and FileManagerApp passed embedded to a FileManager that has no
such prop.
- TunnelApp's minimal Host was missing three required flags.
The remainder were assertions at boundaries that are genuinely loose: bulk
host import takes rows assembled from untyped input and validates them
server-side, and a vi.fn() whose body only throws infers never.
* feat: add drive file browser and drag-and-drop upload for RDP (#1187)
Drive redirection could already be enabled per host, but the redirected
drive lived inside guacd with no way to reach it from the browser: the
client never handled onfilesystem, so the mounted volume was writable
from Windows and invisible from Termix.
Add a file browser panel that lists the drive, downloads files, and
uploads them, plus drag-and-drop onto the display which opens the panel
and uploads into the directory currently shown. The disable-upload and
disable-download connection settings are honoured by the UI, not just
passed to guacd.
A rejected upload stops the BlobWriter without firing onerror or
oncomplete, so the error ack is watched explicitly; otherwise the
transfer would hang forever. Directory reads carry a deadline for the
same reason.
Also declares Guacamole.Object, Client.onfilesystem, BlobReader and
BlobWriter in the local type definitions, which previously omitted them.
* fix: keep the mouse working on touch-capable devices in RDP/VNC (#1190)
Reported as "mouse input broken, keyboard fine" after 2.5.1 (#1102).
2.5.1 bound Guacamole.Mouse unconditionally. 2.6.0 replaced that with a
three-way branch on touchMode, and the touch branches replace the mouse
binding instead of adding to it:
if (touchMode === "touchscreen") new Guacamole.Mouse.Touchscreen(el)
else if (touchMode === "touchpad") new Guacamole.Mouse.Touchpad(el)
else new Guacamole.Mouse(el)
The two do not overlap. Guacamole.Mouse listens for mousedown/mousemove/
mouseup; Touchscreen and Touchpad listen only for touchstart/touchmove/
touchend. So in a touch mode nothing is listening for the mouse at all.
touchMode defaults to "touchscreen" whenever navigator.maxTouchPoints > 0,
which is true of every laptop with a touchscreen — machines that are still
driven by a mouse. Those users lost the pointer entirely while the keyboard
kept working, because Guacamole.Keyboard is bound independently.
The physical pointer is now always bound and a touch emulator is layered on
top when one is selected. Extracted to bindPointerInput() so the binding is
testable; the test fails against the old branch.
Note the issue also carries a second, unrelated report where well-formed
mouse frames do reach guacd and the VNC leg ignores them. That one is not
this, and the guacd image is pinned to 1.6.0 in both 2.5.1 and 2.6.1, so it
is not an upgrade either.
* fix: deduplicate /api/folders requests to prevent intermittent folder disappearance (#1191)
* chore: sync Crowdin translations
* fix: deduplicate /api/folders requests to prevent intermittent folder disappearance
getSSHFolders() had no request deduplication while getSSHHosts() used a TTL
cache with in-flight dedupe. When loadHosts() fired multiple times during
rapid navigation between Credentials and Hosts panels, the folder response
could arrive after the hosts response, causing the sidebar tree to render
without folder metadata.
- Add foldersCache (10s TTL) in hosts-request-cache.ts
- Wrap getSSHFolders() API call in getCachedSSHFolders()
- Invalidate folders cache on renameFolder, updateFolderMetadata,
deleteAllHostsInFolder, and renameCredentialFolder
- Include foldersCache in invalidateHostsAndStatusCaches()
ClosesTermix-SSH/Support#1103
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
---------
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
* chore(deps): bump undici from 8.9.0 to 8.10.0 in the prod-minor-updates group (#1195)
* chore: sync Crowdin translations
* chore(deps): bump undici in the prod-minor-updates group
Bumps the prod-minor-updates group with 1 update: [undici](https://github.com/nodejs/undici).
Updates `undici` from 8.9.0 to 8.10.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.9.0...v8.10.0)
---
updated-dependencies:
- dependency-name: undici
dependency-version: 8.10.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat: proxmox metrics integration
* feat: add folder select to the host multi select feature
* feat: implement context aware terminal toolbar with quick links, host info, image pasting, etc
* feat: made toolbar open file manager at path
* fix: delete folder route not invalidating host list cache
* fix: match host list icons with tab bar iconfix
* fix: change sidebar reset button icon to seperate against fullscreen button
* feat: unify connection system and add connection logs to guacd hosts
* fix: make mobile terminal scrollback match xterm wheel behavior (#1198)
* fix: route mobile terminal scrolling through xterm viewport
* docs: document mobile terminal touch scrolling
* chore: add a note to not place files in docs
* chore: remove touch imput from docs
* feat: improve snippet system with variable snippets and collapse settings
* feat: new fleet system with snippet, packages, files, and inventory features
* fix: command pallete not loading new activity and made enter load first item
* feat: add subhost from parent host organization feature
* feat: add workspaces feature to save tab layout
* perf: greatly improved performance across metrics polling and host management for enterprise users
* feat: add a onboarding system with a new interface simplicity system
* feat: finalize the multi dialect database system
* fix: bind trusted MFA devices to client installs (#1202)
* fix: merge OIDC group claims across sources (#1203)
* fix: allow disabling SSH keepalives (#1204)
* fix: distinguish reachable and available hosts (#1206)
* fix: throttle session activity persistence (#1207)
* fix: preserve saved RDP connection settings (#1208)
* fix: authenticate tunnel status stream (#1209)
* fix: select quick-created credentials (#1210)
* fix: stagger initial metrics collection (#1211)
* fix: stagger initial metrics collection
* fix: admit reachable hosts to initial metrics
* fix: prevent long host names shifting dashboard metrics (#1205)
* feat: add global touch input settings (#1201)
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: keep host list row sizing stable (#1213)
* fix(guacamole): correct Windows key mapping (#1216)
* fix: normalize OIDC discovery issuer URLs (#1218)
* fix: prompt for RDP domain credentials (#1212)
* fix: route status checks by connection origin (#1214)
* fix: restore desktop Tailscale configuration (#1215)
* fix(docker): restore Node 24 for ssh2 native crypto (#1217)
* feat: added new automations feature with events, channels, and steps
* feat: allowed some tabs in the app rail to be opened as its own tab or in a new right sidebar
* feat: expand onboarding process with more customization/features
* feat: initial implementation of the termix ai feature
* chore: run linter
* fix: issue #424 (#424)
https://github.com/Termix-SSH/Support/issues/424
* fix: Not working without internet connection. Missing OPKSSH binary in pre-built image. (#1133)
https://github.com/Termix-SSH/Support/issues/1133
* fix: SQLite forceSave on telemetry writes causes periodic SSH terminal stalls in 2.6.x (#1109)
https://github.com/Termix-SSH/Support/issues/1109
* feat: How to enable SSL using custom certificate (#1083)
https://github.com/Termix-SSH/Support/issues/1083
* fix: show profile API key after creation (#1221)
* feat: add trusted proxy authentication (#1222)
* fix: clarify SSH agent authentication (#1224)
* feat: add first-class split screen tabs (#1226)
* feat: add split tab data model
* feat: make split screens top-level tabs
* feat: persist and manage split layouts
* feat: launch native RDP on Windows desktop (#1223)
* feat: launch native RDP on Windows
* style: format native RDP launcher
* feat: enhance custom disk and network metrics (#1220)
* feat: enhance host disk and network metrics
* fix: align enhanced metrics types
* fix: preserve Proxmox guest identity on edit (#1219)
* fix: preserve Proxmox guest identity on edit
* fix: type Proxmox guest source metadata
* chore: dead-code cleanup and small refactors (#1225)
* chore: remove dead code and unused exports
* chore: remove unused api client functions
* chore: remove unused backend helpers
* refactor: extract getErrorMessage helper for repeated error extraction
* refactor: unify error message extraction across backend with getErrorMessage
* refactor: unify error message extraction in frontend with getErrorMessage
* refactor: merge duplicate imports from the same module
* refactor: use Array.includes in TabBar
* chore: drop biome, keep prettier as the single formatter
* style: apply prettier formatting to refactored files
* fix: close active tab with Ctrl+W on Windows
* fix: make tray Quit terminate the desktop app
* feat: verify host transfer integrity
* fix: reuse transfer sessions during verification
* feat: select the fastest host transfer route
* feat: tune host transfers adaptively
* feat: adapt background polling to activity (#1233)
* feat: adapt background polling to activity
* feat: extend adaptive polling coverage
* feat: make polling cost and network aware (#1234)
* feat: make repeat navigation feel instant (#1235)
* feat: make file operations feel immediate (#1236)
* feat: preload likely user actions (#1237)
* feat: preload likely file previews
* feat: preload likely host tools
* feat: preload likely file viewers
* fix: replace stale terminal input listeners
* feat: add links to docs for all new features
* chore: update readme
* fix: warn before discarding host changes (#1229)
* feat: learn local host action preferences (#1238)
* feat(terminal-toolbar): add bounded movable desktop toolbar (#1239)
* feat: add local adaptive decision engine (#1240)
* feat: adapt speculative resource usage (#1241)
* feat: persist adaptive transfer profiles (#1242)
* Fix .preferred_username when using LDAP login. (#1243)
* chore: sync Crowdin translations
* Fix .preferred_username when using LDAP login. Strips internal LDAP prefix from username.
---------
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
* feat: learn direct transfer routes (#1244)
* feat: learn speculative preload usefulness (#1245)
* fix: - Adjusting the SSH Authentication from Vault to something else fails (#1152)
https://github.com/Termix-SSH/Support/issues/1152
* fix: terminal graphical display, special characters inserted, distorted - `midnight comma... (#1145)
https://github.com/Termix-SSH/Support/issues/1145
* feat: single click on host in list opens session - should be only on double click (#1146)
https://github.com/Termix-SSH/Support/issues/1146
* feat: Terminal: custom font/ font selection/ how-to for adding a font - `MesloLGS NF` (#1140)
https://github.com/Termix-SSH/Support/issues/1140
* fix: revert host single click to open session, make double click an option (#1146)
Single click opens a session again by default. The old double click
behavior can be turned on in Customize Sidebar.
* chore: drop prettier check from beta release workflow, run formatter
* chore: patch dependabot vulnerabilities via npm overrides
* fix: reset adaptive resource state between tests to stop cross-test leaks
* feat: replace terminal toolbar density popover with a native select
* fix: pin hardwareConcurrency in adaptive budget tests so CI cores don't change the tier
* fix: allow dylib files in mac universal arch rules so mas build packages sharp
* feat: add file manager trash (#1250)
* feat: add inheritable connection defaults (#1246)
* feat: add desktop local terminal (#1247)
* feat: add interactive terminal macros (#1248)
* feat: add adaptive SSH local echo (#1249)
* fix: sync desktop host changes immediately (#1252)
* fix: route desktop sharing through synced server (#1253)
* Fix terminal image uploads and add safe diagnostics (#1254)
* feat: add configurable terminal image storage backends
* feat: add admin image storage settings
* fix: preserve native clipboard PNG uploads
* fix: quote terminal image paths safely
* docs: record image storage security remediation plan
* fix: close remote image SFTP channels
* fix: restrict remote image SFTP permissions
* fix: bound remote image SFTP writes
* fix: add best effort remote image retention
* fix: cap normalized image output size
* fix: bound concurrent image processing
* fix: fail closed on local image inspection errors
* test: cover fail closed image storage and atomic settings
* fix: enforce remote image quota and upload admission
* fix: serialize remote quota and verify existing paths
* fix: use synchronous sqlite settings transaction
* fix: keep settings transactions portable across dialects
* fix: bound image processing admission queue
* fix: serialize remote image quota across processes
* fix: recover stale remote image locks safely
* fix: preserve remote storage errors during unlock
* fix: fail closed when stale lock removal fails
* fix: harden image upload resource and storage cleanup
* fix: bound SFTP operations and lock lifetime
* fix: bound SFTP acquisition and cleanup callbacks
* fix: close late SFTP channels and test cleanup stalls
* fix: preserve SFTP inspection client context
* feat: add image upload source metadata
* fix: expose image upload metadata in logs
* chore: exclude internal plan from pull request
* style: apply prettier formatting
---------
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: batch of security hardening fixes (#1255)
* fix: bind desktop auto-session loopback check to the TCP peer address
* fix: escape HTML entities in Vault OIDC callback responses
* fix: route homepage ping and rss through the SSRF-safe outbound fetch
* fix: scope tunnel status endpoints to hosts the caller can access
* chore: update release notes
* chore: update release notes to write more about the ai integration
* fix: unbreak windows and macos electron builds after node-pty
Install Spectre-mitigated MSVC libs on the Windows runner and cover
node-pty's spawn-helper in the macOS universal arch rules.
* fix: rework connection defaults ui into a dialog and add missing i18n keys
* fix: rework macros panel with i18n, plain text matching, and list layout
* feat: add docs links for trash, connection defaults, and local echo
* fix: make image storage and trash tests pass on windows
* fix: stop docs links squeezing sidebar panel headers
* fix: put automations docs link back on the tabs row
* fix(desktop): keep Linux credential storage working on unrecognised desktops (#1261)
Chromium resolves safeStorage's backend from XDG_CURRENT_DESKTOP and falls
back to the basic_text store for any desktop it has no mapping for, which
covers every wlroots-style compositor (Hyprland, sway, niri, river).
isEncryptionAvailable() reports false for that store, so saveRemoteSyncJwt
refused every write and the OIDC sign-in it was storing appeared to succeed.
The sync engine then found no JWT and reported the session as expired, which
sent users looking at their OIDC provider for a fault that was never there.
Name the libsecret backend explicitly on those desktops. They run an ordinary
Secret Service, so that is enough to make encryption available again. KWallet
desktops keep their auto-detected backend, an explicit --password-store still
wins, and no stored secret can be orphaned by the switch because
isEncryptionAvailable() gated every write that would have created one.
Also stop discarding the {success: false} the main process returns when it
cannot store a credential: on a machine with no Secret Service at all, the
sign-in now says so instead of silently completing.
Co-authored-by: alexandre-vl <rafaelsenchais@gmail.com>
* chore: update release notes
* chore: update release notes
* fix(file-manager): widen trash dialog so names and paths are not cut off
* fix(sidebar): stop hover action tray overlapping the row below it
* fix(hosts): make real status colors toggle actually apply
* feat(local-terminal): add rail button and fix hardcoded tab label
* chore: update release notes
* fix(ai): hide assistant everywhere when admin disables it globally
* fix(automations): fix concurrency race, wire docker and internal event triggers
Claim the in-flight slot in the same tick it is checked, poll container
state for docker_event triggers, emit the internal events, apply the
schedule time zone, and expose the concurrency policy in the editor.
* fix(sidebar): rework host and credential drag-to-reorder
Adds a lock toggle in the sort menu and fixes reorder positioning,
cross-folder drops, and the duplicate drop indicator.
* chore(sidebar): drop unused sortKey prop from host and credential trees
* fix(sidebar): fix row height in click tray mode so status stripes stop overlapping
* fix(onboarding): remove add-first-host step that closed onboarding mid-flow
* fix(release): upload release notes so Mac App Store review submission stops failing
* chore: sync Crowdin translations for 2.7.0
---------
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: RawNuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: T3rM1nAt0-R <niraj.sangani91@gmail.com>
Co-authored-by: Horziox <horziox.dev@gmail.com>
Co-authored-by: William Shi <184219650@qq.com>
Co-authored-by: Carl <scarlettme@qq.com>
Co-authored-by: Raw_Nuke <67506722+RawNuke@users.noreply.github.com>
Co-authored-by: njz-cvm <njz@cvm.com>
Co-authored-by: Alexandre VARGAS <alexandre.vargas.lopez@gmail.com>
Co-authored-by: alexandre-vl <rafaelsenchais@gmail.com>
* fix: preserve remote sync references (#1092)
* fix: centralize outbound address validation (#1093)
* fix: preserve architecture in unpacked ASAR path (#1094)
* fix: allow sharing empty folders (#1096)
* fix: preserve WoL broadcast address (#1097)
* fix: deduplicate shared hosts (#1098)
* fix snippet execution result handling (#1099)
* fix SSH login alert delivery (#1100)
* fix outbound DNS lookup callback shape (#1101)
* fix OIDC verification for JWKs without alg (#1102)
* fix file manager navigation after permission errors (#1103)
* fix database persistence during container shutdown (#1104)
* fix: persist host command history setting (#1107)
* fix: recognize Windows terminal Tab events (#1109)
* fix: recognize Windows terminal Tab events
* style: format terminal key event test
* fix: export repository user record (#1111)
* fix: keep localhost database export same-origin (#1112)
* fix: support Tailscale auth in tmux monitor (#1113)
* fix: forward Android hardware keyboard keys (#1114)
* fix: expose jump tunnels to guacd (#1115)
* 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.
FixesTermix-SSH/Support#1016FixesTermix-SSH/Support#1018
* 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.
ClosesTermix-SSH/Support#1006
* 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.
ClosesTermix-SSH/Support#1011
* 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.
ClosesTermix-SSH/Support#1039
* 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.
ClosesTermix-SSH/Support#1041
* 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.
ClosesTermix-SSH/Support#1043
* 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.
ClosesTermix-SSH/Support#1044
* 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.
ClosesTermix-SSH/Support#1025
* 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.
* 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.
* 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.
* 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.
* improve settings navigation and legal disclosure (#1105)
* fix desktop preference synchronization (#1106)
* fix: use jump host SOCKS proxy settings (#1116)
* ci(deps): bump the github-actions group with 2 updates (#1086)
Bumps the github-actions group with 2 updates: [actions/setup-node](https://github.com/actions/setup-node) and [useblacksmith/setup-docker-builder](https://github.com/useblacksmith/setup-docker-builder).
Updates `actions/setup-node` from 6 to 7
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)
Updates `useblacksmith/setup-docker-builder` from 1 to 2
- [Release notes](https://github.com/useblacksmith/setup-docker-builder/releases)
- [Commits](https://github.com/useblacksmith/setup-docker-builder/compare/v1...v2)
---
updated-dependencies:
- dependency-name: actions/setup-node
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: useblacksmith/setup-docker-builder
dependency-version: '2'
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 23 updates (#1087)
Bumps the dev-patch-updates group with 23 updates:
| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.4` | `2.5.5` |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.17` | `1.2.20` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.20` | `1.1.23` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.8` | `1.3.11` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.20` | `1.1.23` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.21` | `2.1.24` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.12` | `2.1.15` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.20` | `1.1.23` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.13` | `1.1.16` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.15` | `1.2.18` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.4` | `2.3.7` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.12` | `1.1.15` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.4` | `1.4.7` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.3.0` | `1.3.3` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.4` | `1.3.7` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.18` | `1.1.21` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.13` | `1.2.16` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.3` | `6.0.4` |
| [concurrently](https://github.com/open-cli-tools/concurrently) | `10.0.3` | `10.0.4` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.6.3` | `1.6.7` |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.7` | `19.2.8` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.7` | `19.2.8` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.10` | `17.0.11` |
Updates `@biomejs/biome` from 2.5.4 to 2.5.5
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.5/packages/@biomejs/biome)
Updates `@radix-ui/react-accordion` from 1.2.17 to 1.2.20
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/accordion/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/accordion)
Updates `@radix-ui/react-alert-dialog` from 1.1.20 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/alert-dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/alert-dialog)
Updates `@radix-ui/react-checkbox` from 1.3.8 to 1.3.11
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)
Updates `@radix-ui/react-dialog` from 1.1.20 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)
Updates `@radix-ui/react-dropdown-menu` from 2.1.21 to 2.1.24
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)
Updates `@radix-ui/react-label` from 2.1.12 to 2.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)
Updates `@radix-ui/react-popover` from 1.1.20 to 1.1.23
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)
Updates `@radix-ui/react-progress` from 1.1.13 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)
Updates `@radix-ui/react-scroll-area` from 1.2.15 to 1.2.18
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)
Updates `@radix-ui/react-select` from 2.3.4 to 2.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/select/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/select)
Updates `@radix-ui/react-separator` from 1.1.12 to 1.1.15
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)
Updates `@radix-ui/react-slider` from 1.4.4 to 1.4.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slider/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slider)
Updates `@radix-ui/react-slot` from 1.3.0 to 1.3.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)
Updates `@radix-ui/react-switch` from 1.3.4 to 1.3.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/switch/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/switch)
Updates `@radix-ui/react-tabs` from 1.1.18 to 1.1.21
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)
Updates `@radix-ui/react-tooltip` from 1.2.13 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)
Updates `@vitejs/plugin-react` from 6.0.3 to 6.0.4
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.4/packages/plugin-react)
Updates `concurrently` from 10.0.3 to 10.0.4
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v10.0.3...v10.0.4)
Updates `radix-ui` from 1.6.3 to 1.6.7
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/1.6.7/packages/react/radix-ui)
Updates `react` from 19.2.7 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react)
Updates `react-dom` from 19.2.7 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom)
Updates `react-i18next` from 17.0.10 to 17.0.11
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.10...v17.0.11)
---
updated-dependencies:
- dependency-name: "@biomejs/biome"
dependency-version: 2.5.5
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-accordion"
dependency-version: 1.2.20
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-alert-dialog"
dependency-version: 1.1.23
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-checkbox"
dependency-version: 1.3.11
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-dialog"
dependency-version: 1.1.23
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-dropdown-menu"
dependency-version: 2.1.24
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-label"
dependency-version: 2.1.15
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-popover"
dependency-version: 1.1.23
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-progress"
dependency-version: 1.1.16
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-scroll-area"
dependency-version: 1.2.18
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-select"
dependency-version: 2.3.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-separator"
dependency-version: 1.1.15
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-slider"
dependency-version: 1.4.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-slot"
dependency-version: 1.3.3
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-switch"
dependency-version: 1.3.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-tabs"
dependency-version: 1.1.21
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-tooltip"
dependency-version: 1.2.16
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: "@vitejs/plugin-react"
dependency-version: 6.0.4
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: concurrently
dependency-version: 10.0.4
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: radix-ui
dependency-version: 1.6.7
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react
dependency-version: 19.2.8
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react-dom
dependency-version: 19.2.8
dependency-type: direct:development
update-type: version-update:semver-patch
dependency-group: dev-patch-updates
- dependency-name: react-i18next
dependency-version: 17.0.11
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 the prod-patch-updates group with 3 updates (#1088)
Bumps the prod-patch-updates group with 3 updates: [@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual), [jose](https://github.com/panva/jose) and [js-yaml](https://github.com/nodeca/js-yaml).
Updates `@tanstack/react-virtual` from 3.14.6 to 3.14.8
- [Release notes](https://github.com/TanStack/virtual/releases)
- [Changelog](https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md)
- [Commits](https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.8/packages/react-virtual)
Updates `jose` from 6.2.3 to 6.2.4
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.3...v6.2.4)
Updates `js-yaml` from 5.2.1 to 5.2.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.1...5.2.2)
---
updated-dependencies:
- dependency-name: "@tanstack/react-virtual"
dependency-version: 3.14.8
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: jose
dependency-version: 6.2.4
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: prod-patch-updates
- dependency-name: js-yaml
dependency-version: 5.2.2
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 undici in the prod-minor-updates group (#1089)
Bumps the prod-minor-updates group with 1 update: [undici](https://github.com/nodejs/undici).
Updates `undici` from 8.7.0 to 8.9.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.7.0...v8.9.0)
---
updated-dependencies:
- dependency-name: undici
dependency-version: 8.9.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-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 4 updates (#1090)
Bumps the major-updates group with 4 updates: [better-sqlite3](https://github.com/WiseLibs/better-sqlite3), [chalk](https://github.com/chalk/chalk), [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) and [typescript](https://github.com/microsoft/TypeScript).
Updates `better-sqlite3` from 12.11.1 to 13.0.1
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v12.11.1...v13.0.1)
Updates `chalk` from 5.6.2 to 6.0.0
- [Release notes](https://github.com/chalk/chalk/releases)
- [Commits](https://github.com/chalk/chalk/compare/v5.6.2...v6.0.0)
Updates `@testing-library/jest-dom` from 6.9.1 to 7.0.0
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.9.1...v7.0.0)
Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)
---
updated-dependencies:
- dependency-name: better-sqlite3
dependency-version: 13.0.1
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: major-updates
- dependency-name: chalk
dependency-version: 6.0.0
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: major-updates
- dependency-name: "@testing-library/jest-dom"
dependency-version: 7.0.0
dependency-type: direct:development
update-type: version-update:semver-major
dependency-group: major-updates
- dependency-name: typescript
dependency-version: 7.0.2
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>
* 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.
* 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.
* keep audit trails and recordings when a user is deleted (#1128)
audit_logs and session_recordings both referenced users with ON DELETE CASCADE,
so removing an account erased everything it had ever done. An audit trail that
disappears with the account it recorded cannot answer the question it exists for,
and a recording is evidence about a host as much as about a person.
Both foreign keys become ON DELETE SET NULL. audit_logs already denormalises
username, so an entry still names who acted once the reference is gone.
session_recordings did not, so the column is added and backfilled first —
otherwise relaxing the constraint would only trade deleted evidence for
anonymous evidence.
SQLite cannot alter a foreign key in place, so existing databases are migrated
by copy-and-swap, guarded by a PRAGMA check that makes it idempotent. Fresh
databases are created in the target shape and skip it. Recordings still cascade
from their host.
* audit the remaining remote access paths (#1129)
Only SSH terminal sessions were audited. Opening a file manager session, an RDP,
VNC or Telnet desktop, a Docker session or an SSH tunnel left no audit entry at
all — which covers most of the ways data leaves a host or a foothold is
established.
Each of those four now writes an entry when the session is established, matching
the existing ssh_connect: who, which host, from what address, and for tunnels
the endpoint and local port being forwarded.
Audit writes are fire-and-forget so they cannot delay or fail the connection,
consistent with logAudit already swallowing its own errors.
getAuditUsername was defined identically in two route files and is needed in
four more, so it moves next to logAudit.
* fix: honor lookupOptions.all in custom DNS lookup hook (#1084)
Node's happy-eyeballs autoSelectFamily calls custom dns lookup functions
with all:true and expects the full address array back. Always replying
with a single (address, family) pair corrupted net's internal state,
surfacing as "Invalid IP address: undefined" instead of a real connect
error, breaking outbound notification delivery (webhook/ntfy).
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* fix: SSH-login alerts silently dropped (channel load + auth middleware ordering) (#1083)
* fix: load notification channels on mount in AlertsPanel
Channels only loaded when the Channels tab was visited, so opening Edit
Alert Rule before ever switching to that tab showed the channel picker
as empty even when channels existed.
(cherry picked from commit caed913ee91990a853f5a048849c67ed3f7c329e)
* fix: register login-alert route before auth middleware
Global JWT auth middleware ran before this internal
service-to-service route, rejecting it with 401 before
its own IP+token check ever ran — silently dropped every
SSH-login alert. Also surface non-OK responses instead of
swallowing them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* test: add coverage for alert-notification fixes
Channel-load-on-mount, login-alert non-OK handling, and a
source-order guard for the route/auth-middleware regression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* format AlertsPanel test with prettier
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
* stop deleting audit trails, and say when they are dropped (#1132)
Two ways audit evidence still disappeared silently.
Deleting an account removed its audit entries and session recordings outright.
#1128 relaxed those foreign keys to ON DELETE SET NULL, but deleteUserAndRelatedData
deletes the rows explicitly, so the schema change had no effect on the path that
actually matters. Both repositories gain anonymizeByUserId, which nulls the
reference and leaves the row; username is already denormalised on both tables, so
entries stay attributable to whoever acted.
Separately, the log pruned itself at a hard-coded 10000 rows with no signal.
Entries well inside any retention window were discarded and nothing recorded it.
Retention is now configurable by age via AUDIT_LOG_RETENTION_DAYS, the row cap
via AUDIT_LOG_MAX_ENTRIES, and the two are reported differently: expiring an old
entry is routine and logged at info, while hitting the cap means the ceiling is
too low for how much this install audits and is logged at warn, naming the range
discarded and how to stop it.
* let the audit log leave the box (#1133)
Retention became configurable in #1132, which only helps if entries can be moved
somewhere before they expire. Until now the only way out was two GET endpoints
built for the UI.
Adds GET /audit-logs/export, taking the same filters as the list endpoint and
streaming the whole matching set as CSV or NDJSON in batches, so an export is not
bounded by the 200-row page cap and does not buffer the result set. Reading the
entire trail is itself recorded as export_audit_logs.
CSV fields starting with =, +, - or @ are prefixed with a quote. Audit rows carry
attacker-influenced values like resource names, and spreadsheet software treats
those as formulas on open.
Adds optional live forwarding to a collector via AUDIT_LOG_FORWARD_URL, with an
optional bearer token. Delivery goes through safeOutboundFetch so a misconfigured
URL cannot be turned into an internal network probe, and it is fire-and-forget:
the local write stays the source of truth and a dead SIEM must never delay or
fail the operation being audited. Repeated failures are reported five times and
then suppressed until delivery recovers, so an outage does not bury the logs it
is supposed to appear in.
* encrypt SSO secrets instead of base64-encoding them (#1135)
The OIDC client secret and LDAP bind password were stored behind an encoded:
prefix that is base64, not encryption. Anyone reading the database read the
secrets. A second path wrote the same thing behind an encrypted: prefix, which
was also base64 — and the reader even documented that it could not decrypt it.
These belong to the installation rather than to a user: sso_providers has no
userId, and the values must be readable during login, before anyone has
authenticated, so the per-user DEK used elsewhere does not apply. They are now
sealed with AES-256-GCM under the system encryption key, which already protects
other installation-level material.
Reading handles both legacy prefixes so an existing install is not locked out of
SSO login, and a legacy value is upgraded the next time the provider is saved.
The three scattered encode/decode sites are replaced by one module.
* remove the unwired field encryption boundary (#1136)
FieldEncryptionBoundary declared a full sensitive/plaintext policy for six
tables and was referenced only by its own test. Nothing in production used it.
Its policy is byte-for-byte the same as FieldCrypto.ENCRYPTED_FIELDS, which is
the copy that actually runs, so nothing is lost by deleting it. Keeping a second
list is the real risk: someone adds a field to this one, sees it classified as
sensitive, and ships something that was never encrypted.
The one apparent improvement it had — requiring an explicit recordId instead of
DataCrypto's temp-${Date.now()} fallback — turns out to guard against nothing.
decryptField derives its context from the recordId stored inside the ciphertext,
not from the argument, so a temporary id at encryption time still decrypts.
* load the database file when encryption is off (#1137)
* Groundwork for Postgres and MySQL backends (#1134)
* groundwork for postgres and mysql backends
#1127 made the repository layer dialect-agnostic. This adds the pieces needed
to actually target a second engine, as a foundation only — nothing is wired up
and sqlite remains the sole runtime path.
- DatabaseDialect covers sqlite, postgres and mysql, resolved from
DATABASE_DIALECT and defaulting to sqlite so nothing changes for existing
deployments or the desktop build
- a column kit holding the per-dialect type choices in one file: booleans are
integers on sqlite and native elsewhere, autoincrement differs three ways, and
MySQL cannot index unbounded TEXT so key columns need varchar
- settings and users declared for all three dialects as a proof slice, chosen
because between them they use every construct the real schema does
- pg and mysql2 added as dependencies
The tests build real queries for all three engines without a server, asserting
identifier quoting, placeholder style and boolean storage, so the property the
repositories depend on is verified rather than assumed.
* verify foreign keys and unique constraints port across dialects
The first slice only covered plain columns. The real schema also has 92 foreign
keys (80 cascade, 12 set null) and 14 unique columns, so the approach is only
viable if those survive the port.
Adds audit_logs and ssh_folders to the proof slice: one nullable reference with
ON DELETE SET NULL, one required reference with ON DELETE CASCADE, a unique
column, and an autoincrement surrogate key — which is spelled three different
ways underneath (integer primary key autoincrement, serial, int auto_increment).
All of it holds. Worth noting for whoever picks this up: getTableConfig is
dialect-specific and silently fails on a table from another dialect, so the test
uses each engine's own.
* generate the postgres and mysql schemas instead of hand-writing them
The proof slice showed the constructs port, but left the maintenance question
open. Three hand-written copies of 52 tables is the wrong answer: with foreign
keys the copies cross-reference each other, so a renamed table has to land in
three places consistently or a key silently points at the wrong one.
The mapping is mechanical, so a script does it. schema.ts stays the single
source of truth and schema.pg.ts / schema.mysql.ts are derived, covering all 52
tables — the column kit and the two-table portable slice are gone, since the
generator now holds those decisions.
The transforms are the ones the kit enumerated: integer-backed booleans become
native, autoincrement keys become serial or int auto_increment, real becomes
double precision or double, and any column that is a primary key, is unique, or
sits on either end of a foreign key becomes varchar because MySQL cannot index
unbounded TEXT.
> termix@2.6.0 lint
> node scripts/generate-dialect-schema.cjs --check && eslint .
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/database/routes/homepage-favicon-routes.ts
99:12 warning 'err' is defined but never used unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/database/routes/homepage-ping-routes.ts
123:12 warning 'err' is defined but never used unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/database/routes/homepage-rss-routes.ts
144:12 warning 'err' is defined but never used unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/database/routes/session-log-routes.ts
46:16 warning 'canAccessRecording' is defined but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/hosts/vault-signer-core.ts
55:12 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
75:13 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/tests/hosts/auth-manager.test.ts
18:73 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/tests/utils/shared-host-secrets-manager.test.ts
7:6 warning 'SecretRow' is defined but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/utils/auth-manager.ts
510:13 warning 'affectedUsers' is assigned a value but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/utils/notification-sender.ts
48:12 warning 'firstErr' is defined but never used unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/api/ssh-file-operations-api.ts
35:10 warning 'buildFileManagerUrl' is defined but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/components/folder-style.tsx
61:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
116:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
121:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
149:17 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/components/proxmox/ProxmoxDiscoverDialog.tsx
109:19 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
190:19 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/HomepageCanvas.tsx
345:15 warning Empty block statement no-empty
388:15 warning Empty block statement no-empty
415:15 warning Empty block statement no-empty
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/dialogs/SingleHostEditForm.tsx
24:6 warning React Hook useEffect has a missing dependency: 'filter'. Either include it or remove the dependency array. If 'setHosts' needs the current value of 'filter', you can also switch to useReducer instead of useState and read 'filter' in the reducer react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/AlertFeedWidget.tsx
93:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/CustomApiWidget.tsx
77:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/DockerActivityWidget.tsx
50:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/DockerWidget.tsx
16:10 warning Fast refresh only works when a file has exports. Move your component(s) to a separate file react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/FileManagerWidget.tsx
16:10 warning Fast refresh only works when a file has exports. Move your component(s) to a separate file react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/HostGridWidget.tsx
61:6 warning React Hook useCallback has a missing dependency: 'hostIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps
61:7 warning React Hook useCallback has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/MetricsChartWidget.tsx
168:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/PingStatusWidget.tsx
79:6 warning React Hook useEffect has a missing dependency: 'fetchAll'. Either include it or remove the dependency array react-hooks/exhaustive-deps
79:7 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/QuickConnectWidget.tsx
64:10 warning Fast refresh only works when a file has exports. Move your component(s) to a separate file react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/RecentActivityWidget.tsx
82:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
82:17 warning React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/SshQuickConnectWidget.tsx
67:6 warning React Hook useCallback has a missing dependency: 'hostIds'. Either include it or remove the dependency array react-hooks/exhaustive-deps
67:7 warning React Hook useCallback has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
99:17 warning 'online' is assigned a value but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/SshTerminalWidget.tsx
17:10 warning Fast refresh only works when a file has exports. Move your component(s) to a separate file react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/SystemOverviewWidget.tsx
72:6 warning React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/homepage/widgets/TunnelWidget.tsx
15:10 warning Fast refresh only works when a file has exports. Move your component(s) to a separate file react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/features/host-metrics/cards/CpuCard.tsx
14:10 warning 'computeChartData' is defined but never used. Allowed unused vars must match /^_/u unused-imports/no-unused-vars
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/sidebar/FolderPathPicker.tsx
15:17 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
22:17 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
/mnt/c/Users/29037/WebstormProjects/Termix/src/ui/sidebar/HostsPanel.tsx
601:52 warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
✖ 44 problems (0 errors, 44 warnings) now fails if the generated files are out of date, so editing the
schema without regenerating cannot reach main.
* select durability behaviour per dialect, and document the backends
The onWrite hook every repository receives exists to serialise the in-memory
SQLite database back to its encrypted file. On a client-server engine a
committed write is already durable and there is nothing to flush, so the factory
now installs no hook at all rather than one that does nothing. Repositories call
it as this.onWrite?.(), so none of the 43 of them change.
Also adds docs/database-backends.md, mostly to be explicit about encryption,
which is the part most likely to be misread. Field-level encryption is identical
on all three engines and covers every credential. Whole-file encryption has no
equivalent on Postgres or MySQL, so host names, snippet contents, audit entries
and backups are only as protected as the storage underneath them — that is the
operator's responsibility and the docs should not imply otherwise.
* generate DDL with drizzle-kit, and give settings a synchronous path
Two of the three remaining blockers.
DDL: db/index.ts hand-writes 67 CREATE TABLE statements and 122 ADD COLUMN
migrations, all in SQLite dialect. Rather than port them, drizzle-kit now
generates migrations from the schema modules — 817 lines for Postgres, 869 for
MySQL, with the type mapping already correct because the schemas it reads are
themselves generated.
> termix@2.6.0 schema:migrations
> drizzle-kit generate --config=drizzle.config.pg.ts && drizzle-kit generate --config=drizzle.config.mysql.ts
Reading config file '/mnt/c/Users/29037/WebstormProjects/Termix/drizzle.config.pg.ts'
52 tables
alert_firings 11 columns 0 indexes 2 fks
alert_rule_channels 3 columns 0 indexes 2 fks
alert_rules 11 columns 0 indexes 2 fks
api_keys 9 columns 0 indexes 1 fks
audit_logs 13 columns 0 indexes 1 fks
c2s_tunnel_presets 8 columns 0 indexes 1 fks
command_history 5 columns 0 indexes 2 fks
dashboard_service_links 8 columns 0 indexes 1 fks
dismissed_alerts 4 columns 0 indexes 1 fks
file_manager_pinned 6 columns 0 indexes 2 fks
file_manager_recent 6 columns 0 indexes 2 fks
file_manager_shortcuts 6 columns 0 indexes 2 fks
homepage_items 9 columns 0 indexes 1 fks
homepage_layouts 4 columns 0 indexes 1 fks
host_access 11 columns 0 indexes 5 fks
host_health_checks 7 columns 0 indexes 2 fks
host_health_history 8 columns 0 indexes 2 fks
host_metrics_history 8 columns 0 indexes 1 fks
host_metrics_preferences 6 columns 0 indexes 2 fks
ssh_data 94 columns 0 indexes 6 fks
network_topology 5 columns 0 indexes 1 fks
notification_channels 7 columns 0 indexes 1 fks
opkssh_tokens 12 columns 0 indexes 2 fks
recent_activity 6 columns 0 indexes 2 fks
roles 8 columns 0 indexes 0 fks
session_recordings 15 columns 0 indexes 3 fks
session_share_participants 6 columns 0 indexes 2 fks
session_shares 15 columns 0 indexes 3 fks
sessions 11 columns 0 indexes 1 fks
settings 2 columns 0 indexes 0 fks
shared_host_secrets 15 columns 0 indexes 3 fks
snippet_access 8 columns 0 indexes 4 fks
snippet_folders 8 columns 0 indexes 1 fks
snippets 11 columns 0 indexes 1 fks
ssh_credential_usage 5 columns 0 indexes 3 fks
ssh_credentials 21 columns 0 indexes 1 fks
ssh_folders 9 columns 0 indexes 2 fks
sso_providers 8 columns 0 indexes 0 fks
sync_tombstones 5 columns 0 indexes 1 fks
termix_identities 6 columns 0 indexes 1 fks
termix_identity_ca 8 columns 0 indexes 2 fks
termix_identity_keys 12 columns 0 indexes 3 fks
tmux_session_tags 6 columns 0 indexes 2 fks
transfer_recent 7 columns 0 indexes 3 fks
trusted_devices 8 columns 0 indexes 1 fks
user_open_tabs 9 columns 0 indexes 2 fks
user_preferences 23 columns 0 indexes 1 fks
user_roles 5 columns 0 indexes 3 fks
users 20 columns 0 indexes 0 fks
vault_profiles 18 columns 0 indexes 1 fks
vault_tokens 8 columns 0 indexes 2 fks
webauthn_credentials 12 columns 0 indexes 1 fks
No schema changes, nothing to migrate 😴
Reading config file '/mnt/c/Users/29037/WebstormProjects/Termix/drizzle.config.mysql.ts'
Reading schema files:
/mnt/c/Users/29037/WebstormProjects/Termix/src/backend/database/db/schema.mysql.ts
52 tables
alert_firings 11 columns 0 indexes 2 fks
alert_rule_channels 3 columns 0 indexes 2 fks
alert_rules 11 columns 0 indexes 2 fks
api_keys 9 columns 0 indexes 1 fks
audit_logs 13 columns 0 indexes 1 fks
c2s_tunnel_presets 8 columns 0 indexes 1 fks
command_history 5 columns 0 indexes 2 fks
dashboard_service_links 8 columns 0 indexes 1 fks
dismissed_alerts 4 columns 0 indexes 1 fks
file_manager_pinned 6 columns 0 indexes 2 fks
file_manager_recent 6 columns 0 indexes 2 fks
file_manager_shortcuts 6 columns 0 indexes 2 fks
homepage_items 9 columns 0 indexes 1 fks
homepage_layouts 4 columns 0 indexes 1 fks
host_access 11 columns 0 indexes 5 fks
host_health_checks 7 columns 0 indexes 2 fks
host_health_history 8 columns 0 indexes 2 fks
host_metrics_history 8 columns 0 indexes 1 fks
host_metrics_preferences 6 columns 0 indexes 2 fks
ssh_data 94 columns 0 indexes 6 fks
network_topology 5 columns 0 indexes 1 fks
notification_channels 7 columns 0 indexes 1 fks
opkssh_tokens 12 columns 0 indexes 2 fks
recent_activity 6 columns 0 indexes 2 fks
roles 8 columns 0 indexes 0 fks
session_recordings 15 columns 0 indexes 3 fks
session_share_participants 6 columns 0 indexes 2 fks
session_shares 15 columns 0 indexes 3 fks
sessions 11 columns 0 indexes 1 fks
settings 2 columns 0 indexes 0 fks
shared_host_secrets 15 columns 0 indexes 3 fks
snippet_access 8 columns 0 indexes 4 fks
snippet_folders 8 columns 0 indexes 1 fks
snippets 11 columns 0 indexes 1 fks
ssh_credential_usage 5 columns 0 indexes 3 fks
ssh_credentials 21 columns 0 indexes 1 fks
ssh_folders 9 columns 0 indexes 2 fks
sso_providers 8 columns 0 indexes 0 fks
sync_tombstones 5 columns 0 indexes 1 fks
termix_identities 6 columns 0 indexes 1 fks
termix_identity_ca 8 columns 0 indexes 2 fks
termix_identity_keys 12 columns 0 indexes 3 fks
tmux_session_tags 6 columns 0 indexes 2 fks
transfer_recent 7 columns 0 indexes 3 fks
trusted_devices 8 columns 0 indexes 1 fks
user_open_tabs 9 columns 0 indexes 2 fks
user_preferences 23 columns 0 indexes 1 fks
user_roles 5 columns 0 indexes 3 fks
users 20 columns 0 indexes 0 fks
vault_profiles 18 columns 0 indexes 1 fks
vault_tokens 8 columns 0 indexes 2 fks
webauthn_credentials 12 columns 0 indexes 1 fks
No schema changes, nothing to migrate 😴 regenerates both.
Settings: 27 call sites read settings synchronously, during startup and inside
request handlers. better-sqlite3 can do that; Postgres and MySQL cannot, and
making all 27 async would push await through code that has no reason to be
asynchronous. Settings are a handful of rarely-changing rows read constantly, so
they are cached in full — primed at startup, kept in step by SettingsRepository
on every set/delete/deleteLike. SQLite keeps reading the database directly and
stays authoritative; only the other engines use the cache.
Opening a connection is still not done. DatabaseContext.drizzle is typed as
BetterSQLite3Database and 43 repositories depend on that inference; the three
drizzle instance types are not interchangeable, so widening it is a design
decision rather than a mechanical change.
* exclude drizzle-kit output from prettier
The generated migrations and snapshots are tool output; their formatting is
drizzle-kit's to decide, and prettier cannot parse the .sql files at all.
* absorb the RETURNING gap so mysql stays reachable
MySQL has no RETURNING clause and drizzle's mysql-core does not expose the
method, while 156 call sites here read the result of a write. That is the real
blocker for MySQL, not the connection layer.
Classifying those call sites showed the split is favourable: 92 of them only
read .length, which every engine reports — as a returned array on sqlite and
postgres, as affectedRows on MySQL. rowsAffected() reads both shapes, so those
sites need no change in query shape. insertedId() does the same for the
autoincrement key, which MySQL reports as insertId.
What is left is the ~34 sites that genuinely consume the returned rows. Those
cannot be emulated without reading first, which needs a transaction to stay
correct under concurrency, so they will be handled individually rather than
behind a helper that quietly adds a round trip. supportsReturning() is the seam
for that.
Identifying the mysql2 result by its own fields rather than by array shape
matters: it hands back [ResultSetHeader, fields], which is an array, so shape
alone cannot tell it apart from a returning() result.
* name the portable database type, and open remote connections
Two pieces of the connection layer.
drizzle's three Database classes share no base class and their signatures are
incompatible, so there is no honest type that covers all three: a union is not
callable and a generic would have to be threaded through 43 repositories and
every method on them. DatabaseContext.drizzle is now PortableDatabase, still
the SQLite type underneath, but named and documented as the deliberate
approximation it is. What makes it safe is that the equivalence is asserted in
multi-dialect.test.ts rather than assumed, and the one place the surfaces truly
differ — RETURNING — is handled explicitly in mutation-result.ts.
connect.ts opens Postgres and MySQL from DATABASE_URL, with the schema module
and driver imported lazily so neither is loaded on a SQLite deployment. The URL
scheme is checked against the configured dialect first: a postgres:// URL with
DATABASE_DIALECT=mysql otherwise surfaces as a driver error deep in a stack
that never mentions the actual misconfiguration.
* open postgres and mysql at startup
* count writes without RETURNING
* read affected rows without RETURNING on mysql
* insert without RETURNING, and split the sync transactions
* stop pretending the generated schemas are used at runtime
* run the dialect checks in CI
* mysql rejects a bare CURRENT_TIMESTAMP default on text
* make the read-back mismatch loud, and stop the next bare returning()
* run the repository tests on the real schema
* skip the byte-level assertions off sqlite
* move generated ids past the seeded ones
* keep the export order the same on every engine
* stop reading better-sqlite3 fields off every write
* read counts as numbers, not whatever the driver returns
* make the fixture usable against a live server
* upsert on the engine that has no ON CONFLICT
* run the repository suite on all three engines in CI
* mysql cannot index a text column without a length
* document how to actually run on postgres or mysql
* keep the sqlite-era migrations off the other engines
* concat strings in a way mysql agrees with
* run every repository test on every engine
* bound how long replicas can disagree about settings
* generate the sqlite migrations alongside the others
* Bump version from 2.6.0 to 2.6.1
* resolve the dialect in the repository factory instead of assuming sqlite (#1143)
createCurrentRepositoryContext() hardcoded `dialect: "sqlite"` while the
runtime already carried all three engines. That field is not decoration:
returning.ts reads it to decide whether it can ask for RETURNING, and
whether an upsert spells itself onConflictDoUpdate or onDuplicateKeyUpdate.
Reporting sqlite while connected to MySQL means the first upsert calls
onConflictDoUpdate on a mysql2 insert builder, which does not have it --
a TypeError, not a rejected query, as the note in returning.ts warned.
So MySQL never worked outside the tests, and Postgres worked only because
it also supports RETURNING and shares the conflict syntax.
Three things were supposed to catch this and none could. The repository
suite builds its own DatabaseContext in test-support.ts, verify-dialects.mjs
builds its own, and the CI matrix runs both against real Postgres and MySQL
containers -- all of them bypassing the one function the application calls.
Green on three engines, broken on two.
Resolve it from the environment, and test the factory itself rather than a
hand-built context: the default, each configured dialect, the write hook it
installs only for sqlite, and that an unsupported value throws rather than
falling back. Reverting the fix fails two of them.
FixesTermix-SSH/Support#282
* fix remote sync stalling after the first pass and never propagating deletions (#1140)
The incremental cursor never matched. updated_at/deleted_at are TEXT columns
written by CURRENT_TIMESTAMP ("2026-07-29 10:11:21"), while the client sends
an ISO 8601 since ("2026-07-29T10:06:55.172Z"). Both comparisons are lexical
and ' ' sorts below 'T', so a newer row lost at position 10 and every
?since= query came back empty. Pass 1 syncs everything (since is null) and
persists a cursor; every pass after it returns nothing with lastError: null
and reports success. Normalize since into the stored shape on the way in,
leaving an already-normalized value alone -- parsing that would treat it as
local time and, west of UTC, push the cursor past unsynced rows.
POST /sync/tombstones was unreachable. It was registered after
POST /:entityType, and "tombstones" is a valid :entityType, so the wildcard
answered it with 400 "Unknown entity type" and the handler never ran. The
pass has no per-entity error handling, so that 400 also discarded the state
of every entity type already synced in the same pass. Move it ahead of the
wildcards.
The tombstone guard consulted the incremental window. A row deleted on one
side and untouched on the other -- the shape every ordinary deletion takes
once the two sides converge -- is not in that window, so the tombstone was
skipped, and skipped again on each later pass as it slid out of its own
window. The guard cannot just be dropped: recording a tombstone for a row
that was already gone hands the sender a fresh one to push back, and the two
trade the same deletion forever. So only a delete that removed something
records a tombstone, which makes the endpoint idempotent and lets the client
push every tombstone unconditionally.
Deletions missed while the cursor was broken stay missed -- their tombstones
predate the persisted cursor. Ordinary edits do come through, since the
row's updatedAt is still newer than it.
FixesTermix-SSH/Support#1050FixesTermix-SSH/Support#1051
* report why every JWKS fetch failed instead of swallowing the reason (#1142)
An OIDC login that cannot reach the provider's keys ends in "Failed to
fetch JWKS from any URL" and nothing else. Getting there discards
everything worth knowing: a non-2xx response hit an empty else branch, a
thrown request hit a bare `continue`, and discovery only logged when it
threw -- a 404 or a document without jwks_uri passed in silence. An
administrator cannot tell an issuer URL typo from a proxy, a private CA,
or an outage at the provider, and neither can anyone reading the report.
Collect each attempt with its reason and put them in the thrown error.
It reaches the log through the existing "OIDC callback failed" handler;
the browser still gets the same generic message it did before.
Unwrapping the cause is the part that matters: undici reports every
transport failure as "fetch failed" and hangs the real reason -- ENOTFOUND,
ECONNREFUSED, a certificate that will not verify -- off error.cause. An
attempt list built from the outer messages would be as useless as the
single line it replaces.
Also require jwks_uri to be a string before using it, so a malformed
discovery document is reported as such rather than as a failed fetch of
"[object Object]".
Refs Termix-SSH/Support#1047
* restore the closing quote on the version string (#1147)
"Bump version from 2.6.0 to 2.6.1" (2a66775) wrote
"version": "2.6.1,
dropping the closing quote, so package.json has not been valid JSON since.
Anything that parses it fails: npm install, npm run build, and every CI run
on this branch -- vitest cannot even load its config, because vite reads
package.json before it gets to the test files.
2.6.1 cannot be built or released until this is fixed, which is why it goes
in on its own rather than riding along with anything else.
* Revert "fix remote sync stalling after the first pass and never propagating deletions (#1140)" (#1146)
This reverts commit ca7abf8426.
Reverted for process, not for content. Both defects were reported by
@kacperpietrzyk in Support#1050 and Support#1051, and he opened #1138 and
#1139 fixing them 4.5 hours before #1140 was filed. Merging #1140 made two
PRs from the person who found and diagnosed the bugs redundant.
#1138 and #1139 stand on their own: the same root-cause analysis, complete
regression tests, and a tombstone guard that only pays for its extra lookup
on a pass that actually carries a deletion. There is no technical reason to
prefer the reverted commit over them.
The sync fixes land through those two PRs instead.
* fix: make sync deletions reach the other side (#1139)
* fix: apply sync tombstones to rows outside the incremental window
Deletions never reached the other side. `syncEntity` decides whether to apply a
tombstone by looking the row up in `localBySyncId` / `remoteBySyncId`, which are
built from `pullSide(..., since)` -- the incremental window. A row deleted on one
side and untouched on the other is by definition absent from that window, which
is the shape every ordinary deletion takes once the two sides have converged, so
the tombstone was silently skipped and never retried.
The guard cannot simply be dropped. `POST /sync/tombstones` records a tombstone
on the receiving side, so an unconditional push would give the other side a fresh
tombstone to push back on the following pass, and the two would trade deletions
forever.
Instead ask the receiving side what it still holds, ignoring the window, and only
when there is a deletion to apply -- so an ordinary pass costs nothing extra, and
a pass carrying a deletion costs one additional list per affected entity type.
Once the row is gone the push stops, so nothing ping-pongs.
Note this only becomes observable together with the cursor fix in
Termix-SSH/Support#1050: while that defect is present the tombstone endpoint
returns nothing at all, so there is no tombstone to apply in the first place.
Refs Termix-SSH/Support#1034
* fix: make the sync tombstone endpoint reachable
`POST /sync/tombstones` was registered after `POST /:entityType`, and Express
matches in registration order, so every deletion push was swallowed by the
wildcard: "tombstones" is a perfectly good value for :entityType, fails
isValidEntityType, and comes back as 400 "Unknown entity type". The handler
below it has never run.
Registering the literal path before the parameterised one restores it. The
regression test reads the router stack rather than the source, so a future
re-order fails the test rather than silently disabling deletions again.
The GET pair is unaffected -- "/:entityType/tombstones" and "/:entityType" have
different segment counts, so they cannot shadow each other.
* feat: add host export dialog with host and field selection (#1108)
* fix: compare sync cursors independently of timestamp layout (#1138)
* fix: compare sync cursors independently of timestamp layout
Incremental sync returned nothing after its first pass. `GET /sync/:entityType`
filters with `gt(table.updatedAt, since)` on a TEXT column, and the tombstone
endpoint does the same through `listSince`, but the two sides of that comparison
are written in different layouts: the columns default to `CURRENT_TIMESTAMP`
("2026-07-29 10:11:21") while the desktop engine sends `new Date().toISOString()`
("2026-07-29T10:06:55.172Z"). Text comparison is decided at position 10, where
' ' (0x20) sorts below 'T' (0x54), so the predicate answers on layout rather than
on time and is false for every CURRENT_TIMESTAMP row however new it is.
The engine only sends a cursor from the second pass onward, so pass 1 synced
everything and passes 2..n pulled zero rows and zero tombstones while reporting
success -- edits and deletions silently stopped propagating in both directions.
This was masked until now: before the reference fix in #1092 the loop threw
before persisting state, so the cursor never advanced past null and every cycle
was a full sync.
Comparing "YYYY-MM-DD HH:MM:SS" on both sides is layout-independent. `replace`
and `substr` are used rather than `datetime()` to keep the expression portable
across engines, since the repository layer is deliberately drizzle-only. The
comparison is `>=` because normalising truncates sub-second precision, and a
strict `>` would permanently skip rows written in the cursor's own second; the
re-sent boundary rows are a no-op, as the engine pushes only when one side is
strictly newer.
`updatedAt` is written in both layouts across the codebase (14 sites use
toISOString, 11 use CURRENT_TIMESTAMP), so the tests cover rows of each kind.
ClosesTermix-SSH/Support#1050
* test: seed the cursor tests against the migrated schema
#1134 moved schema creation into the repository test harness, so the
hand-written CREATE TABLE blocks here collided with tables that already
existed. Seeding into the real tables instead surfaced two constraints the
local definitions had papered over: the harness enables foreign keys and
both `sync_tombstones.user_id` and `ssh_credentials.user_id` reference
`users`, so the owning row has to be seeded first; and `auth_type` is NOT
NULL with no default, unlike the local copy.
`exec` is awaited, since it only returns synchronously on SQLite.
The assertions are unchanged.
* Make Proxmox guest discovery and import reliable over a jump host (#1144)
* fix: repair unterminated version string in package.json
The version field on dev-2.6.1 reads "2.6.1, (no closing quote), which
makes package.json invalid JSON and breaks every npm invocation on the
branch. Close the string so the branch builds.
* fix(proxmox): reliable guest discovery and import over jump hosts
Importing Proxmox guests from a node reachable only through a jump host
(with the guests behind the same jump) failed in a chain of small ways.
- Discovery timed out intermittently: execCommand capped every pvesh call
at 8s, but a single call over a jump measured ~8.3s. Raised to 25s for
core calls and 12s for best-effort agent/interface lookups.
- No IPs were resolved (so nothing imported): resolveIp fanned out 6
concurrent pvesh calls; on a small node they contend (3 concurrent
already exceeded the timeout), so every IP came back empty. Lowered
CONCURRENCY to 2.
- RDP guests aborted the whole sync via NOT NULL on ssh_data.username;
use "" instead of null (matches the normal create path).
- Guests without a resolvable IP (e.g. QEMU with no guest agent) were
skipped entirely; they now import with a 0.0.0.0 placeholder, and
re-sync preserves any manually entered IP (guest.ip || existing.ip).
- Manual import did not inherit the source host's jump chain or
credential (guests ended up unreachable with authType "none"). The
discovery result now carries the source jumpHosts, and
resolveProxmoxImportAuth uses an available credential even under the
default "password" authType (explicit secretless choices still win).
- Long discoveries had no feedback and fought client/proxy timeouts;
added an SSE endpoint GET /proxmox/discover/stream (heartbeat + n/N
progress), keeping POST /discover as a fallback. Also always render the
IP cell in the discovery table so IP-less rows stay aligned.
Adds a unit test for resolveProxmoxImportAuth covering the credential
inheritance behaviour.
* test(proxmox): lock resolveProxmoxImportAuth matrix on both copies; fix agent secretless drift
- extract the backend decision into src/backend/database/routes/proxmox-import-auth.ts
(leaf module mirroring the UI copy) so it is unit-testable without pulling the
whole backend module graph into the test env
- add src/backend/tests/database/routes/proxmox-import-auth.test.ts asserting the
shared matrix (lifted from #1141, thanks @ZacharyZcR)
- consolidate the UI test into src/ui/tests/components/proxmox/proxmox-import-auth.test.ts
and drop the duplicate src/ui/tests/proxmox/ copy
- add 'agent' to the UI SECRETLESS_AUTH_TYPES: the one real auth type where the two
copies still diverged (UI -> credential, backend -> passthrough)
* fix(hosts): parse portKnockSequence JSON in host-resolver (#1149)
host-resolver JSON-parses jumpHosts/tunnelConnections/statsConfig/
terminalConfig/socks5ProxyChain/quickActions but NOT portKnockSequence.
Empty knock is stored as the string "[]" (UI save of empty array); the
terminal code then checks portKnockSequence.length > 0 on the STRING, so
"[]".length === 2 is truthy -> logs 'Loaded 2 port knock(s)' and attempts
a bogus knock. Real knock sequences (JSON string) are likewise never
parsed to the Array<{port,...}> that performPortKnocking expects, so a
genuine knock would never fire. Parse portKnockSequence like the other
JSON columns: '[]' -> [] (length 0, no knock), real seq -> array.
Adds unit tests for both cases.
Co-authored-by: XtraLarge <xtralarge@users.noreply.github.com>
* Feature request map OIDC provider groups to RBAC roles (#1148)
* Bump version from 2.6.0 to 2.6.1 in package-lock.json
* Fix formatting issue in package-lock.json
* Feature request map OIDC provider groups to RBAC roles
Group membership from an OIDC provider currently drives only a single
boolean: OIDC_ADMIN_GROUP toggles isAdmin and switches the user between
the built-in `admin` and `user` roles. There is no way to map a provider
group onto a custom role, so deployments that use host_access grants for
environment-scoped access (e.g. a role that can reach staging hosts and
another that can reach production) have to assign those roles by hand
for every user.
Add OIDC_ROLE_MAP, a comma- or newline-separated list of `group:role`
pairs, reconciled against the user's roles on each OIDC login:
OIDC_ROLE_MAP=devops-interns:devops-intern,devops-seniors:devops-senior
Only roles named in the map are ever added or removed. Roles assigned by
hand, and the admin/user pair maintained by the existing admin-group
sync, are deliberately left untouched so the two mechanisms don't fight
each other. Group names are matched case-insensitively with leading
slashes stripped, so providers that emit full group paths (Keycloak's
"Full group path" option) work without extra configuration.
Reuses the existing extractOidcGroups claim handling, so custom claim
paths via OIDC_GROUP_CLAIM are supported too, and invalidates the
permission cache when roles change so new grants apply to the session
that triggered the sync. Malformed map entries are skipped and a failed
sync is logged but non-fatal — neither can block a valid login.
Adds unit tests for the parser and resolver covering full group paths,
multi-group membership, colons in group names and malformed input.
---------
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
* feat: support for overriding shared host ssh credentials (#1145)
* Bump version from 2.6.0 to 2.6.1 in package-lock.json
* Fix formatting issue in package-lock.json
* feat: support for overriding ssh credentials
---------
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
* ci(deps): bump the github-actions group with 2 updates (#1150)
* Bump version from 2.6.0 to 2.6.1 in package-lock.json
* Fix formatting issue in package-lock.json
* Update README to remove Tailscale and add Ginernet
Removed Tailscale logo and link from the README. Added Ginernet logo and link.
* Update README.md
* ci(deps): bump the github-actions group with 2 updates
Bumps the github-actions group with 2 updates: [actions/setup-node](https://github.com/actions/setup-node) and [useblacksmith/setup-docker-builder](https://github.com/useblacksmith/setup-docker-builder).
Updates `actions/setup-node` from 6 to 7
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)
Updates `useblacksmith/setup-docker-builder` from 1 to 2
- [Release notes](https://github.com/useblacksmith/setup-docker-builder/releases)
- [Commits](https://github.com/useblacksmith/setup-docker-builder/compare/v1...v2)
---
updated-dependencies:
- dependency-name: actions/setup-node
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: useblacksmith/setup-docker-builder
dependency-version: '2'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
...
Signed-off-by: dependabot[bot] <support@github.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(deps): bump the prod-minor-updates group with 3 updates (#1154)
* Bump version from 2.6.0 to 2.6.1 in package-lock.json
* Fix formatting issue in package-lock.json
* Update README to remove Tailscale and add Ginernet
Removed Tailscale logo and link from the README. Added Ginernet logo and link.
* Update README.md
* chore(deps): bump the prod-minor-updates group with 3 updates
Bumps the prod-minor-updates group with 3 updates: [axios](https://github.com/axios/axios), [motion](https://github.com/motiondivision/motion) and [undici](https://github.com/nodejs/undici).
Updates `axios` from 1.18.1 to 1.19.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.18.1...v1.19.0)
Updates `motion` from 12.42.2 to 12.43.0
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v12.42.2...v12.43.0)
Updates `undici` from 8.7.0 to 8.9.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.7.0...v8.9.0)
---
updated-dependencies:
- dependency-name: axios
dependency-version: 1.19.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: motion
dependency-version: 12.43.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
- dependency-name: undici
dependency-version: 8.9.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: prod-minor-updates
...
Signed-off-by: dependabot[bot] <support@github.com>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix: data guard test failure
* chore(deps): bump 23 dependencies and fix dialect-unsafe queries
Applies the non-major updates from the open dependabot PRs directly,
since dependabot rebases against main and could not resolve its
lockfiles against this branch. Holds back typescript 7 and jsdom 30;
those majors need their own pass.
Reformats with prettier 3.9.6, which collapses short union types onto
one line. Formatting only: the compiled backend output is byte for byte
identical.
Also fixes two lint errors in the shared host auth override repository,
where onConflictDoUpdate and .returning() are SQLite-only and broke the
Postgres and MySQL builds, and drops unused imports left over from the
shared host auth override merge.
* chore: reversal of legal work
* feat: improve pin side rail button position and added env var for telemetrics
* Add Ctrl+F terminal search (#1156)
* Bump version from 2.6.0 to 2.6.1 in package-lock.json
* Fix formatting issue in package-lock.json
* Update README to remove Tailscale and add Ginernet
Removed Tailscale logo and link from the README. Added Ginernet logo and link.
* Update README.md
* Add Ctrl+F terminal search
---------
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
* fix: host export dialog using incorrect widths
* fix: made logger display expanded errors
* feat: added support for multi disk usage in file manager and host metrics
* chore: harden nginx headers and improve static asset caching
* chore: format
* chore: update release notes
* fix: default font size to md instead of lg
* feat: support Tailscale SSH check mode
* chore: sync Crowdin translations for 2.6.1
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brennan Neoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: kacperpietrzyk <105545577+kacperpietrzyk@users.noreply.github.com>
Co-authored-by: Max <50905012+maxiwolleb@users.noreply.github.com>
Co-authored-by: XtraLarge <eMail@WilliWerres.de>
Co-authored-by: XtraLarge <xtralarge@users.noreply.github.com>
Co-authored-by: Devin Dissanayaka <dsdissanayaka2002@gmail.com>
Co-authored-by: Peter Cinibulk <petercinibulk@gmail.com>
Co-authored-by: Med Ali Ezzeddine <47082236+xDaly@users.noreply.github.com>
Thanks for the report. A maintainer reads every issue, even when there is no reply right away. We usually reply once there is a fix or a workaround.
If you came from **Report an issue** in Termix, your versions and environment are already filled in below.
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type:textarea
id:what-happened
attributes:
label:What happened?
description:Describe the bug. Screenshots help a lot.
validations:
required:true
- type:textarea
id:steps
attributes:
label:Steps to reproduce
description:As few steps as you can.
placeholder:|
1.
2.
3.
validations:
required:true
- type:textarea
id:expected
attributes:
label:What did you expect?
description:What should have happened instead?
validations:
required:false
- type:input
id:termix-version
attributes:
label:Termix version
description:Shown in Settings under About, or in your User Profile.
placeholder:"e.g. 26.10.0"
validations:
required:true
- type:dropdown
id:platform
attributes:
label:Platform
description:Where do you see the problem?
options:
- "Web browser"
- "Desktop app (Windows)"
- "Desktop app (macOS)"
- "Desktop app (Linux)"
- "Mobile app (iOS)"
- "Mobile app (Android)"
- "Other"
validations:
required:true
- type:dropdown
id:install-method
attributes:
label:Server install
description:How is the Termix server installed?
options:
- "Docker"
- "Manual build"
- "Proxmox community script"
- "Desktop app (built-in server)"
- "Not sure"
validations:
required:true
- type:textarea
id:environment
attributes:
label:Environment
description:Browser and OS, plus anything special about your setup (reverse proxy, database, SSO).
placeholder:|
Web, Chrome on Windows. Behind nginx, Postgres.
validations:
required:false
- type:textarea
id:logs
attributes:
label:Logs
description:Errors from the browser console, `docker logs termix`, or the error Termix shows. This is shown as code, so no backticks needed.
render:text
validations:
required:false
- type:textarea
id:context
attributes:
label:Anything else?
description:Links, config, or anything that might help.
validations:
required:false
- type:checkboxes
id:checks
attributes:
label:Before you submit
options:
- label:I searched open and closed issues and this is not a duplicate
required:true
- label:I am on the latest version, or I said which version I am on
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type:markdown
attributes:
value:|
Thanks for the idea. Explain the problem first, then how you would solve it.
- type:textarea
id:problem
attributes:
label:What problem would this solve?
description:What are you trying to do, and what gets in the way today?
validations:
required:true
- type:textarea
id:solution
attributes:
label:What would you like?
description:Describe how it should work.
validations:
required:true
- type:textarea
id:alternatives
attributes:
label:Alternatives
description:Anything you tried or considered instead.
CHANGES="- No new commits since the last beta (or this is the first beta build)."
fi
cat > BETA_RELEASE_BODY.md << EOF
> [!WARNING]
> This is an automated weekly beta build, snapshotted from the \`${{ needs.prep.outputs.dev_branch }}\` branch. It is not a stable release: it may contain unfinished features, regressions, or breaking changes, and this tag is overwritten every week. Do not run it in production.
>
> Found a bug? [Open a Beta Feedback report](https://github.com/Termix-SSH/Support/issues/new?template=beta_feedback.yml) and mention this build: \`${{ needs.prep.outputs.beta_version }}\`.
**Docker image:** \`ghcr.io/lukegus/termix:beta\` / \`docker.io/bugattiguy527/termix:beta\` (rolling), or pin to \`:beta-${{ needs.prep.outputs.beta_version }}\` for this exact build.
# DIRTY / CONFLICTING = a real conflict. Try a rebase once (label it so
# we can tell next time); if it is STILL conflicting on a later run
# despite already being labelled, the rebase failed for good - close it
# so Dependabot reopens a fresh PR against the current dev branch.
if [ "$MERGEABLE" = "CONFLICTING" ] || [ "$STATE" = "DIRTY" ]; then
if has_conflict_label "$PR"; then
echo " PR #$PR still conflicts after a prior rebase request; closing so Dependabot reopens it fresh."
gh pr close "$PR" --repo "$REPO" --delete-branch \
--comment "Closing: this PR still conflicts with $DEV_BRANCH after a rebase attempt (its changes are likely already merged). Dependabot will reopen a fresh PR computed against the current $DEV_BRANCH."
else
echo " PR #$PR conflicts with $DEV_BRANCH; requesting a rebase and labelling it."
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/vVERSION_PLACEHOLDER/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/vVERSION_PLACEHOLDER/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml
sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml
- Choose the layout of the whole app in Settings, Appearance. Admins can also set the default layout and theme for everyone
- Plugins can add layouts and themes
- A repair screen shows when the chosen layout cannot load, with ways to update, switch, reset or turn it off. Open it any time with ?safe-layout=1, or for everyone with TERMIX_SAFE_LAYOUT=1
- Hovering a theme in Settings previews it
- Restore the bundled copy of a plugin from the repair screen, which also pins the shipped version
- Switching layouts closes the tabs only the old layout could show, like its Settings tab
- A chosen layout whose plugin was uninstalled falls back to the default instead of the repair screen
- Filter plugins by type, like layouts, themes or tabs, from a filter list beside the results
- Browse shows plugins a page at a time
- Plugins that need a newer Termix are hidden in Browse, with a toggle to show them
- Hosts can be managed from a config file through a plugin. Managed hosts are read only for everyone, but pins and order still work
- A plugin can block adding hosts by hand, so every host comes from one place
- Plugins can set login rules: sign out after a time without activity, limit how long a session lasts, turn off remember me and require a second factor
- A user who must have a second factor but has none can sign in only to set one up
- Admins can redirect HTTP to HTTPS and send HSTS while HTTPS is on. Off by default so reverse proxies keep working
### Changed
- The icon rail, sidebar, tab bar, dock, Dashboard, Manage, Settings and command palette now come from a bundled layout. It can be swapped for another layout but not removed, and a fix to it no longer needs a Termix release
- Your theme is saved to your account so your other devices use it too, unless you keep settings on this device only
- The themes besides light and dark now come from a bundled themes plugin. It can be swapped but not removed, and Termix keeps a basic light and dark theme of its own
- A theme that no longer exists shows as System instead of an unstyled page
- Installs and updates from a registry check the Termix version a plugin needs, not only its plugin API
- Host online and offline dots now come from the Host Status plugin. The check interval setting moved to its settings and keeps its value. Without the plugin hosts show no dot
- Audit log forwarding and the CSV and NDJSON export now come from the Audit Tools plugin. The collector URL and token move to its settings on upgrade. The audit log viewer, retention and the audit log itself stay in Termix
- Client tunnels on your own computer and opening a host in Windows Remote Desktop moved out of the desktop app into plugins, so fixes to them no longer need a new desktop build
- The local terminal, the local side of the file manager with its transfers, and opening a file in an external editor moved out of the desktop app into plugins, so fixes to them no longer need a new desktop build
- The recent activity list and the first run steps for the preset and for theme, accent, font and language now come from bundled plugins. Existing entries and choices are kept, and people who went through those steps are not asked again
- Branding (app name, tagline and logo) is now saved by the bundled layout plugin. An existing setup keeps its branding, and the sign in screen still reads it from Termix
- Without the plugin that stores recent activity, the list stays empty and nothing is recorded
- Importing hosts from a JSON or SSH config file and exporting hosts now come from the Import and Export plugin. Exports are now written to the audit log. Without the plugin there is no import or export
- Generating and checking SSH keys in the credential editor and deploying a key to a host now come from the Credential Tools plugin. Deploys are now written to the audit log. Without the plugin those buttons are hidden
### Removed
- The desktop app no longer has its own client tunnel and native Remote Desktop code
## 26.10.2
### Added
- Hide sidebar after opening, in Customize Sidebar, closes the host list once you open a host
- Sort the dashboard's Host Status card by name, IP address or status
- Windows on ARM builds of the desktop app (installer, MSI and portable)
- Plugins get an event when a host is added
- Plugins can start a short-lived session as a user and read the user's language, for tasks that run without a browser
- Plugins can pin a panel to the right edge of the window that pushes the page over
### Changed
- The Docker Compose files no longer start a guacd container. npm run dev -- --guacd also points remote desktop at the guacd it starts
- The side menu now opens over the page when it expands on hover, so open sessions like remote desktop are not resized each time. Turn on Push content when it expands in Settings to get the old behavior back
- On macOS, moving between split panes is now Cmd+Option+Arrow, so Option+Arrow can move the cursor by word in a terminal
### Fixed
- Picking a section lower down in the host editor, like Warpgate, stuttered on the way and sometimes stopped before it got there
- Sorting hosts by IP address put 10.0.0.210 before 10.0.0.3
- The local terminal failed to start in the universal macOS build
- Hosts shared with you never showed up on a linked desktop
- A linked desktop could never turn on an unsigned plugin the server had enabled
- Creating, cloning or deleting hosts failed with "no such table: main.p_vault_profiles" after the Vault plugin was removed
- Opening a user in Settings, Users blocked the other admin sections until you went back to the user list
- A linked desktop could keep showing the same sync conflicts, or delete a local host, when the server could not send its copy
- Usernames are now unique
- Importing user data keeps foreign keys on
- Trusted proxy login checks the address nginx saw, not nginx itself
- The desktop login frame only trusts the server's own origin
- External login URLs and callback ports are checked on the desktop app
- A changed jump host key is no longer accepted without asking
- Synced plugins signed only by a key the server sent are no longer turned on by themselves
- Two connects to the same host at once could overwrite each other's connection
- Indonesian uses the same technical terms everywhere
- When the server was down or still starting, the login page said it could not connect to the database and stopped trying. It now says the server is not responding and continues on its own once the server is back
- nginx answered 404 instead of 502 while the backend was down
- A client tunnel that could not start always said "Endpoint SSH connection closed" on the desktop app instead of the real reason, like the target refusing the connection
- The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24
- Plugin names and descriptions showed in English in the plugin manager, setup picker and host editor no matter the UI language
- Creating a host through the API without an auth type failed with a server error. It now picks the type from the credential, key or password, or says auth type is required
- The desktop app would not start its local server when another program used port 30001. It now picks a free port on its own
- A linked desktop could never finish syncing with a server on Postgres
- Routine plugin activity filled the audit log and pushed out real security entries
- Opening a full-screen view link while signed out showed a spinner forever. It now shows the login and opens the link after
- A linked desktop behind a proxy that needs extra headers, or with an allowed self-signed certificate, could not open remote terminals or tunnels
## 26.10.1
### Added
- Slovak, Croatian, Slovenian, Lithuanian, Latvian, Estonian and Malay
- The desktop app can update itself. Pick Automatic, Ask first or Never in Settings, Updates. Store and package manager installs keep updating the way they do now
- Community plugins. Turn on the community registry in Plugins, Registries to browse and install plugins made by other people, each version reviewed before it is listed
- Custom plugin registries. Add your own registry by its index URL; its signing key is pinned when you add it
### Changed
- Every language is now fully translated, in Termix and in every plugin, and stays up to date with each release
- Languages with more than two plural forms, like Russian, Polish and Arabic, now use the right one
- The Plugins tab shows how many times each plugin was downloaded, in a Downloads column
- Plugins, Browse hides plugins you already have. Turn on Show installed to see them
### Fixed
- Regional browser languages like `es-MX`, `pt` or `zh-HK` showed English instead of the closest translation
- Counts like "1 keys" or "Moved 1 host(s)" now use the singular
- Plugin updates on the desktop app, synced or installed from the Plugins tab, failed to start with "Cannot find package"
- The desktop app showed plugins that only run on the server as not running and out of date
- Keep the terminal WebSocket open past nginx's 60s default
- Plugin download counts now show in the Plugins tab without waiting for the background check
- Clicking into a saved password, key passphrase or sudo password field and saving without typing erased it, which stopped sudo autofill
- The desktop app now says when its data is from 2.8 or older and needs 2.9 first, instead of only saying the local server could not start
- Client tunnels on the desktop app stopped with "Endpoint SSH connection closed" when a reverse proxy dropped their idle connection
- Typing right after opening a host from the sidebar went to the host search instead of the new session
- SSH config import failed on files that put a tab or `=` between a keyword and its value
- App icons and the favicon have rounded corners again
## 26.10.0
### Added
- Plugins. Every feature beyond hosts, credentials and sharing is now its own plugin with its own repo, and you pick which ones you want
- A Plugins tab to install, update, pin, turn off and remove plugins from the official registry, with release notes and a beta channel per plugin
- Developer mode to install a plugin from a file, and a plugin SDK on npm to build your own
- The community plugin registry is open for submissions. Termix can install community plugins in a later update
- Redesigned UI with a Manage tab for hosts and credentials and a Settings tab with search
- New full screen onboarding where you choose which plugins to start with
- Beta channel for Termix itself in Settings > Updates
- Docs links on every settings page, in the Plugins tab, in plugin settings and in the command palette
- Plugins can link their own docs and list the environment variables they read, shown on their page in the Plugins tab
- Report a bug links that open a prefilled issue in the right repo
-`TRUSTED_PROXIES` to choose which proxies nginx takes client IPs from
### Changed
- Termix now uses calendar versions (year, month, patch), starting with 26.10.0
- Redesigned host defaults with admin, user and folder levels
- Simpler host status: online or offline, kept live without a refresh
- Bug reports and feature requests now go to each repo instead of the Support repo
### Fixed
- A lot of bugs across the app, the desktop app and every plugin
- Several security issues reported through GitHub advisories
### Removed
- Downgrading to 2.8 is no longer supported. A 2.8 install has to start 2.9 once before upgrading
Termix is a small core plus plugins. This repo is the core: hosts, credentials, users, sharing, sessions, the app shell and the plugin runtime. Every other feature lives in its own `Plugin-<Name>` repo. If your change is about the terminal, file manager, Docker or any other plugin feature, open it in that plugin's repo instead.
## Prerequisites
- [Node.js](https://nodejs.org/en/download/) (built with v24)
- [Node.js](https://nodejs.org/en/download/) 22.12 or newer (built with v24)
This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.
This builds everything, starts the backend and then the Vite server at `http://localhost:5173/`. Backend changes restart the backend, and plugin changes in `../Plugins` rebuild and reload that plugin. `npm run dev:electron` does the same with the desktop app, and `npm run dev -- --help` lists the other options.
## Contributing
## Useful commands
1. **Fork the repository**: Click the "Fork" button at the top right of
the [repository page](https://github.com/Termix-SSH/Termix).
2. **Create a new branch**:
```sh
npm run test # run the test suite
npm run type-check # type-check the core
npm run lint # lint and the other repo checks
npm run format # format the code with Prettier
```
## Making a change
1. Fork the [repo](https://github.com/Termix-SSH/Termix).
2. Create a branch:
```sh
git checkout -b feature/my-new-feature
```
3. **Make your changes**: Implement your feature, fix, or improvement.
4. **Commit your changes**:
3. Make your changes and add or update tests for them.
4. Commit your changes:
```sh
git commit -m "Feature request my new feature"
git commit -m "feat: add my new feature"
```
5. **Push to your fork**:
5. Push to your fork:
```sh
git push origin feature/my-feature-request
git push origin feature/my-new-feature
```
6. **Open a pull request**: Go to the original repository and create a PR with a clear description.
6. Open a pull request against the current `dev-*` branch with a clear description.
## Guidelines
- Follow the existing code style. Use Tailwind CSS with shadcn components.
- Use the below color scheme with the respective CSS variable placed in the `className` of a div/component.
- Place all API routes in the `main-axios.ts` file. Updating the `openapi.json` is unneeded.
- Include meaningful commit messages.
- Link related issues when applicable.
- `MobileApp.tsx` renders when the users screen width is less than 768px, otherwise it loads the usual `DesktopApp.tsx`.
- Follow the existing code style. The frontend uses React, Tailwind CSS and shadcn components in `src/ui/`.
- Never hardcode text. Use `t()` and add the English string to `src/ui/locales/en.json`. That's the only locale file you need to update. Other languages are translated automatically, and if an automatic translation is wrong you can fix it in that language's file.
- Keep the UI flat and square. Avoid rounded corners on panels, cards and dialogs.
- Use the theme variables (like `var(--bg-base)` and `var(--text-primary)`) instead of fixed colors.
- There is one component tree for desktop and mobile. Use `src/ui/hooks/use-mobile.ts` for layout changes.
- Core never names a plugin. If core needs something from a plugin, add a slot, action or component id that the plugin fills.
- Tests live in `src/backend/tests/` and `src/ui/tests/`, mirroring the source tree. `npm run test` must pass.
- Every API route gets an OpenAPI JSDoc comment like the routes around it.
- Use short commit messages that start with `feat:`, `fix:` or `chore:`, and link related issues.
## Color Scheme
## Building a plugin
### Background Colors
| CSS Variable | Color Value | Usage | Description |
| `--color-dark-hover` | `#2d2d30` | Hover states | Background color for hover effects |
| `--color-dark-active` | `#2a2a2c` | Active states | Background color for active elements |
| `--color-dark-pressed` | `#1a1a1c` | Pressed states | Background color for pressed/clicked elements |
| `--color-dark-hover-alt` | `#2a2a2d` | Alternative hover | Alternative hover state color |
Start from the [plugin template](https://github.com/Termix-SSH/Termix-Plugin-Template). It talks to Termix only through `@termix-ssh/plugin-sdk` and has its own build, test and release setup.
## Support
If you need help or want to request a feature with Termix, visit the [Issues](https://github.com/Termix-SSH/Support/issues) page, log in, and press `New Issue`.
Please be as detailed as possible in your issue, preferably written in English. You can also join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
channel, however, response times may be longer.
Bugs and ideas for Termix itself go in [this repo](https://github.com/Termix-SSH/Termix/issues/new/choose). Plugin problems go in that plugin's own `Plugin-<Name>` repo. Add as much detail as you can, in English if possible. You can also ask in the [Discord](https://discord.gg/jVQGdvHDrf) server.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix is free and open source. If you find it useful, consider [donating](https://donate.termix.site/) to help cover server costs and development time.
<img src="docs/repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Achieved on September 1st, 2025</sub>
</p>
<a href="https://www.youtube.com/watch?v=vhtkP-UqV5U"><img src="./.github/readme/overview.jpg" width="720" alt="Watch the Termix overview on YouTube" /></a>
</div>
@@ -58,160 +26,83 @@ Termix is free and open source. If you find it useful, consider [donating](https
## Overview
Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a multi-platform solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal access, remote desktop control (RDP, VNC, Telnet), SSH tunneling capabilities, remote file management, and many other tools. Termix is the perfect free and self-hosted alternative to Termius available for all platforms.
Termix is a free, opensource, self-hosted server management platform built on plugins. The core keeps your hosts, credentials, users, sharing and sessions. Everything else, like the SSH terminal, remote desktop, file manager, Docker and metrics, is a plugin you can turn on, off or remove.
It runs on web, desktop and mobile. Think of it as a free, self-hosted Termius.
<br />
## Features
## Try the Demo
<table>
<tr>
<td width="50%" valign="top">
**SSH Terminal Access:**
Full-featured terminal with split-screen support (up to 4 panels) with a browser-like tab system. Includes support for customizing the terminal including common terminal themes, fonts, and other components.
</td>
<td width="50%" valign="top">
**Remote Desktop Access:**
RDP, VNC, and Telnet support over the browser with complete customization and split screening.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**SSH Tunnel Management:**
Create and manage server-to-server SSH tunnels with automatic reconnection, health monitoring, and local, remote, or dynamic SOCKS forwarding. Desktop client-to-server tunnel settings are stored locally per desktop install, optional C2S preset snapshots can be saved to the server, renamed, loaded, or deleted when you want to move a local tunnel configuration between clients.
</td>
<td width="50%" valign="top">
**Remote File Manager:**
Manage files directly on remote servers with support for viewing and editing code, images, audio, and video. Upload, download, rename, delete, and move files seamlessly with sudo support. Includes support for moving files from server to server.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Docker and Podman Management:**
Start, stop, pause, remove containers. View container stats. Control containers using a docker exec terminal. Supports both Docker and Podman as the container runtime. It was not made to replace Portainer or Dockge but rather to simply manage your containers compared to creating them.
</td>
<td width="50%" valign="top">
**SSH Host Manager:**
Save, organize, and manage your SSH connections with tags and folders (folder customization and nested folder support), and easily save reusable login info while being able to automate the deployment of SSH keys.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Host Metrics:**
View CPU, memory, disk usage, network, uptime, system information, firewall, port monitor, log viewer, users/permissions, certificates, and many more which work on most Linux based servers. Includes time-series history graphs and threshold-based alerts with ntfy and webhook support.
</td>
<td width="50%" valign="top">
**User Authentication:**
Secure user management with admin controls (can edit other users information) and OIDC/LDAP/SSO (with access control), 2FA (TOTP), and passkey (WebAuthn) support. View active user sessions across all platforms and revoke permissions. Link your OIDC/Local accounts together. View audit log of all users actions.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscale Integration:**
List devices from your tailnet to quickly add them as hosts, and connect using Tailscale SSH as an authentication method, letting your tailnet ACLs handle authorization without storing credentials.
</td>
<td width="50%" valign="top">
**RBAC/Sharing:**
Create roles and share hosts across users/roles. Supports all auth types and all host protocols.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Serial Connections:**
Connect to serial devices (routers, switches, microcontrollers, etc.) directly from the browser or desktop app. Configure baud rate, data bits, stop bits, and parity. Uses the Web Serial API in supported browsers or a native backend in the Electron app.
</td>
<td width="50%" valign="top">
**Alerts:**
Set threshold-based alert rules on host metrics (CPU, memory, disk, etc.) and get notified via ntfy or webhooks when they fire. View firing and resolved alerts in a history log.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Homepage:**
A fully customizable homepage with a drag-and-drop widget grid. Add widgets for host status, service links, clocks, notes, RSS feeds, weather, Docker containers, host metrics charts, embedded terminals, iframes, and more.
</td>
<td width="50%" valign="top">
**Database Encryption:**
Backend stored as encrypted SQLite database files. View [docs](https://docs.termix.site/security) for more.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Network Graph:**
Customize your Dashboard to visualize your homelab based off your SSH connections with status support.
</td>
<td width="50%" valign="top">
**SSH Tools:**
Create reusable command snippets that execute with a single click. Run one command simultaneously across multiple open terminals.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Persistent Tabs:**
SSH sessions and tabs stay open across devices/refreshes if enabled in user profile.
</td>
<td width="50%" valign="top">
**Languages:**
Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
Try it at [demo.termix.site](https://demo.termix.site/). Any username and password works.
<br />
<details>
<summary><b>More features</b></summary>
## Core
What every install has, with or without plugins:
- Hosts: Save hosts in nested folders with tags and parent hosts, bulk edit, import and export, and use Quick Connect for one-off connections
- Credentials: Reuse saved passwords and keys across hosts, with host defaults you can set for the server, a user or a folder
- Sharing and Roles: Share hosts, credentials and folders with users or roles at four levels: connect, view, edit and manage
- Accounts: Local accounts, sessions you can see and revoke, trusted devices, API keys and an audit log
- Security: Passwords, keys and other secrets are encrypted per user, and the database can be encrypted on disk
- Desktop Sync: The desktop app runs on its own and can link to a Termix server to keep an offline copy of your account
- Interface: Tabs and split screen, a command palette (double tap left shift), rebindable shortcuts, themes, branding and around 30 languages
- Databases: SQLite by default, with PostgreSQL and MySQL supported too
<br />
- **Dashboard** - View server information at a glance on your dashboard
- **API Keys** - Create user-scoped API keys with expiration dates to be used for automation/CI
- **Data Export/Import** - Export and import SSH hosts, credentials, and file manager data
- **Automatic SSL Setup** - Built-in SSL certificate generation and management with HTTPS redirects
- **Modern UI** - Clean desktop/mobile-friendly interface built with React, Tailwind CSS, and Shadcn. Choose between many different UI themes including light, dark, Dracula, etc. Use URL routes to open any connection in full-screen.
- **Command History** - Auto-complete and view previously ran SSH commands
- **Quick Connect** - Connect to a server without having to save the connection data
- **Command Palette** - Double tap left shift to quickly access SSH connections with your keyboard
- **Proxmox Integration** - Auto-add hosts into Termix from your Proxmox instance
- **SSH Feature Rich** - Supports jump hosts, Warpgate, TOTP based connections, SOCKS5, host key verification, password autofill, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, terminal logging, SSH agent forwarding, Bitwarden SSH agent, HashiCorp Vault SSH signing, and more.
- **Termix ID** - A sshid.io equivalent built into Termix. Claim a handle, publish your public SSH keys at a resolver URL, and use a built-in CA to issue SSH certificates.
## Plugins
</details>
Plugins add everything else. The official ones ship with Termix, and admins can install, update or remove plugins from the Plugins tab. Each plugin has its own repo.
| [ACME Certificates](https://github.com/Termix-SSH/Plugin-ACME-SSL) | Gets the Termix HTTPS certificate from Let's Encrypt or any ACME provider and renews it for you. |
| [AI Assistant](https://github.com/Termix-SSH/Plugin-AI) | An AI assistant that reads your setup and proposes changes for you to approve. |
| [Alerts](https://github.com/Termix-SSH/Plugin-Alerts) | One inbox for every alert in Termix, with delivery to webhooks, ntfy, Discord and email. |
| [Audit Tools](https://github.com/Termix-SSH/Plugin-Audit-Tools) | Send the audit log to your SIEM and export it as CSV or NDJSON. |
| [Automations](https://github.com/Termix-SSH/Plugin-Automations) | Run steps on your hosts on a schedule or when something happens. |
| [Classic Layout](https://github.com/Termix-SSH/Plugin-Classic-Layout) | The classic Termix layout with an icon rail, sidebar, tab bar and dock. |
| [Classic Themes](https://github.com/Termix-SSH/Plugin-Classic-Themes) | Light, dark and seven more color themes for Termix. |
| [Credential Tools](https://github.com/Termix-SSH/Plugin-Credential-Tools) | Generate SSH keys, check them and copy a public key to a host. |
| [Docker](https://github.com/Termix-SSH/Plugin-Docker) | Manage Docker and Podman containers on your hosts over SSH, with logs, stats and a console. |
| [File Manager](https://github.com/Termix-SSH/Plugin-File-Manager) | Browse, edit and move files over SFTP, including straight from one server to another. |
| [Fleets](https://github.com/Termix-SSH/Plugin-Fleets) | Group hosts into fleets and run commands, package actions and file transfers on all of them at once. |
| [HashiCorp Vault](https://github.com/Termix-SSH/Plugin-Vault) | Connect to hosts with short-lived SSH certificates signed by HashiCorp Vault. |
| [Homepage](https://github.com/Termix-SSH/Plugin-Homepage) | A drag and drop widget page for your servers and services, plus service links for the dashboard. |
| [Host Metrics](https://github.com/Termix-SSH/Plugin-Host-Metrics) | Live CPU, memory, disk, network and GPU stats, plus tools to manage your hosts. |
| [Host Status](https://github.com/Termix-SSH/Plugin-Host-Status) | Shows if each host is online with a dot in the host list, checked by connecting to its port. |
| [Hosts Config](https://github.com/Termix-SSH/Plugin-Hosts-Config) | Keep hosts, folders and sharing in a YAML or JSON file. Termix loads them as read only hosts and reloads when the file changes. |
| [Import and Export](https://github.com/Termix-SSH/Plugin-Import-Export) | Import hosts from a JSON file or an SSH config file and export them to share or back up. |
| [LDAP](https://github.com/Termix-SSH/Plugin-LDAP) | Sign in with your LDAP or Active Directory username and password. |
| [Login Security](https://github.com/Termix-SSH/Plugin-Login-Security) | Sign people out when idle, limit session length, turn off remember me and require a second factor at login. |
| [Network Topology](https://github.com/Termix-SSH/Plugin-Network-Topology) | Draws your hosts and the links between them as a live, interactive graph. |
| [OPKSSH](https://github.com/Termix-SSH/Plugin-OPKSSH) | Connect to hosts with short-lived OpenPubkey SSH certificates after signing in with your identity provider. |
| [Passkeys](https://github.com/Termix-SSH/Plugin-WebAuthn) | Sign in with a passkey or security key instead of a password. |
| [Proxmox](https://github.com/Termix-SSH/Plugin-Proxmox) | Import Proxmox VE guests as hosts, keep them in sync and watch node stats. |
| [Remote Desktop](https://github.com/Termix-SSH/Plugin-Remote-Desktop) | RDP, VNC and Telnet sessions in your browser, with clipboard, file transfer, sharing and recording. |
| [Secret Sources](https://github.com/Termix-SSH/Plugin-Secret-Sources) | Pull host passwords and keys from 1Password when you connect instead of storing them in Termix. |
| [Serial](https://github.com/Termix-SSH/Plugin-Serial) | Open a serial console to a router, switch or other device plugged into your computer. |
| [Session Recording](https://github.com/Termix-SSH/Plugin-Session-Recording) | Record terminal and remote desktop sessions and play them back or download them later. |
| [Session Sharing](https://github.com/Termix-SSH/Plugin-Session-Sharing) | Share live sessions by link or with another user, and present sessions to a group in meeting rooms. |
| [Single sign-on](https://github.com/Termix-SSH/Plugin-SSO) | Sign in with any OpenID Connect provider, GitHub or Google. |
| [Snippets](https://github.com/Termix-SSH/Plugin-Snippets) | Save the commands you run often and run them in a terminal or on many hosts in one click. |
| [SSH Terminal](https://github.com/Termix-SSH/Plugin-SSH-Terminal) | SSH terminal sessions with tabs, split screen, history, macros and a local terminal in the desktop app. |
| [Step CA](https://github.com/Termix-SSH/Plugin-Step-CA) | Connect to hosts with short-lived SSH certificates from a smallstep step-ca server. |
| [Tailscale](https://github.com/Termix-SSH/Plugin-Tailscale) | Add tailnet devices as hosts and connect with Tailscale SSH. |
| [Termix Identity](https://github.com/Termix-SSH/Plugin-Termix-Identity) | Publish your SSH public keys under a public handle and run your own SSH certificate authority. |
| [Tmux Monitor](https://github.com/Termix-SSH/Plugin-Tmux-Monitor) | Browse and control tmux sessions, windows and panes across your hosts. |
| [TOTP](https://github.com/Termix-SSH/Plugin-TOTP) | Two-factor sign in with a code from an authenticator app, with backup codes. |
| [Tunnels](https://github.com/Termix-SSH/Plugin-Tunnels) | SSH port forwarding that reconnects on its own, plus client tunnels from the desktop app. |
| [Usage Statistics](https://github.com/Termix-SSH/Plugin-Telemetry) | Sends a small anonymous daily report so we can see how Termix is used. |
| [Wake-on-LAN](https://github.com/Termix-SSH/Plugin-Wake-On-LAN) | Wake sleeping machines by sending a Wake-on-LAN packet to their MAC address. |
| [Warpgate](https://github.com/Termix-SSH/Plugin-Warpgate) | Connect to hosts through a Warpgate SSH bastion. |
| [Web Endpoint](https://github.com/Termix-SSH/Plugin-Web-Endpoint) | Open a host's web UI inside Termix, directly or over an SSH tunnel. |
| [Workspaces](https://github.com/Termix-SSH/Plugin-Workspaces) | Save your open tabs and split layouts and reopen them in one click. |
Want to build your own? Start from the [plugin template](https://github.com/Termix-SSH/Termix-Plugin-Template). Plugins you can install are listed in the [registry](https://github.com/Termix-SSH/Termix-Registry). Turn on the community registry in Plugins, Registries to install plugins made by other people, or add your own registry. See [submitting a plugin](https://docs.termix.site/develop/community-registry) and [hosting your own registry](https://docs.termix.site/develop/registries#host-your-own-registry).
<br />
@@ -224,27 +115,27 @@ Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/tr
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Any modern browser (Chrome, Safari, Firefox) · PWA support</td>
<td>Any modern browser (Chrome, Safari, Firefox) | PWA support</td>
@@ -252,14 +143,14 @@ Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/tr
## Installation
Visit the [Termix Docs](https://docs.termix.site/install) for full installation instructions across all platforms.
See the [docs](https://docs.termix.site/install) for install steps on every platform.
Sample Docker Compose file (you can omit `guacd` and the network if you don't plan on using remote desktop features):
Sample Docker Compose file. Remote desktop (RDP, VNC and Telnet) is built in, so nothing else needs to run next to it:
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
image:ghcr.io/termix-ssh/termix:latest
container_name:termix
restart:unless-stopped
ports:
@@ -268,34 +159,23 @@ services:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
### Cloud Hosting
If Termix runs on the network it manages, an outage takes it down right when you need it. A VPS keeps it reachable, gives you a static IP and works from anywhere without a VPN or port forward.
[GINERNET](https://docs.termix.site/install/ginernet) sponsors Termix and the docs have a setup guide for their VPS.
<br />
## Donate
Termix is free and open source with no subscriptions or paid plans. If you find it useful, consider donating to help cover server costs, domains, and development time. Donations also help fund the time to research and learn what's needed to build features like SAML, Kubernetes, and Agent support. Track progress and donate below.
Termix has no paid plans. Donations cover servers, domains and development time.
[Donate](https://donate.termix.site/)
@@ -303,114 +183,60 @@ Termix is free and open source with no subscriptions or paid plans. If you find
## Sponsors
Interested in a paid placement to support development? Email [mail@termix.site](mailto:mail@termix.site).
For a paid sponsor spot, email [mail@termix.site](mailto:mail@termix.site).
If you need help or want to request a feature with Termix, visit the [Issues](https://github.com/Termix-SSH/Support/issues) page, log in, and press `New Issue`. Please be as detailed as possible in your issue, preferably written in English. You can also join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel, however, response times may be longer.
Bugs and ideas for Termix itself (login, hosts, credentials, sharing, sync, the app and desktop app) go in this repo: [report a bug](https://github.com/Termix-SSH/Termix/issues/new?template=bug_report.yml) or [request a feature](https://github.com/Termix-SSH/Termix/issues/new?template=feature_request.yml).
<br />
Every plugin has its own repo, so a problem with the terminal, file manager, Docker, remote desktop or any other plugin goes in that plugin's repo (see the table above, or use Report an issue on the plugin's page in the app, which fills in your versions for you). Not sure where it goes? Open it here and it will be moved.
## Screenshots
You need a GitHub account. Add as much detail as you can, in English if possible.
<sub>Some videos and images may be out of date or may not perfectly showcase features.</sub>
</div>
<br />
## Planned Features
See [Projects](https://github.com/orgs/Termix-SSH/projects/5) for all planned features. If you are looking to contribute, see [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
For discussions and questions, join the [Discord](https://discord.gg/jVQGdvHDrf) server. Update videos are on [YouTube](https://www.youtube.com/@TermixSSH/videos).
<br />
## License
Distributed under the Apache License Version 2.0. See `LICENSE` for more information.
Revamped RBAC/sharing, session recording & replay, Vault auth for monitors, API key host enrollment, Proxmox guest auto sync, database refactor, plus 30+ bug fixes across terminal, file manager, RDP/VNC, and auth. DO NOT DOWNGRADE FROM THIS VERSION.
<!-- /SUMMARY -->
<!-- YOUTUBE -->
https://youtu.be/c3UD4q2jW_8
<!-- /YOUTUBE -->
<!-- UPDATE_LOG -->
- Revamped RBAC/sharing system (new UI, all auth types and host protocols now supported)
- Complete admin control over user information (manage all users hosts, credentials, and snippets)
- Support Vault auth for monitors
- API key host enrollment endpoint
- Allow pinned hosts with name sorting
- Session recording and replay
- Terminal font size shortcuts (ctrl + / -)
- Open File Manager to tab right-click menu
- Proxmox guest auto sync
- Complete database refactor
- 30-day donation reminder and new donation milestones that support research: (donate.termix.site)
- Improve site performance with cache and poll pauses
- Save quick connect sessions as hosts
<!-- /UPDATE_LOG -->
<!-- BUG_FIXES -->
- Syntax highlighting artifacts
- Filter dashboard status hosts
- Persist dashboard service link changes
- Snippet text overflow
- Persist remote desktop credential auth
- Guard language switching failures
- Resolve tunnel source credentials
- Windows file delete command
- Artifact release checkout ref
- Command palette escape in fullscreen
- Alerts and audit log normalization
- macOS VNC protocol negotiation
- Port knocking before SSH connect
- Allow escape to close link confirmation
- Prevent Electron modifier wheel zoom
- Credential auth optional password
- Retry transient terminal DNS lookups
- OIDC redirect forwarded port handling
- Preserve recent open tabs on startup
- Terminal font selection
- Poor font legibility in multiple places
- File manager uploads failing
- Tmux detection for non-POSTIX shells
- OPKSSH js-yaml ESM import
- Android Vietnamese IME input
- Firefox RDP clipboard paste
- Proxmox discovery over HTTPS
- External editor actions in file preview
- Firefox desktop OIDC callback
- Status checks through jump hosts
- Restore sudo password auto fill settings
- Preserve file editor position on save
- Sync cloud preference storage mode
- Render RDP sessions at native pixel density
- Restore database import in embedded desktop mode
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>تم تحقيقه في 1 سبتمبر 2025</sub>
</p>
</div>
<br />
## نظرة عامة
Termix هي منصة مفتوحة المصدر ومجانية للأبد وذاتية الاستضافة لإدارة الخوادم بشكل شامل. توفر حلاً متعدد المنصات لإدارة خوادمك وبنيتك التحتية من خلال واجهة واحدة وسهلة الاستخدام. يوفر Termix الوصول إلى طرفية SSH، والتحكم في سطح المكتب البعيد (RDP، VNC، Telnet)، وقدرات إنشاء أنفاق SSH، وإدارة ملفات SSH عن بُعد، والعديد من الأدوات الأخرى. يُعد Termix البديل المثالي المجاني وذاتي الاستضافة لـ Termius المتاح لجميع المنصات.
<br />
## الميزات
<table>
<tr>
<td width="50%" valign="top">
**الوصول إلى طرفية SSH:**
طرفية كاملة الميزات مع دعم تقسيم الشاشة (حتى 4 لوحات) مع نظام علامات تبويب شبيه بالمتصفح. يتضمن دعم تخصيص الطرفية بما في ذلك سمات الطرفية الشائعة والخطوط والمكونات الأخرى.
</td>
<td width="50%" valign="top">
**الوصول إلى سطح المكتب البعيد:**
دعم RDP و VNC و Telnet عبر المتصفح مع تخصيص كامل وتقسيم الشاشة.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**إدارة أنفاق SSH:**
إنشاء وإدارة أنفاق SSH بين الخوادم مع إعادة الاتصال التلقائي ومراقبة الحالة وإعادة التوجيه المحلي أو البعيد أو SOCKS الديناميكي. يتم تخزين إعدادات نفق العميل-المكتبي إلى السيرفر محلياً لكل تثبيت مكتبي، ويمكن حفظ لقطات C2S الاختيارية على الخادم وإعادة تسميتها وتحميلها أو حذفها عندما تريد نقل تكوين النفق المحلي بين العملاء.
</td>
<td width="50%" valign="top">
**مدير الملفات عن بُعد:**
إدارة الملفات مباشرة على الخوادم البعيدة مع دعم عرض وتحرير الكود والصور والصوت والفيديو. رفع وتنزيل وإعادة تسمية وحذف ونقل الملفات بسلاسة مع دعم sudo. يتضمن دعم نقل الملفات من خادم إلى آخر.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**إدارة Docker و Podman:**
تشغيل وإيقاف وتعليق وحذف الحاويات. عرض إحصائيات الحاويات. التحكم في الحاوية باستخدام طرفية docker exec. يدعم كلاً من Docker و Podman كبيئة تشغيل للحاويات. لم يُصمم ليحل محل Portainer أو Dockge بل لإدارة حاوياتك ببساطة مقارنة بإنشائها.
</td>
<td width="50%" valign="top">
**مدير مضيفات SSH:**
حفظ وتنظيم وإدارة اتصالات SSH الخاصة بك باستخدام العلامات والمجلدات (مع دعم تخصيص المجلدات والمجلدات المتداخلة)، وحفظ بيانات تسجيل الدخول القابلة لإعادة الاستخدام بسهولة مع إمكانية أتمتة نشر مفاتيح SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**مقاييس المضيف:**
عرض استخدام المعالج والذاكرة والقرص والشبكة ووقت التشغيل ومعلومات النظام وجدار الحماية ومراقب المنافذ وعارض السجلات والمستخدمين/الصلاحيات والشهادات وغيرها الكثير، تعمل على معظم الخوادم المبنية على Linux. يتضمن رسوم بيانية تاريخية زمنية السلسلة وتنبيهات قائمة على الحدود مع دعم ntfy والـ webhook.
</td>
<td width="50%" valign="top">
**مصادقة المستخدمين:**
إدارة آمنة للمستخدمين مع ضوابط إدارية (يمكن تعديل معلومات المستخدمين الآخرين) ودعم OIDC/LDAP/SSO (مع التحكم في الوصول) و 2FA (TOTP) ودعم مفاتيح المرور (WebAuthn). عرض جلسات المستخدمين النشطة عبر جميع المنصات وإلغاء الصلاحيات. ربط حسابات OIDC/المحلية معاً. عرض سجل تدقيق لجميع إجراءات المستخدمين.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**تكامل Tailscale:**
عرض أجهزة شبكتك من Tailscale لإضافتها بسرعة كمضيفات، والاتصال عبر Tailscale SSH كطريقة مصادقة، مما يتيح لقوائم تحكم الوصول في Tailscale التعامل مع التفويض دون الحاجة لتخزين بيانات اعتماد.
</td>
<td width="50%" valign="top">
**RBAC/المشاركة:**
إنشاء الأدوار ومشاركة المضيفات عبر المستخدمين/الأدوار. يدعم جميع أنواع المصادقة وجميع بروتوكولات المضيف.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**الاتصالات التسلسلية:**
الاتصال بالأجهزة التسلسلية (أجهزة التوجيه والمفاتيح والمتحكمات الدقيقة وغيرها) مباشرة من المتصفح أو تطبيق سطح المكتب. ضبط معدل نقل البيانات وبتات البيانات وبتات التوقف والتكافؤ. يستخدم Web Serial API في المتصفحات المدعومة أو خلفية أصلية في تطبيق Electron.
</td>
<td width="50%" valign="top">
**التنبيهات:**
ضبط قواعد تنبيه قائمة على الحدود لمقاييس المضيف (المعالج والذاكرة والقرص وغيرها) والحصول على إشعارات عبر ntfy أو webhooks عند إطلاقها. عرض التنبيهات النشطة والمحلولة في سجل التاريخ.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**الصفحة الرئيسية:**
صفحة رئيسية قابلة للتخصيص بالكامل مع شبكة أدوات قابلة للسحب والإفلات. أضف أدوات لحالة المضيف وروابط الخدمات والساعات والملاحظات وخلاصات RSS والطقس وحاويات Docker ومخططات مقاييس المضيف والطرفيات المضمنة والإطارات المضمنة وأكثر.
</td>
<td width="50%" valign="top">
**تشفير قاعدة البيانات:**
يُخزَّن الخادم الخلفي كملفات قاعدة بيانات SQLite مشفرة. اطلع على [الوثائق](https://docs.termix.site/security) لمزيد من المعلومات.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**الرسم البياني للشبكة:**
تخصيص لوحة التحكم لتصور مختبرك المنزلي بناءً على اتصالات SSH مع دعم الحالة.
</td>
<td width="50%" valign="top">
**أدوات SSH:**
إنشاء مقتطفات أوامر قابلة لإعادة الاستخدام تُنفَّذ بنقرة واحدة. تشغيل أمر واحد في وقت واحد عبر عدة طرفيات مفتوحة.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**علامات التبويب الدائمة:**
تبقى جلسات SSH وعلامات التبويب مفتوحة عبر الأجهزة/التحديثات إذا تم تفعيلها في ملف تعريف المستخدم.
</td>
<td width="50%" valign="top">
**اللغات:**
دعم مدمج لحوالي 30 لغة (تُدار بواسطة [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>المزيد من الميزات</b></summary>
<br />
- **لوحة التحكم** - عرض معلومات الخادم بنظرة واحدة على لوحة التحكم
- **مفاتيح API** - إنشاء مفاتيح API محددة النطاق للمستخدم مع تواريخ انتهاء صلاحية للاستخدام في الأتمتة/CI
- **إعداد SSL تلقائي** - إنشاء وإدارة شهادات SSL مدمجة مع إعادة التوجيه إلى HTTPS
- **واجهة مستخدم حديثة** - واجهة نظيفة متوافقة مع سطح المكتب والهاتف المحمول مبنية بـ React و Tailwind CSS و Shadcn. الاختيار بين العديد من سمات واجهة المستخدم بما في ذلك الفاتح والداكن و Dracula وغيرها. استخدام مسارات URL لفتح أي اتصال في وضع ملء الشاشة.
- **سجل الأوامر** - الإكمال التلقائي وعرض أوامر SSH التي تم تنفيذها سابقاً
- **الاتصال السريع** - الاتصال بخادم دون الحاجة إلى حفظ بيانات الاتصال
- **لوحة الأوامر** - اضغط مرتين على Shift الأيسر للوصول السريع إلى اتصالات SSH باستخدام لوحة المفاتيح
- **تكامل Proxmox** - إضافة المضيفات تلقائياً إلى Termix من نسخة Proxmox الخاصة بك
- **ميزات SSH الغنية** - دعم مضيفات القفز، Warpgate، الاتصالات المبنية على TOTP، SOCKS5، التحقق من مفتاح المضيف، الملء التلقائي لكلمة المرور، [OPKSSH](https://github.com/openpubkey/opkssh)، tmux، port knocking، تسجيل الطرفية، إعادة توجيه وكيل SSH، وكيل Bitwarden SSH، توقيع SSH عبر HashiCorp Vault، وغيرها.
- **Termix ID** - مكافئ لـ sshid.io مدمج في Termix. احصل على اسم مستخدم، انشر مفاتيح SSH العامة الخاصة بك على رابط محلل (resolver URL)، واستخدم هيئة إصدار شهادات (CA) مدمجة لإصدار شهادات SSH.
</details>
<br />
## دعم المنصات
<table align="center">
<tr>
<th align="center">المنصة</th>
<th align="center">التوزيع</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>أي متصفح حديث (Chrome، Safari، Firefox) · دعم PWA</td>
قم بزيارة [وثائق](https://docs.termix.site/install) Termix للحصول على تعليمات التثبيت الكاملة عبر جميع المنصات.
نموذج ملف Docker Compose (يمكنك حذف `guacd` والشبكة إذا كنت لا تخطط لاستخدام ميزات سطح المكتب البعيد):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## التبرع
Termix مجاني ومفتوح المصدر بدون اشتراكات أو خطط مدفوعة. إذا وجدته مفيدًا، فكّر في التبرع للمساعدة في تغطية تكاليف الخادم والنطاقات ووقت التطوير. تساعد التبرعات أيضاً في تمويل الوقت اللازم للبحث وتعلم ما هو مطلوب لبناء ميزات مثل SAML و Kubernetes ودعم الوكلاء (Agent). تابع التقدم وتبرع أدناه.
[تبرع](https://donate.termix.site/)
<br />
## الرعاة
هل تريد إعلاناً مدفوعاً لدعم التطوير؟ راسلنا عبر البريد الإلكتروني [mail@termix.site](mailto:mail@termix.site).
إذا كنت بحاجة إلى مساعدة أو ترغب في طلب ميزة لـ Termix، قم بزيارة صفحة [المشكلات](https://github.com/Termix-SSH/Support/issues)، وسجل الدخول، واضغط على `New Issue`. يرجى أن تكون مفصلاً قدر الإمكان في مشكلتك، ويُفضَّل كتابتها باللغة الإنجليزية. يمكنك أيضاً الانضمام إلى خادم [Discord](https://discord.gg/jVQGdvHDrf) وزيارة قناة الدعم، ومع ذلك قد تكون أوقات الاستجابة أطول.
<sub>قد تكون بعض مقاطع الفيديو والصور قديمة أو قد لا تعرض الميزات بشكل مثالي.</sub>
</div>
<br />
## الميزات المخططة
راجع [المشاريع](https://github.com/orgs/Termix-SSH/projects/5) لعرض جميع الميزات المخططة. إذا كنت تتطلع للمساهمة، راجع [المساهمة](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## الترخيص
موزع بموجب رخصة Apache License الإصدار 2.0. راجع ملف `LICENSE` لمزيد من المعلومات.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix ist kostenlos und Open Source. Wenn Sie es nützlich finden, erwägen Sie eine [Spende](https://donate.termix.site/), um Serverkosten und Entwicklungszeit zu decken.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Erreicht am 1. September 2025</sub>
</p>
</div>
<br />
## Uberblick
Termix ist eine quelloffene, dauerhaft kostenlose, selbst gehostete All-in-One-Serververwaltungsplattform. Sie bietet eine plattformubergreifende Losung zur Verwaltung Ihrer Server und Infrastruktur uber eine einzige, intuitive Oberflache. Termix bietet SSH-Terminalzugriff, Remote-Desktop-Steuerung (RDP, VNC, Telnet), SSH-Tunneling-Funktionen, Remote-Dateiverwaltung und viele weitere Werkzeuge. Termix ist die perfekte kostenlose und selbst gehostete Alternative zu Termius, verfugbar fur alle Plattformen.
<br />
## Funktionen
<table>
<tr>
<td width="50%" valign="top">
**SSH-Terminalzugriff:**
Voll ausgestattetes Terminal mit Split-Screen-Unterstutzung (bis zu 4 Panels) mit einem browserahnlichen Tab-System. Enthalt Unterstutzung fur die Anpassung des Terminals einschliesslich gangiger Terminal-Themes, Schriftarten und anderer Komponenten.
</td>
<td width="50%" valign="top">
**Remote-Desktop-Zugriff:**
RDP-, VNC- und Telnet-Unterstutzung uber den Browser mit vollstandiger Anpassung und Split-Screen.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**SSH-Tunnelverwaltung:**
Erstellen und verwalten Sie Server-zu-Server-SSH-Tunnel mit automatischer Wiederverbindung, Gesundheitsuberwachung sowie lokaler, entfernter oder dynamischer SOCKS-Weiterleitung. Desktop-Client-zu-Server-Tunneleinstellungen werden lokal pro Desktop-Installation gespeichert, optionale C2S-Preset-Snapshots konnen auf dem Server gespeichert, umbenannt, geladen oder geloscht werden, wenn Sie eine lokale Tunnelkonfiguration zwischen Clients ubertragen mochten.
</td>
<td width="50%" valign="top">
**Remote-Dateimanager:**
Verwalten Sie Dateien direkt auf Remote-Servern mit Unterstutzung fur das Anzeigen und Bearbeiten von Code, Bildern, Audio und Video. Laden Sie Dateien hoch, herunter, benennen Sie sie um, loschen oder verschieben Sie sie nahtlos mit Sudo-Unterstutzung. Enthalt Unterstutzung fur das Verschieben von Dateien von Server zu Server.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Docker- und Podman-Verwaltung:**
Container starten, stoppen, pausieren, entfernen. Container-Statistiken anzeigen. Container uber Docker-Exec-Terminal steuern. Unterstutzt sowohl Docker als auch Podman als Container-Laufzeitumgebung. Es wurde nicht entwickelt, um Portainer oder Dockge zu ersetzen, sondern um Ihre Container einfach zu verwalten, anstatt sie zu erstellen.
</td>
<td width="50%" valign="top">
**SSH-Host-Manager:**
Speichern, organisieren und verwalten Sie Ihre SSH-Verbindungen mit Tags und Ordnern (Ordneranpassung und verschachtelte Ordner werden unterstutzt) und speichern Sie einfach wiederverwendbare Anmeldeinformationen mit der Moglichkeit, die Bereitstellung von SSH-Schlusseln zu automatisieren.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Host-Metriken:**
CPU-, Arbeitsspeicher- und Festplattenauslastung, Netzwerk, Betriebszeit, Systeminformationen, Firewall, Port-Monitor, Log-Viewer, Benutzer/Berechtigungen, Zertifikate und vieles mehr anzeigen, was auf den meisten Linux-basierten Servern funktioniert. Enthalt Zeitreihen-Verlaufsdiagramme und schwellenwertbasierte Warnmeldungen mit ntfy- und Webhook-Unterstutzung.
</td>
<td width="50%" valign="top">
**Benutzerauthentifizierung:**
Sichere Benutzerverwaltung mit Admin-Kontrollen (kann Informationen anderer Benutzer bearbeiten) und OIDC-/LDAP-/SSO-Unterstutzung (mit Zugriffskontrolle), 2FA (TOTP) und Passkey (WebAuthn)-Unterstutzung. Aktive Benutzersitzungen uber alle Plattformen anzeigen und Berechtigungen widerrufen. OIDC-/Lokale Konten miteinander verknupfen. Audit-Protokoll aller Benutzeraktionen anzeigen.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscale-Integration:**
Gerate aus Ihrem Tailnet auflisten, um sie schnell als Hosts hinzuzufugen, und mit Tailscale SSH als Authentifizierungsmethode verbinden, sodass Ihre Tailnet-ACLs die Autorisierung ubernehmen, ohne Anmeldedaten speichern zu mussen.
</td>
<td width="50%" valign="top">
**RBAC/Freigabe:**
Erstellen Sie Rollen und teilen Sie Hosts uber Benutzer/Rollen hinweg. Unterstutzt alle Authentifizierungstypen und alle Host-Protokolle.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Serielle Verbindungen:**
Verbinden Sie sich direkt vom Browser oder der Desktop-App aus mit seriellen Geraten (Router, Switches, Mikrocontroller usw.). Konfigurieren Sie Baudrate, Datenbits, Stoppbits und Paritat. Verwendet die Web Serial API in unterstutzten Browsern oder ein natives Backend in der Electron-App.
</td>
<td width="50%" valign="top">
**Warnmeldungen:**
Legen Sie schwellenwertbasierte Warnregeln fur Host-Metriken (CPU, Arbeitsspeicher, Festplatte usw.) fest und erhalten Sie Benachrichtigungen uber ntfy oder Webhooks, wenn diese ausgelost werden. Zeigen Sie ausgeloste und aufgeloste Warnmeldungen in einem Verlaufsprotokoll an.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Startseite:**
Eine vollstandig anpassbare Startseite mit einem Drag-and-Drop-Widget-Raster. Fugen Sie Widgets fur Hoststatus, Service-Links, Uhren, Notizen, RSS-Feeds, Wetter, Docker-Container, Host-Metrik-Diagramme, eingebettete Terminals, iFrames und mehr hinzu.
</td>
<td width="50%" valign="top">
**Datenbankverschlusselung:**
Backend gespeichert als verschlusselte SQLite-Datenbankdateien. Weitere Informationen in der [Dokumentation](https://docs.termix.site/security).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Netzwerkgraph:**
Passen Sie Ihr Dashboard an, um Ihr Homelab basierend auf Ihren SSH-Verbindungen mit Statusunterstutzung zu visualisieren.
</td>
<td width="50%" valign="top">
**SSH-Werkzeuge:**
Erstellen Sie wiederverwendbare Befehlsvorlagen, die mit einem einzigen Klick ausgefuhrt werden. Fuhren Sie einen Befehl gleichzeitig in mehreren geoffneten Terminals aus.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Persistente Tabs:**
SSH-Sitzungen und Tabs bleiben uber Gerate/Aktualisierungen hinweg offen, wenn im Benutzerprofil aktiviert.
</td>
<td width="50%" valign="top">
**Sprachen:**
Integrierte Unterstutzung fur ca. 30 Sprachen (verwaltet uber [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Weitere Funktionen</b></summary>
<br />
- **Dashboard** - Serverinformationen auf einen Blick auf Ihrem Dashboard anzeigen
- **API-Schlussel** - Erstellen Sie benutzerbezogene API-Schlussel mit Ablaufdaten zur Verwendung fur Automatisierung/CI
- **Datenexport/-import** - SSH-Hosts, Anmeldeinformationen und Dateimanager-Daten exportieren und importieren
- **Automatische SSL-Einrichtung** - Integrierte SSL-Zertifikatsgenerierung und -verwaltung mit HTTPS-Weiterleitungen
- **Moderne Benutzeroberflache** - Saubere desktop-/mobilfreundliche Oberflache, erstellt mit React, Tailwind CSS und Shadcn. Wahlen Sie zwischen vielen verschiedenen UI-Themes einschliesslich Hell, Dunkel, Dracula usw. Verwenden Sie URL-Routen, um jede Verbindung im Vollbildmodus zu offnen.
- **Befehlsverlauf** - Autovervollstandigung und Anzeige zuvor ausgefuhrter SSH-Befehle
- **Schnellverbindung** - Verbinden Sie sich mit einem Server, ohne die Verbindungsdaten speichern zu mussen
- **Befehlspalette** - Doppeltippen Sie die linke Umschalttaste, um schnell auf SSH-Verbindungen mit Ihrer Tastatur zuzugreifen
- **Proxmox-Integration** - Automatisches Hinzufugen von Hosts zu Termix aus Ihrer Proxmox-Instanz
- **SSH-Funktionsreich** - Unterstutzt Jump-Hosts, Warpgate, TOTP-basierte Verbindungen, SOCKS5, Host-Key-Verifizierung, automatisches Ausfullen von Passwortern, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, Port Knocking, Terminal-Protokollierung, SSH-Agent-Forwarding, Bitwarden SSH-Agent, HashiCorp Vault SSH-Signierung und mehr.
- **Termix ID** - Ein sshid.io-Aquivalent, integriert in Termix. Beanspruchen Sie einen Handle, veroffentlichen Sie Ihre offentlichen SSH-Schlussel unter einer Resolver-URL und nutzen Sie eine integrierte CA zur Ausstellung von SSH-Zertifikaten.
</details>
<br />
## Plattformunterstutzung
<table align="center">
<tr>
<th align="center">Plattform</th>
<th align="center">Distribution</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Jeder moderne Browser (Chrome, Safari, Firefox) · PWA-Unterstutzung</td>
Besuchen Sie die [Termix-Dokumentation](https://docs.termix.site/install) fur vollstandige Installationsanleitungen fur alle Plattformen.
Beispiel einer Docker-Compose-Datei (Sie konnen `guacd` und das Netzwerk weglassen, wenn Sie keine Remote-Desktop-Funktionen nutzen mochten):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Spenden
Termix ist kostenlos und Open Source, ohne Abonnements oder kostenpflichtige Plane. Wenn Sie es nutzlich finden, erwagen Sie eine Spende, um Serverkosten, Domains und Entwicklungszeit zu decken. Spenden helfen auch dabei, die Zeit zu finanzieren, die benotigt wird, um zu erforschen und zu lernen, was fur Funktionen wie SAML-, Kubernetes- und Agent-Unterstutzung erforderlich ist. Verfolgen Sie den Fortschritt und spenden Sie unten.
[Spenden](https://donate.termix.site/)
<br />
## Sponsoren
Interessiert an einer bezahlten Platzierung zur Unterstutzung der Entwicklung? Schreiben Sie eine E-Mail an [mail@termix.site](mailto:mail@termix.site).
Wenn Sie Hilfe benotigen oder eine Funktion fur Termix anfragen mochten, besuchen Sie die [Issues](https://github.com/Termix-SSH/Support/issues)-Seite, melden Sie sich an und klicken Sie auf `New Issue`. Bitte beschreiben Sie Ihr Anliegen so detailliert wie moglich, vorzugsweise auf Englisch. Sie konnen auch dem [Discord](https://discord.gg/jVQGdvHDrf)-Server beitreten und den Support-Kanal besuchen, allerdings konnen die Antwortzeiten dort langer sein.
<sub>Einige Videos und Bilder konnen veraltet sein oder Funktionen moglicherweise nicht perfekt darstellen.</sub>
</div>
<br />
## Geplante Funktionen
Siehe [Projekte](https://github.com/orgs/Termix-SSH/projects/5) fur alle geplanten Funktionen. Wenn Sie beitragen mochten, siehe [Mitwirken](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Lizenz
Verteilt unter der Apache License Version 2.0. Siehe `LICENSE` fur weitere Informationen.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix es gratuito y de código abierto. Si lo encuentras útil, considera [donar](https://donate.termix.site/) para ayudar a cubrir los costos del servidor y el tiempo de desarrollo.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Logrado el 1 de septiembre de 2025</sub>
</p>
</div>
<br />
## Descripcion General
Termix es una plataforma de gestion de servidores todo en uno, de codigo abierto, siempre gratuita y autoalojada. Proporciona una solucion multiplataforma para gestionar sus servidores e infraestructura a traves de una interfaz unica e intuitiva. Termix ofrece acceso a terminal SSH, control de escritorio remoto (RDP, VNC, Telnet), capacidades de tuneles SSH, gestion remota de archivos y muchas otras herramientas. Termix es la alternativa perfecta, gratuita y autoalojada a Termius, disponible para todas las plataformas.
<br />
## Caracteristicas
<table>
<tr>
<td width="50%" valign="top">
**Acceso a Terminal SSH:**
Terminal completo con soporte de pantalla dividida (hasta 4 paneles) con un sistema de pestanas similar al navegador. Incluye soporte para personalizar el terminal incluyendo temas comunes de terminal, fuentes y otros componentes.
</td>
<td width="50%" valign="top">
**Acceso a Escritorio Remoto:**
Soporte RDP, VNC y Telnet a traves del navegador con personalizacion completa y pantalla dividida.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestion de Tuneles SSH:**
Cree y gestione tuneles SSH de servidor a servidor con reconexion automatica, monitoreo de estado y reenvio local, remoto o dinamico SOCKS. La configuracion de tuneles de cliente de escritorio a servidor se almacena localmente por instalacion de escritorio, los snapshots de presets C2S opcionales pueden guardarse en el servidor, renombrarse, cargarse o eliminarse cuando desee mover una configuracion de tunel local entre clientes.
</td>
<td width="50%" valign="top">
**Gestor Remoto de Archivos:**
Gestione archivos directamente en servidores remotos con soporte para visualizar y editar codigo, imagenes, audio y video. Suba, descargue, renombre, elimine y mueva archivos sin problemas con soporte sudo. Incluye soporte para mover archivos de servidor a servidor.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestion de Docker y Podman:**
Inicie, detenga, pause, elimine contenedores. Vea estadisticas de contenedores. Controle contenedores usando el terminal docker exec. Compatible con Docker y Podman como entorno de ejecucion de contenedores. No fue creado para reemplazar Portainer o Dockge, sino para simplemente gestionar sus contenedores en lugar de crearlos.
</td>
<td width="50%" valign="top">
**Gestor de Hosts SSH:**
Guarde, organice y gestione sus conexiones SSH con etiquetas y carpetas (con personalizacion de carpetas y soporte de carpetas anidadas), y guarde facilmente informacion de inicio de sesion reutilizable con la capacidad de automatizar el despliegue de claves SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Metricas del Host:**
Vea el uso de CPU, memoria y disco, red, tiempo de actividad, informacion del sistema, firewall, monitor de puertos, visor de registros, usuarios/permisos, certificados y muchos mas, que funcionan en la mayoria de los servidores basados en Linux. Incluye graficos de historial de series temporales y alertas basadas en umbrales con soporte para ntfy y webhooks.
</td>
<td width="50%" valign="top">
**Autenticacion de Usuarios:**
Gestion segura de usuarios con controles de administrador (puede editar la informacion de otros usuarios) y soporte para OIDC/LDAP/SSO (con control de acceso), 2FA (TOTP) y soporte para passkeys (WebAuthn). Vea sesiones activas de usuarios en todas las plataformas y revoque permisos. Vincule sus cuentas OIDC/Locales entre si. Vea el registro de auditoria de las acciones de todos los usuarios.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Integracion con Tailscale:**
Liste dispositivos de su red Tailscale para agregarlos rapidamente como hosts y conectese usando Tailscale SSH como metodo de autenticacion, permitiendo que las ACL de Tailscale gestionen la autorizacion sin almacenar credenciales.
</td>
<td width="50%" valign="top">
**RBAC/Compartir:**
Cree roles y comparta hosts entre usuarios/roles. Compatible con todos los tipos de autenticacion y todos los protocolos de host.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Conexiones Serie:**
Conectese a dispositivos serie (routers, switches, microcontroladores, etc.) directamente desde el navegador o la aplicacion de escritorio. Configure la tasa de baudios, bits de datos, bits de parada y paridad. Utiliza la Web Serial API en navegadores compatibles o un backend nativo en la aplicacion Electron.
</td>
<td width="50%" valign="top">
**Alertas:**
Configure reglas de alerta basadas en umbrales para metricas del host (CPU, memoria, disco, etc.) y reciba notificaciones a traves de ntfy o webhooks cuando se activen. Vea las alertas activas y resueltas en un historial de registros.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Pagina de Inicio:**
Una pagina de inicio completamente personalizable con una cuadricula de widgets de arrastrar y soltar. Agregue widgets para estado del host, enlaces de servicios, relojes, notas, feeds RSS, clima, contenedores Docker, graficos de metricas del host, terminales integrados, iframes y mas.
</td>
<td width="50%" valign="top">
**Cifrado de Base de Datos:**
Backend almacenado como archivos de base de datos SQLite cifrados. Consulte la [documentacion](https://docs.termix.site/security) para mas informacion.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Grafico de Red:**
Personalice su Dashboard para visualizar su homelab basado en sus conexiones SSH con soporte de estado.
</td>
<td width="50%" valign="top">
**Herramientas SSH:**
Cree fragmentos de comandos reutilizables que se ejecutan con un solo clic. Ejecute un comando simultaneamente en multiples terminales abiertos.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Pestanas Persistentes:**
Las sesiones SSH y pestanas permanecen abiertas entre dispositivos/actualizaciones si esta habilitado en el perfil de usuario.
</td>
<td width="50%" valign="top">
**Idiomas:**
Soporte integrado para aproximadamente 30 idiomas (gestionado por [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Mas caracteristicas</b></summary>
<br />
- **Dashboard** - Vea la informacion del servidor de un vistazo en su dashboard
- **Claves API** - Cree claves API con ambito de usuario y fechas de vencimiento para usar en automatizacion/CI
- **Exportacion/Importacion de Datos** - Exporte e importe hosts SSH, credenciales y datos del gestor de archivos
- **Configuracion Automatica de SSL** - Generacion y gestion integrada de certificados SSL con redirecciones HTTPS
- **Interfaz Moderna** - Interfaz limpia compatible con escritorio/movil construida con React, Tailwind CSS y Shadcn. Elija entre muchos temas de UI diferentes, incluyendo claro, oscuro, Dracula, etc. Use rutas URL para abrir cualquier conexion en pantalla completa.
- **Historial de Comandos** - Autocompletado y visualizacion de comandos SSH ejecutados anteriormente
- **Conexion Rapida** - Conectese a un servidor sin necesidad de guardar los datos de conexion
- **Paleta de Comandos** - Pulse dos veces la tecla Shift izquierda para acceder rapidamente a las conexiones SSH con su teclado
- **Integracion con Proxmox** - Agregue automaticamente hosts a Termix desde su instancia de Proxmox
- **SSH Rico en Funciones** - Soporta jump hosts, Warpgate, conexiones basadas en TOTP, SOCKS5, verificacion de clave de host, autocompletado de contrasenas, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, registro de terminal, reenvio de agente SSH, agente SSH de Bitwarden, firma SSH con HashiCorp Vault y mas.
- **Termix ID** - Un equivalente a sshid.io integrado en Termix. Reclame un identificador, publique sus claves publicas SSH en una URL de resolucion y use una CA integrada para emitir certificados SSH.
Visite la [documentacion de Termix](https://docs.termix.site/install) para obtener instrucciones completas de instalacion en todas las plataformas.
Archivo de ejemplo de Docker Compose (puede omitir `guacd` y la red si no planea usar las funciones de escritorio remoto):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Donar
Termix es gratuito y de codigo abierto, sin suscripciones ni planes de pago. Si lo encuentra util, considere donar para ayudar a cubrir los costos del servidor, los dominios y el tiempo de desarrollo. Las donaciones tambien ayudan a financiar el tiempo necesario para investigar y aprender lo que se necesita para construir funciones como soporte para SAML, Kubernetes y Agent. Siga el progreso y done a continuacion.
[Donar](https://donate.termix.site/)
<br />
## Patrocinadores
Interesado en un espacio patrocinado de pago para apoyar el desarrollo? Escriba a [mail@termix.site](mailto:mail@termix.site).
Si necesita ayuda o desea solicitar una funcion para Termix, visite la pagina de [Issues](https://github.com/Termix-SSH/Support/issues), inicie sesion y pulse `New Issue`. Por favor, sea lo mas detallado posible en su reporte, preferiblemente escrito en ingles. Tambien puede unirse al servidor de [Discord](https://discord.gg/jVQGdvHDrf) y visitar el canal de soporte, sin embargo, los tiempos de respuesta pueden ser mas largos.
<sub>Algunos videos e imagenes pueden estar desactualizados o no mostrar perfectamente las caracteristicas.</sub>
</div>
<br />
## Caracteristicas Planeadas
Consulte [Proyectos](https://github.com/orgs/Termix-SSH/projects/5) para todas las caracteristicas planeadas. Si desea contribuir, consulte [Contribuir](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Licencia
Distribuido bajo la Licencia Apache Version 2.0. Consulte `LICENSE` para mas informacion.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix est gratuit et open source. Si vous le trouvez utile, pensez à [faire un don](https://donate.termix.site/) pour aider à couvrir les coûts de serveur et le temps de développement.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Obtenu le 1er septembre 2025</sub>
</p>
</div>
<br />
## Presentation
Termix est une plateforme de gestion de serveurs tout-en-un, open source, a jamais gratuite et auto-hebergee. Elle fournit une solution multiplateforme pour gerer vos serveurs et votre infrastructure a travers une interface unique et intuitive. Termix offre un acces terminal SSH, le controle de bureau a distance (RDP, VNC, Telnet), des capacites de tunneling SSH, la gestion de fichiers SSH a distance et de nombreux autres outils. Termix est l'alternative parfaite, gratuite et auto-hebergee a Termius, disponible sur toutes les plateformes.
<br />
## Fonctionnalites
<table>
<tr>
<td width="50%" valign="top">
**Acces terminal SSH:**
Terminal complet avec support d'ecran partage (jusqu'a 4 panneaux) et un systeme d'onglets inspire des navigateurs. Inclut la personnalisation du terminal avec des themes courants, des polices et d'autres composants.
</td>
<td width="50%" valign="top">
**Acces Bureau a Distance:**
Support RDP, VNC et Telnet via navigateur avec personnalisation complete et ecran partage.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestion des tunnels SSH:**
Creez et gerez des tunnels SSH de serveur a serveur avec reconnexion automatique, surveillance de l'etat et transfert local, distant ou SOCKS dynamique. Les parametres de tunnel client-bureau-vers-serveur sont stockes localement par installation bureau ; des instantanes de prereglages C2S optionnels peuvent etre sauvegardes sur le serveur, renommes, charges ou supprimes pour deplacer une configuration de tunnel locale entre clients.
</td>
<td width="50%" valign="top">
**Gestionnaire de fichiers distant:**
Gerez les fichiers directement sur les serveurs distants avec support de la visualisation et de l'edition de code, images, audio et video. Televersez, telechargez, renommez, supprimez et deplacez des fichiers de maniere fluide avec support sudo. Inclut la prise en charge du deplacement de fichiers de serveur a serveur.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestion Docker et Podman:**
Demarrez, arretez, mettez en pause, supprimez des conteneurs. Consultez les statistiques des conteneurs. Controlez les conteneurs via le terminal docker exec. Compatible avec Docker et Podman comme environnement d'execution de conteneurs. Non concu pour remplacer Portainer ou Dockge, mais plutot pour gerer simplement vos conteneurs plutot que de les creer.
</td>
<td width="50%" valign="top">
**Gestionnaire d'hotes SSH:**
Enregistrez, organisez et gerez vos connexions SSH avec des tags et des dossiers (personnalisation des dossiers et prise en charge des dossiers imbriques), et sauvegardez facilement les informations de connexion reutilisables tout en automatisant le deploiement des cles SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Metriques d'hote:**
Visualisez l'utilisation du CPU, de la memoire, du disque, le reseau, le temps de fonctionnement, les informations systeme, le pare-feu, le moniteur de ports, le visualiseur de journaux, les utilisateurs/permissions, les certificats et bien plus encore sur la plupart des serveurs Linux. Inclut des graphiques d'historique en serie temporelle et des alertes basees sur des seuils avec support ntfy et webhook.
</td>
<td width="50%" valign="top">
**Authentification des utilisateurs:**
Gestion securisee des utilisateurs avec controles administrateur (peut modifier les informations des autres utilisateurs) et support OIDC/LDAP/SSO (avec controle d'acces), 2FA (TOTP), et support des passkeys (WebAuthn). Visualisez les sessions utilisateur actives sur toutes les plateformes et revoquez les permissions. Liez vos comptes OIDC/locaux ensemble. Consultez le journal d'audit des actions de tous les utilisateurs.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Integration Tailscale:**
Listez les appareils de votre reseau Tailscale pour les ajouter rapidement comme hotes, et connectez-vous en utilisant Tailscale SSH comme methode d'authentification, laissant les ACL de votre reseau gerer l'autorisation sans stocker de credentials.
</td>
<td width="50%" valign="top">
**RBAC/Partage:**
Creez des roles et partagez des hotes entre utilisateurs/roles. Prend en charge tous les types d'authentification et tous les protocoles d'hote.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Connexions Serie:**
Connectez-vous a des appareils serie (routeurs, commutateurs, microcontroleurs, etc.) directement depuis le navigateur ou l'application bureau. Configurez le debit en bauds, les bits de donnees, les bits d'arret et la parite. Utilise l'API Web Serial dans les navigateurs compatibles ou un backend natif dans l'application Electron.
</td>
<td width="50%" valign="top">
**Alertes:**
Definissez des regles d'alerte basees sur des seuils pour les metriques d'hote (CPU, memoire, disque, etc.) et recevez des notifications via ntfy ou webhooks lorsqu'elles se declenchent. Consultez les alertes actives et resolues dans un journal d'historique.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Page d'accueil:**
Une page d'accueil entierement personnalisable avec une grille de widgets glisser-deposer. Ajoutez des widgets pour l'etat des hotes, les liens de services, les horloges, les notes, les flux RSS, la meteo, les conteneurs Docker, les graphiques de metriques d'hote, les terminaux integres, les iframes et plus encore.
</td>
<td width="50%" valign="top">
**Chiffrement de la base de donnees:**
Le backend est stocke sous forme de fichiers de base de donnees SQLite chiffres. Consultez la [documentation](https://docs.termix.site/security) pour plus de details.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Graphe reseau:**
Personnalisez votre tableau de bord pour visualiser votre homelab base sur vos connexions SSH avec support des statuts.
</td>
<td width="50%" valign="top">
**Outils SSH:**
Creez des extraits de commandes reutilisables executables en un seul clic. Executez une commande simultanement sur plusieurs terminaux ouverts.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Onglets Persistants:**
Les sessions SSH et les onglets restent ouverts sur tous les appareils/actualisations si active dans le profil utilisateur.
</td>
<td width="50%" valign="top">
**Langues:**
Support integre d'environ 30 langues (gere par [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Plus de fonctionnalites</b></summary>
<br />
- **Tableau de bord** - Consultez les informations de vos serveurs en un coup d'oeil depuis votre tableau de bord
- **Cles API** - Creez des cles API a portee utilisateur avec des dates d'expiration pour une utilisation en automatisation/CI
- **Export/Import de donnees** - Exportez et importez les hotes SSH, les identifiants et les donnees du gestionnaire de fichiers
- **Configuration SSL automatique** - Generation et gestion integrees de certificats SSL avec redirections HTTPS
- **Interface moderne** - Interface epuree compatible desktop/mobile construite avec React, Tailwind CSS et Shadcn. Choisissez parmi de nombreux themes d'interface utilisateur, notamment clair, sombre, Dracula, etc. Utilisez les routes URL pour ouvrir n'importe quelle connexion en plein ecran.
- **Historique des commandes** - Auto-completion et consultation des commandes SSH precedemment executees
- **Connexion rapide** - Connectez-vous a un serveur sans avoir a sauvegarder les donnees de connexion
- **Palette de commandes** - Appuyez deux fois sur Shift gauche pour acceder rapidement aux connexions SSH avec votre clavier
- **Integration Proxmox** - Ajoutez automatiquement des hotes dans Termix depuis votre instance Proxmox
- **SSH riche en fonctionnalites** - Support des hotes de rebond, Warpgate, connexions basees sur TOTP, SOCKS5, verification des cles d'hote, remplissage automatique des mots de passe, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, journalisation du terminal, transfert d'agent SSH, agent SSH Bitwarden, signature SSH HashiCorp Vault, et plus encore.
- **Termix ID** - Un equivalent de sshid.io integre a Termix. Reservez un identifiant, publiez vos cles SSH publiques a une URL de resolution, et utilisez une autorite de certification integree pour emettre des certificats SSH.
</details>
<br />
## Support des plateformes
<table align="center">
<tr>
<th align="center">Plateforme</th>
<th align="center">Distribution</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Tout navigateur moderne (Chrome, Safari, Firefox) · Support PWA</td>
Visitez la [documentation](https://docs.termix.site/install) de Termix pour des instructions d'installation completes sur toutes les plateformes.
Voici un exemple de fichier Docker Compose (vous pouvez omettre guacd et le reseau si vous ne prevoyez pas d'utiliser les fonctionnalites de bureau a distance) :
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Faire un don
Termix est gratuit et open source, sans abonnement ni plan payant. Si vous le trouvez utile, pensez a faire un don pour aider a couvrir les couts de serveur, les domaines et le temps de developpement. Les dons contribuent egalement a financer le temps necessaire pour rechercher et apprendre ce qui est requis pour construire des fonctionnalites comme SAML, Kubernetes et le support des agents. Suivez la progression et faites un don ci-dessous.
[Faire un don](https://donate.termix.site/)
<br />
## Sponsors
Interesse par un placement payant pour soutenir le developpement ? Envoyez un email a [mail@termix.site](mailto:mail@termix.site).
Si vous avez besoin d'aide ou souhaitez demander une fonctionnalite pour Termix, visitez la page [Issues](https://github.com/Termix-SSH/Support/issues), connectez-vous et appuyez sur `New Issue`. Veuillez etre aussi detaille que possible dans votre issue, de preference redigee en anglais. Vous pouvez egalement rejoindre le serveur [Discord](https://discord.gg/jVQGdvHDrf) et visiter le canal de support, cependant les temps de reponse peuvent etre plus longs.
<sub>Certaines videos et images peuvent etre obsoletes ou ne pas presenter parfaitement les fonctionnalites.</sub>
</div>
<br />
## Fonctionnalites prevues
Consultez les [Projects](https://github.com/orgs/Termix-SSH/projects/5) pour toutes les fonctionnalites prevues. Si vous souhaitez contribuer, consultez [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Licence
Distribue sous la licence Apache Version 2.0. Consultez `LICENSE` pour plus d'informations.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>1 सितंबर, 2025 को प्राप्त</sub>
</p>
</div>
<br />
## अवलोकन
Termix एक ओपन-सोर्स, हमेशा के लिए मुफ़्त, सेल्फ-होस्टेड ऑल-इन-वन सर्वर प्रबंधन प्लेटफ़ॉर्म है। यह एक एकल, सहज इंटरफ़ेस के माध्यम से आपके सर्वर और बुनियादी ढाँचे के प्रबंधन के लिए एक मल्टी-प्लेटफ़ॉर्म समाधान प्रदान करता है। Termix SSH टर्मिनल एक्सेस, रिमोट डेस्कटॉप कंट्रोल (RDP, VNC, Telnet), SSH टनलिंग क्षमताएँ, रिमोट फ़ाइल प्रबंधन, और कई अन्य उपकरण प्रदान करता है। Termix सभी प्लेटफ़ॉर्म पर उपलब्ध Termius का सही मुफ़्त और सेल्फ-होस्टेड विकल्प है।
<br />
## विशेषताएँ
<table>
<tr>
<td width="50%" valign="top">
**SSH टर्मिनल एक्सेस:**
ब्राउज़र जैसी टैब प्रणाली के साथ स्प्लिट-स्क्रीन सपोर्ट (4 पैनल तक) वाला पूर्ण-विशेषता वाला टर्मिनल। इसमें लोकप्रिय टर्मिनल थीम, फ़ॉन्ट और अन्य कंपोनेंट सहित टर्मिनल को कस्टमाइज़ करने का सपोर्ट शामिल है।
</td>
<td width="50%" valign="top">
**रिमोट डेस्कटॉप एक्सेस:**
ब्राउज़र पर RDP, VNC और Telnet सपोर्ट, पूर्ण कस्टमाइज़ेशन और स्प्लिट स्क्रीन के साथ।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**SSH टनल प्रबंधन:**
ऑटोमैटिक रीकनेक्शन, हेल्थ मॉनिटरिंग और लोकल, रिमोट या डायनेमिक SOCKS फॉरवर्डिंग के साथ सर्वर-टु-सर्वर SSH टनल बनाएँ और प्रबंधित करें। डेस्कटॉप क्लाइंट-टु-सर्वर टनल सेटिंग्स प्रत्येक डेस्कटॉप इंस्टॉल में स्थानीय रूप से संग्रहीत होती हैं; वैकल्पिक C2S प्रीसेट स्नैपशॉट सर्वर पर सेव किए जा सकते हैं, तथा जब आप किसी लोकल टनल कॉन्फ़िगरेशन को क्लाइंट के बीच स्थानांतरित करना चाहें तो उन्हें रीनेम, लोड या डिलीट किया जा सकता है।
</td>
<td width="50%" valign="top">
**रिमोट फ़ाइल मैनेजर:**
कोड, इमेज, ऑडियो और वीडियो देखने और संपादित करने के सपोर्ट के साथ रिमोट सर्वर पर सीधे फ़ाइलें प्रबंधित करें। sudo सपोर्ट के साथ फ़ाइलें अपलोड, डाउनलोड, रीनेम, डिलीट और मूव करें। इसमें फ़ाइलों को एक सर्वर से दूसरे सर्वर में स्थानांतरित करने का सपोर्ट भी शामिल है।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Docker और Podman प्रबंधन:**
कंटेनर शुरू, बंद, पॉज़, हटाएँ। कंटेनर स्टैट्स देखें। docker exec टर्मिनल का उपयोग करके कंटेनर को नियंत्रित करें। Docker और Podman दोनों को कंटेनर रनटाइम के रूप में सपोर्ट करता है। इसे Portainer या Dockge की जगह लेने के लिए नहीं बनाया गया बल्कि कंटेनर बनाने की तुलना में उन्हें सरलता से प्रबंधित करने के लिए बनाया गया है।
</td>
<td width="50%" valign="top">
**SSH होस्ट मैनेजर:**
टैग और फ़ोल्डर (फ़ोल्डर कस्टमाइज़ेशन और नेस्टेड फ़ोल्डर सपोर्ट के साथ) के साथ अपने SSH कनेक्शन सहेजें, व्यवस्थित करें और प्रबंधित करें, और SSH कुंजियों की तैनाती को स्वचालित करने की क्षमता के साथ पुन: उपयोग योग्य लॉगिन जानकारी आसानी से सहेजें।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**होस्ट मेट्रिक्स:**
अधिकांश Linux आधारित सर्वर पर CPU, मेमोरी, डिस्क उपयोग, नेटवर्क, अपटाइम, सिस्टम जानकारी, फ़ायरवॉल, पोर्ट मॉनिटर, लॉग व्यूअर, उपयोगकर्ता/अनुमतियाँ, सर्टिफ़िकेट और भी बहुत कुछ देखें। इसमें टाइम-सीरीज़ हिस्ट्री ग्राफ़ और ntfy व webhook सपोर्ट के साथ थ्रेशोल्ड-आधारित अलर्ट शामिल हैं।
</td>
<td width="50%" valign="top">
**उपयोगकर्ता प्रमाणीकरण:**
व्यवस्थापक नियंत्रण (अन्य उपयोगकर्ताओं की जानकारी संपादित कर सकते हैं) और OIDC/LDAP/SSO (एक्सेस कंट्रोल के साथ), 2FA (TOTP), और पासकी (WebAuthn) सपोर्ट के साथ सुरक्षित उपयोगकर्ता प्रबंधन। सभी प्लेटफ़ॉर्म पर सक्रिय उपयोगकर्ता सत्र देखें और अनुमतियाँ रद्द करें। अपने OIDC/स्थानीय खातों को एक साथ जोड़ें। सभी उपयोगकर्ताओं की कार्रवाइयों का ऑडिट लॉग देखें।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscale एकीकरण:**
अपने Tailscale नेटवर्क के डिवाइस सूचीबद्ध करें ताकि उन्हें जल्दी से होस्ट के रूप में जोड़ा जा सके, और Tailscale SSH को प्रमाणीकरण विधि के रूप में उपयोग करके कनेक्ट करें, जिससे आपके Tailscale ACL क्रेडेंशियल संग्रहीत किए बिना प्राधिकरण संभाल सकें।
</td>
<td width="50%" valign="top">
**RBAC/शेयरिंग:**
भूमिकाएँ बनाएँ और उपयोगकर्ताओं/भूमिकाओं में होस्ट साझा करें। सभी प्रमाणीकरण प्रकारों और सभी होस्ट प्रोटोकॉल का सपोर्ट करता है।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**सीरियल कनेक्शन:**
सीरियल डिवाइस (राउटर, स्विच, माइक्रोकंट्रोलर आदि) से सीधे ब्राउज़र या डेस्कटॉप ऐप से कनेक्ट करें। बॉड रेट, डेटा बिट्स, स्टॉप बिट्स और पैरिटी कॉन्फ़िगर करें। समर्थित ब्राउज़र में Web Serial API या Electron ऐप में नेटिव बैकएंड का उपयोग करता है।
</td>
<td width="50%" valign="top">
**अलर्ट:**
होस्ट मेट्रिक्स (CPU, मेमोरी, डिस्क आदि) पर थ्रेशोल्ड-आधारित अलर्ट नियम सेट करें और जब वे ट्रिगर हों तो ntfy या webhooks के माध्यम से सूचना पाएँ। इतिहास लॉग में सक्रिय और हल किए गए अलर्ट देखें।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**होमपेज:**
ड्रैग-एंड-ड्रॉप विजेट ग्रिड के साथ पूरी तरह से कस्टमाइज़ करने योग्य होमपेज। होस्ट स्टेटस, सर्विस लिंक, घड़ियाँ, नोट्स, RSS फ़ीड, मौसम, Docker कंटेनर, होस्ट मेट्रिक्स चार्ट, एम्बेडेड टर्मिनल, iframes और अन्य के लिए विजेट जोड़ें।
</td>
<td width="50%" valign="top">
**डेटाबेस एन्क्रिप्शन:**
बैकएंड एन्क्रिप्टेड SQLite डेटाबेस फ़ाइलों के रूप में संग्रहीत। अधिक जानकारी के लिए [डॉक्स](https://docs.termix.site/security) देखें।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**नेटवर्क ग्राफ़:**
स्थिति सपोर्ट के साथ अपने SSH कनेक्शन के आधार पर अपने होमलैब को विज़ुअलाइज़ करने के लिए अपना डैशबोर्ड कस्टमाइज़ करें।
</td>
<td width="50%" valign="top">
**SSH टूल्स:**
एक क्लिक से निष्पादित होने वाले पुन: उपयोग योग्य कमांड स्निपेट बनाएँ। एक साथ कई खुले टर्मिनलों में एक कमांड चलाएँ।
</td>
</tr>
<tr>
<td width="50%" valign="top">
**परसिस्टेंट टैब:**
उपयोगकर्ता प्रोफ़ाइल में सक्षम होने पर SSH सेशन और टैब डिवाइस/रीफ्रेश के पार खुले रहते हैं।
</td>
<td width="50%" valign="top">
**भाषाएँ:**
लगभग 30 भाषाओं का बिल्ट-इन सपोर्ट ([Crowdin](https://docs.termix.site/translations) द्वारा प्रबंधित)।
</td>
</tr>
</table>
<br />
<details>
<summary><b>अधिक विशेषताएँ</b></summary>
<br />
- **डैशबोर्ड** - अपने डैशबोर्ड पर एक नज़र में सर्वर की जानकारी देखें
- **API कुंजियाँ** - ऑटोमेशन/CI के लिए उपयोग हेतु समाप्ति तिथियों के साथ उपयोगकर्ता-स्कोप्ड API कुंजियाँ बनाएँ
- **डेटा एक्सपोर्ट/इम्पोर्ट** - SSH होस्ट, क्रेडेंशियल और फ़ाइल मैनेजर डेटा एक्सपोर्ट और इम्पोर्ट करें
- **स्वचालित SSL सेटअप** - HTTPS रीडायरेक्ट के साथ बिल्ट-इन SSL सर्टिफ़िकेट जनरेशन और प्रबंधन
- **आधुनिक UI** - React, Tailwind CSS, और Shadcn से बना साफ़ डेस्कटॉप/मोबाइल-फ़्रेंडली इंटरफ़ेस। लाइट, डार्क, ड्रैकुला आदि सहित कई अलग-अलग UI थीम के बीच चुनें। किसी भी कनेक्शन को फ़ुल-स्क्रीन में खोलने के लिए URL रूट का उपयोग करें।
- **कमांड इतिहास** - पहले चलाए गए SSH कमांड का ऑटो-कम्प्लीट और दृश्य
- **क्विक कनेक्ट** - कनेक्शन डेटा सहेजे बिना सर्वर से कनेक्ट करें
- **कमांड पैलेट** - अपने कीबोर्ड से SSH कनेक्शन तक त्वरित पहुँच के लिए बाएँ Shift को दो बार टैप करें
- **Proxmox एकीकरण** - अपने Proxmox इंस्टेंस से Termix में होस्ट स्वचालित रूप से जोड़ें
- **SSH सुविधाओं से भरपूर** - जम्प होस्ट, Warpgate, TOTP आधारित कनेक्शन, SOCKS5, होस्ट की वेरिफ़िकेशन, पासवर्ड ऑटोफ़िल, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, पोर्ट नॉकिंग, टर्मिनल लॉगिंग, SSH एजेंट फ़ॉरवर्डिंग, Bitwarden SSH एजेंट, HashiCorp Vault SSH सिग्निंग, और अन्य का सपोर्ट।
- **Termix ID** - Termix में बिल्ट-इन sshid.io के समकक्ष। एक हैंडल क्लेम करें, अपनी सार्वजनिक SSH कुंजियों को एक रिज़ॉल्वर URL पर प्रकाशित करें, और SSH सर्टिफ़िकेट जारी करने के लिए बिल्ट-इन CA का उपयोग करें।
</details>
<br />
## प्लेटफ़ॉर्म सपोर्ट
<table align="center">
<tr>
<th align="center">प्लेटफ़ॉर्म</th>
<th align="center">वितरण</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>कोई भी आधुनिक ब्राउज़र (Chrome, Safari, Firefox) · PWA सपोर्ट</td>
सभी प्लेटफ़ॉर्म पर पूर्ण इंस्टॉलेशन निर्देशों के लिए Termix [डॉक्स](https://docs.termix.site/install) पर जाएँ।
नमूना Docker Compose फ़ाइल (यदि आप रिमोट डेस्कटॉप सुविधाओं का उपयोग करने की योजना नहीं बना रहे हैं तो आप `guacd` और नेटवर्क को हटा सकते हैं):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## दान करें
Termix मुफ़्त और ओपन सोर्स है, बिना किसी सब्सक्रिप्शन या पेड प्लान के। यदि आपको यह उपयोगी लगता है, तो सर्वर लागत, डोमेन और विकास समय को कवर करने में मदद के लिए दान करने पर विचार करें। दान SAML, Kubernetes, और Agent सपोर्ट जैसी सुविधाओं के निर्माण के लिए आवश्यक शोध और सीखने में लगने वाले समय को वित्त पोषित करने में भी मदद करते हैं। नीचे प्रगति देखें और दान करें।
[दान करें](https://donate.termix.site/)
<br />
## प्रायोजक
विकास को समर्थन देने के लिए पेड प्लेसमेंट में रुचि है? [mail@termix.site](mailto:mail@termix.site) पर ईमेल करें।
यदि आपको सहायता चाहिए या Termix के लिए किसी विशेषता का अनुरोध करना चाहते हैं, तो [इश्यूज़](https://github.com/Termix-SSH/Support/issues) पेज पर जाएँ, लॉग इन करें, और `New Issue` दबाएँ। कृपया अपने इश्यू में यथासंभव विस्तृत विवरण दें, अधिमानतः अंग्रेज़ी में लिखें। आप [Discord](https://discord.gg/jVQGdvHDrf) सर्वर में भी शामिल हो सकते हैं और सहायता चैनल पर जा सकते हैं, हालाँकि, प्रतिक्रिया समय अधिक हो सकता है।
<sub>कुछ वीडियो और छवियाँ पुरानी हो सकती हैं या विशेषताओं को पूरी तरह से प्रदर्शित नहीं कर सकती हैं।</sub>
</div>
<br />
## नियोजित विशेषताएँ
सभी नियोजित विशेषताओं के लिए [प्रोजेक्ट्स](https://github.com/orgs/Termix-SSH/projects/5) देखें। यदि आप योगदान देना चाहते हैं, तो [योगदान](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md) देखें।
<br />
## लाइसेंस
Apache License Version 2.0 के तहत वितरित। अधिक जानकारी के लिए `LICENSE` देखें।
<a href="https://donate.termix.site/"><img alt="Donazioni di questo mese" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donazioni%20di%20questo%20mese&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix è gratuito e open source. Se lo trovi utile, considera di [donare](https://donate.termix.site/) per aiutare a coprire i costi del server e il tempo di sviluppo.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Ottenuto il 1 settembre 2025</sub>
</p>
</div>
<br />
## Panoramica
Termix è una piattaforma di gestione server tutto-in-uno, open-source, per sempre gratuita e self-hosted. Fornisce una soluzione multipiattaforma per gestire i tuoi server e la tua infrastruttura attraverso un'unica interfaccia intuitiva. Termix offre accesso al terminale SSH, controllo remoto del desktop (RDP, VNC, Telnet), funzionalità di tunneling SSH, gestione remota dei file e molti altri strumenti. Termix è la perfetta alternativa gratuita e self-hosted a Termius, disponibile per tutte le piattaforme.
<br />
## Funzionalità
<table>
<tr>
<td width="50%" valign="top">
**Accesso Terminale SSH:**
Terminale completo con supporto schermo diviso (fino a 4 pannelli) con un sistema di schede in stile browser. Include il supporto per la personalizzazione del terminale, inclusi temi, font e altri componenti comuni.
</td>
<td width="50%" valign="top">
**Accesso Desktop Remoto:**
Supporto RDP, VNC e Telnet tramite browser con personalizzazione completa e schermo diviso.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestione Tunnel SSH:**
Crea e gestisci tunnel SSH da server a server con riconnessione automatica, monitoraggio dello stato e inoltro locale, remoto o SOCKS dinamico. Le impostazioni del tunnel da client desktop a server sono archiviate localmente per ogni installazione desktop; gli snapshot di preset C2S opzionali possono essere salvati sul server, rinominati, caricati o eliminati per spostare una configurazione di tunnel locale tra i client.
</td>
<td width="50%" valign="top">
**Gestore File Remoto:**
Gestisci i file direttamente sui server remoti con supporto per la visualizzazione e la modifica di codice, immagini, audio e video. Carica, scarica, rinomina, elimina e sposta file senza problemi con supporto sudo. Include il supporto per spostare file da server a server.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gestione Docker e Podman:**
Avvia, ferma, metti in pausa, rimuovi container. Visualizza le statistiche dei container. Controlla i container tramite terminale docker exec. Supporta sia Docker che Podman come runtime dei container. Non è stato creato per sostituire Portainer o Dockge, ma piuttosto per gestire semplicemente i tuoi container rispetto alla loro creazione.
</td>
<td width="50%" valign="top">
**Gestore Host SSH:**
Salva, organizza e gestisci le tue connessioni SSH con tag e cartelle (con personalizzazione delle cartelle e supporto per cartelle annidate), salva facilmente le informazioni di accesso riutilizzabili e automatizza il deployment delle chiavi SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Metriche Host:**
Visualizza CPU, memoria, utilizzo del disco, rete, uptime, informazioni di sistema, firewall, monitoraggio porte, visualizzatore di log, utenti/permessi, certificati e molto altro, funzionanti sulla maggior parte dei server basati su Linux. Include grafici storici delle serie temporali e avvisi basati su soglie con supporto ntfy e webhook.
</td>
<td width="50%" valign="top">
**Autenticazione Utente:**
Gestione utenti sicura con controlli amministrativi (può modificare le informazioni di altri utenti) e OIDC/LDAP/SSO (con controllo degli accessi), 2FA (TOTP) e supporto passkey (WebAuthn). Visualizza le sessioni utente attive su tutte le piattaforme e revoca i permessi. Collega i tuoi account OIDC/Locali tra loro. Visualizza il log di controllo delle azioni di tutti gli utenti.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Integrazione Tailscale:**
Elenca i dispositivi della tua rete Tailscale per aggiungerli rapidamente come host, e connettiti utilizzando Tailscale SSH come metodo di autenticazione, lasciando che le ACL della tua rete gestiscano l'autorizzazione senza memorizzare credenziali.
</td>
<td width="50%" valign="top">
**RBAC/Condivisione:**
Crea ruoli e condividi host tra utenti/ruoli. Supporta tutti i tipi di autenticazione e tutti i protocolli host.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Connessioni Seriali:**
Connettiti a dispositivi seriali (router, switch, microcontrollori, ecc.) direttamente dal browser o dall'app desktop. Configura baud rate, bit di dati, bit di stop e parità. Utilizza la Web Serial API nei browser supportati o un backend nativo nell'app Electron.
</td>
<td width="50%" valign="top">
**Avvisi:**
Imposta regole di avviso basate su soglie per le metriche dell'host (CPU, memoria, disco, ecc.) e ricevi notifiche tramite ntfy o webhook quando si attivano. Visualizza gli avvisi attivi e risolti in un registro storico.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Homepage:**
Una homepage completamente personalizzabile con una griglia di widget drag-and-drop. Aggiungi widget per lo stato dell'host, link ai servizi, orologi, note, feed RSS, meteo, container Docker, grafici delle metriche dell'host, terminali incorporati, iframe e altro ancora.
</td>
<td width="50%" valign="top">
**Crittografia Database:**
Il backend è archiviato come file di database SQLite crittografati. Consulta la [documentazione](https://docs.termix.site/security) per maggiori informazioni.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Grafico di Rete:**
Personalizza la tua Dashboard per visualizzare il tuo homelab basato sulle connessioni SSH con supporto dello stato.
</td>
<td width="50%" valign="top">
**Strumenti SSH:**
Crea snippet di comandi riutilizzabili che si eseguono con un singolo clic. Esegui un comando simultaneamente su più terminali aperti.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Schede Persistenti:**
Le sessioni SSH e le schede rimangono aperte tra dispositivi/aggiornamenti se abilitato nel profilo utente.
</td>
<td width="50%" valign="top">
**Lingue:**
Supporto integrato per circa 30 lingue (gestito da [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Altre funzionalità</b></summary>
<br />
- **Dashboard** - Visualizza le informazioni del server a colpo d'occhio sulla tua dashboard
- **Chiavi API** - Crea chiavi API con ambito utente e date di scadenza da utilizzare per automazione/CI
- **Esportazione/Importazione Dati** - Esporta e importa host SSH, credenziali e dati del gestore file
- **Configurazione SSL Automatica** - Generazione e gestione integrata dei certificati SSL con reindirizzamenti HTTPS
- **Interfaccia Moderna** - Interfaccia pulita e responsive per desktop/mobile costruita con React, Tailwind CSS e Shadcn. Scegli tra molti temi UI diversi, inclusi chiaro, scuro, Dracula, ecc. Usa i percorsi URL per aprire qualsiasi connessione a schermo intero.
- **Cronologia Comandi** - Autocompletamento e visualizzazione dei comandi SSH eseguiti in precedenza
- **Connessione Rapida** - Connettiti a un server senza dover salvare i dati di connessione
- **Palette Comandi** - Premi due volte shift sinistro per accedere rapidamente alle connessioni SSH con la tastiera
- **Integrazione Proxmox** - Aggiungi automaticamente host a Termix dalla tua istanza Proxmox
- **SSH Ricco di Funzionalità** - Supporta jump host, Warpgate, connessioni basate su TOTP, SOCKS5, verifica chiave host, compilazione automatica password, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, registrazione terminale, SSH agent forwarding, Bitwarden SSH agent, firma SSH HashiCorp Vault e altro ancora
- **Termix ID** - L'equivalente di sshid.io integrato in Termix. Rivendica un handle, pubblica le tue chiavi SSH pubbliche su un URL resolver e utilizza una CA integrata per emettere certificati SSH
Visita la [Documentazione Termix](https://docs.termix.site/install) per le istruzioni complete di installazione su tutte le piattaforme.
File Docker Compose di esempio (puoi omettere `guacd` e la rete se non prevedi di utilizzare le funzioni di desktop remoto):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Dona
Termix è gratuito e open source, senza abbonamenti o piani a pagamento. Se lo trovi utile, considera di donare per aiutare a coprire i costi del server, i domini e il tempo di sviluppo. Le donazioni aiutano anche a finanziare il tempo necessario per ricercare e imparare ciò che serve per costruire funzionalità come SAML, Kubernetes e supporto Agent. Segui i progressi e dona qui sotto.
[Dona](https://donate.termix.site/)
<br />
## Sponsor
Interessato a un posizionamento a pagamento per supportare lo sviluppo? Scrivi a [mail@termix.site](mailto:mail@termix.site).
Se hai bisogno di aiuto o vuoi richiedere una funzionalità per Termix, visita la pagina [Issues](https://github.com/Termix-SSH/Support/issues), accedi e premi `New Issue`. Per favore, sii il più dettagliato possibile nella tua segnalazione, preferibilmente scritta in inglese. Puoi anche unirti al server [Discord](https://discord.gg/jVQGdvHDrf) e visitare il canale di supporto, tuttavia i tempi di risposta potrebbero essere più lunghi.
<sub>Alcuni video e immagini potrebbero non essere aggiornati o potrebbero non mostrare perfettamente le funzionalità.</sub>
</div>
<br />
## Funzionalità Pianificate
Consulta [Projects](https://github.com/orgs/Termix-SSH/projects/5) per tutte le funzionalità pianificate. Se desideri contribuire, consulta [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Licenza
Distribuito sotto la Licenza Apache Versione 2.0. Consulta `LICENSE` per maggiori informazioni.
ブラウザまたはデスクトップアプリからシリアルデバイス(ルーター、スイッチ、マイクロコントローラーなど)に直接接続できます。ボーレート、データビット、ストップビット、パリティを設定できます。対応ブラウザではWeb Serial APIを使用し、Electronアプリではネイティブバックエンドを使用します。
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>2025년 9월 1일 달성</sub>
</p>
</div>
<br />
## 개요
Termix는 오픈 소스이며 영구 무료인 셀프 호스팅 올인원 서버 관리 플랫폼입니다. 단일 직관적인 인터페이스를 통해 서버와 인프라를 관리할 수 있는 멀티 플랫폼 솔루션을 제공합니다. Termix는 SSH 터미널 접속, 원격 데스크톱 제어(RDP, VNC, Telnet), SSH 터널링 기능, 원격 SSH 파일 관리 및 기타 다양한 도구를 제공합니다. Termix는 모든 플랫폼에서 사용 가능한 Termius의 완벽한 무료 셀프 호스팅 대안입니다.
<br />
## 기능
<table>
<tr>
<td width="50%" valign="top">
**SSH 터미널 접속:**
브라우저 스타일 탭 시스템과 분할 화면 지원(최대 4개 패널)을 갖춘 완전한 기능의 터미널. 일반 터미널 테마, 글꼴 및 기타 구성 요소를 포함한 터미널 사용자 정의 지원.
</td>
<td width="50%" valign="top">
**원격 데스크톱 접속:**
완전한 사용자 정의와 분할 화면을 지원하는 브라우저 기반 RDP, VNC, Telnet 지원.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**SSH 터널 관리:**
자동 재연결, 상태 모니터링, 로컬·원격·동적 SOCKS 포워딩을 지원하는 서버 간 SSH 터널 생성 및 관리. 데스크톱 클라이언트-서버 터널 설정은 데스크톱 설치별로 로컬에 저장되며, 선택적 C2S 사전 설정 스냅샷을 서버에 저장·이름 변경·불러오기·삭제하여 클라이언트 간 로컬 터널 구성을 이동할 수 있습니다.
</td>
<td width="50%" valign="top">
**원격 파일 관리자:**
코드, 이미지, 오디오, 비디오의 보기 및 편집을 지원하여 원격 서버에서 파일을 직접 관리. sudo 지원으로 파일 업로드, 다운로드, 이름 변경, 삭제, 이동을 원활하게 수행. 서버 간 파일 이동도 지원합니다.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Docker 및 Podman 관리:**
컨테이너 시작, 중지, 일시 정지, 제거. 컨테이너 통계 보기. docker exec 터미널로 컨테이너 제어. Docker와 Podman을 모두 컨테이너 런타임으로 지원. Portainer나 Dockge를 대체하기 위한 것이 아니라 컨테이너 생성보다는 간편한 관리를 목적으로 합니다.
</td>
<td width="50%" valign="top">
**SSH 호스트 관리자:**
태그와 폴더(폴더 사용자 지정 및 중첩 폴더 지원)로 SSH 연결을 저장, 정리, 관리하고, 재사용 가능한 로그인 정보를 쉽게 저장하면서 SSH 키 배포를 자동화.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**호스트 메트릭:**
대부분의 Linux 기반 서버에서 CPU, 메모리, 디스크 사용량, 네트워크, 업타임, 시스템 정보, 방화벽, 포트 모니터, 로그 뷰어, 사용자/권한, 인증서 등 다양한 정보를 표시. 시계열 히스토리 그래프와 ntfy 및 웹훅을 지원하는 임계값 기반 알림을 포함합니다.
</td>
<td width="50%" valign="top">
**사용자 인증:**
관리자 제어(다른 사용자 정보 편집 가능)와 OIDC/LDAP/SSO(액세스 제어 포함), 2FA(TOTP), 패스키(WebAuthn) 지원을 통한 안전한 사용자 관리. 모든 플랫폼에서 활성 사용자 세션을 보고 권한을 취소 가능. OIDC/로컬 계정 연동. 모든 사용자 작업의 감사 로그 조회.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscale 통합:**
Tailscale 네트워크의 기기를 나열하여 호스트로 빠르게 추가하고, Tailscale SSH를 인증 방법으로 사용하여 연결함으로써 자격 증명을 저장하지 않고도 네트워크 ACL이 권한 부여를 처리하도록 합니다.
</td>
<td width="50%" valign="top">
**RBAC/공유:**
역할을 생성하고 사용자/역할 간에 호스트를 공유합니다. 모든 인증 유형과 모든 호스트 프로토콜을 지원합니다.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**시리얼 연결:**
브라우저 또는 데스크톱 앱에서 직접 시리얼 장치(라우터, 스위치, 마이크로컨트롤러 등)에 연결. 보드레이트, 데이터 비트, 스톱 비트, 패리티 구성. 지원 브라우저에서는 Web Serial API를, Electron 앱에서는 네이티브 백엔드를 사용합니다.
</td>
<td width="50%" valign="top">
**알림:**
호스트 메트릭(CPU, 메모리, 디스크 등)에 대한 임계값 기반 알림 규칙을 설정하고 트리거될 때 ntfy 또는 웹훅을 통해 알림 수신. 기록 로그에서 발생 중인 알림과 해결된 알림 확인.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**홈페이지:**
드래그 앤 드롭 위젯 그리드를 갖춘 완전 맞춤형 홈페이지. 호스트 상태, 서비스 링크, 시계, 메모, RSS 피드, 날씨, Docker 컨테이너, 호스트 메트릭 차트, 임베디드 터미널, iframe 등의 위젯 추가 가능.
</td>
<td width="50%" valign="top">
**데이터베이스 암호화:**
백엔드가 암호화된 SQLite 데이터베이스 파일로 저장됨. 자세한 내용은 [문서](https://docs.termix.site/security)를 참조하세요.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**네트워크 그래프:**
대시보드를 사용자 정의하여 SSH 연결 기반의 홈랩 네트워크를 상태 표시와 함께 시각화.
</td>
<td width="50%" valign="top">
**SSH 도구:**
한 번의 클릭으로 실행 가능한 재사용 가능 명령어 스니펫 생성. 여러 열린 터미널에서 동시에 하나의 명령어 실행.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**지속 탭:**
사용자 프로필에서 활성화된 경우 SSH 세션 및 탭이 기기/새로 고침 간에 열린 상태 유지.
</td>
<td width="50%" valign="top">
**다국어 지원:**
약 30개 언어 내장 지원([Crowdin](https://docs.termix.site/translations)으로 관리).
</td>
</tr>
</table>
<br />
<details>
<summary><b>더 많은 기능</b></summary>
<br />
- **대시보드** - 대시보드에서 서버 정보를 한눈에 확인
- **API 키** - 자동화/CI에 사용할 만료일이 있는 사용자 범위 API 키 생성
- **데이터 내보내기/가져오기** - SSH 호스트, 자격 증명, 파일 관리자 데이터의 내보내기 및 가져오기
- **자동 SSL 설정** - HTTPS 리디렉션을 포함한 내장 SSL 인증서 생성 및 관리
- **모던 UI** - React, Tailwind CSS, Shadcn으로 구축된 깔끔한 데스크톱/모바일 친화적 인터페이스. 라이트, 다크, 드라큘라 등 다양한 UI 테마 선택 가능. URL 라우트를 사용하여 모든 연결을 전체 화면으로 열기 가능.
- **명령어 기록** - 이전에 실행한 SSH 명령어의 자동 완성 및 조회
- **빠른 연결** - 연결 데이터를 저장하지 않고 서버에 접속
- **명령어 팔레트** - 왼쪽 Shift 키를 두 번 눌러 키보드로 SSH 연결에 빠르게 접근
- **Proxmox 통합** - Proxmox 인스턴스에서 Termix로 호스트를 자동 추가
- **풍부한 SSH 기능** - 점프 호스트, Warpgate, TOTP 기반 연결, SOCKS5, 호스트 키 검증, 비밀번호 자동 입력, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, 포트 노킹, 터미널 로깅, SSH 에이전트 포워딩, Bitwarden SSH 에이전트, HashiCorp Vault SSH 서명 등 지원.
- **Termix ID** - Termix에 내장된 sshid.io와 동등한 기능. 핸들을 등록하고, 리졸버 URL에 공개 SSH 키를 게시하며, 내장 CA를 사용하여 SSH 인증서를 발급할 수 있습니다.
</details>
<br />
## 플랫폼 지원
<table align="center">
<tr>
<th align="center">플랫폼</th>
<th align="center">배포판</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>모든 최신 브라우저(Chrome, Safari, Firefox) · PWA 지원</td>
모든 플랫폼에 Termix를 설치하는 방법에 대한 자세한 내용은 Termix [문서](https://docs.termix.site/install)를 방문하세요.
다음은 Docker Compose 파일 예시입니다(원격 데스크톱 기능을 사용할 계획이 없다면 guacd와 네트워크를 생략할 수 있습니다):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## 후원
Termix는 구독이나 유료 요금제가 없는 무료 오픈소스 프로젝트입니다. 유용하게 사용하고 있다면 서버 비용, 도메인, 개발 시간을 위해 후원을 고려해 주세요. 후원은 SAML, Kubernetes, 에이전트 지원과 같은 기능을 구축하는 데 필요한 사항을 연구하고 학습하는 시간에도 사용됩니다. 아래에서 진행 상황을 확인하고 후원할 수 있습니다.
[후원하기](https://donate.termix.site/)
<br />
## 스폰서
개발 지원을 위한 유료 광고에 관심이 있으신가요? [mail@termix.site](mailto:mail@termix.site)로 이메일을 보내주세요.
Termix에 대한 도움이 필요하거나 기능을 요청하려면 [Issues](https://github.com/Termix-SSH/Support/issues) 페이지를 방문하여 로그인하고 `New Issue`를 누르세요. 이슈는 가능한 한 상세하게 작성하고, 영어로 작성하는 것이 좋습니다. [Discord](https://discord.gg/jVQGdvHDrf) 서버에 참여하여 지원 채널을 이용할 수도 있지만, 응답 시간이 더 길 수 있습니다.
<sub>일부 비디오 및 이미지는 최신이 아니거나 기능을 완벽하게 보여주지 않을 수 있습니다.</sub>
</div>
<br />
## 계획된 기능
모든 계획된 기능은 [Projects](https://github.com/orgs/Termix-SSH/projects/5)를 참조하세요. 기여를 원하시면 [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md)을 참조하세요.
<br />
## 라이선스
Apache License Version 2.0에 따라 배포됩니다. 자세한 내용은 `LICENSE`를 참조하세요.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix é gratuito e de código aberto. Se o achar útil, considere [doar](https://donate.termix.site/) para ajudar a cobrir os custos de servidor e o tempo de desenvolvimento.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Conquistado em 1 de setembro de 2025</sub>
</p>
</div>
<br />
## Visao Geral
Termix e uma plataforma de gerenciamento de servidores tudo-em-um, de codigo aberto, sempre gratuita e auto-hospedada. Ela fornece uma solucao multiplataforma para gerenciar seus servidores e infraestrutura atraves de uma interface unica e intuitiva. Termix oferece acesso a terminal SSH, controle de desktop remoto (RDP, VNC, Telnet), capacidades de tunelamento SSH, gerenciamento remoto de arquivos SSH e muitas outras ferramentas. Termix e a alternativa perfeita, gratuita e auto-hospedada ao Termius, disponivel para todas as plataformas.
<br />
## Funcionalidades
<table>
<tr>
<td width="50%" valign="top">
**Acesso ao Terminal SSH:**
Terminal completo com suporte a tela dividida (ate 4 paineis) com um sistema de abas similar ao navegador. Inclui suporte para personalizacao do terminal incluindo temas comuns de terminal, fontes e outros componentes.
</td>
<td width="50%" valign="top">
**Acesso a Area de Trabalho Remota:**
Suporte a RDP, VNC e Telnet pelo navegador com personalizacao completa e tela dividida.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gerenciamento de Tuneis SSH:**
Crie e gerencie tuneis SSH de servidor para servidor com reconexao automatica, monitoramento de saude e encaminhamento local, remoto ou SOCKS dinamico. As configuracoes de tunel de cliente desktop para servidor sao armazenadas localmente por instalacao de desktop, snapshots de predefinicoes C2S opcionais podem ser salvos no servidor, renomeados, carregados ou excluidos quando voce quiser mover uma configuracao de tunel local entre clientes.
</td>
<td width="50%" valign="top">
**Gerenciador Remoto de Arquivos:**
Gerencie arquivos diretamente em servidores remotos com suporte para visualizar e editar codigo, imagens, audio e video. Faca upload, download, renomeie, exclua e mova arquivos facilmente com suporte sudo. Inclui suporte para mover arquivos de servidor para servidor.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Gerenciamento de Docker e Podman:**
Inicie, pare, pause, remova conteineres. Visualize estatisticas de conteineres. Controle conteineres usando o terminal Docker Exec. Suporta Docker e Podman como ambiente de execucao de conteineres. Nao foi feito para substituir Portainer ou Dockge, mas sim para simplesmente gerenciar seus conteineres em vez de cria-los.
</td>
<td width="50%" valign="top">
**Gerenciador de Hosts SSH:**
Salve, organize e gerencie suas conexoes SSH com tags e pastas (com personalizacao de pastas e suporte a pastas aninhadas), e salve facilmente informacoes de login reutilizaveis com a capacidade de automatizar a implantacao de chaves SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Metricas do Host:**
Visualize o uso de CPU, memoria e disco, rede, tempo de atividade, informacoes do sistema, firewall, monitor de portas, visualizador de logs, usuarios/permissoes, certificados e muito mais na maioria dos servidores baseados em Linux. Inclui graficos de historico em serie temporal e alertas baseados em limites com suporte a ntfy e webhook.
</td>
<td width="50%" valign="top">
**Autenticacao de Usuarios:**
Gerenciamento seguro de usuarios com controles de administrador (podem editar informacoes de outros usuarios) e suporte para OIDC/LDAP/SSO (com controle de acesso), 2FA (TOTP) e passkey (WebAuthn). Visualize sessoes ativas de usuarios em todas as plataformas e revogue permissoes. Vincule suas contas OIDC/Locais entre si. Visualize o log de auditoria de todas as acoes dos usuarios.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Integracao com Tailscale:**
Liste dispositivos da sua rede Tailscale para adicioná-los rapidamente como hosts, e conecte-se usando Tailscale SSH como metodo de autenticacao, deixando as ACLs da sua rede gerenciar a autorizacao sem armazenar credenciais.
</td>
<td width="50%" valign="top">
**RBAC/Compartilhamento:**
Crie funcoes e compartilhe hosts entre usuarios/funcoes. Suporta todos os tipos de autenticacao e todos os protocolos de host.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Conexoes Seriais:**
Conecte-se a dispositivos seriais (roteadores, switches, microcontroladores, etc.) diretamente do navegador ou do aplicativo desktop. Configure taxa de baud, bits de dados, bits de parada e paridade. Usa a Web Serial API em navegadores suportados ou um backend nativo no aplicativo Electron.
</td>
<td width="50%" valign="top">
**Alertas:**
Defina regras de alerta baseadas em limites para metricas do host (CPU, memoria, disco, etc.) e receba notificacoes via ntfy ou webhooks quando forem ativadas. Visualize alertas ativos e resolvidos em um historico de registros.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Pagina Inicial:**
Uma pagina inicial totalmente personalizavel com uma grade de widgets de arrastar e soltar. Adicione widgets para status do host, links de servicos, relogios, notas, feeds RSS, clima, conteineres Docker, graficos de metricas do host, terminais incorporados, iframes e mais.
</td>
<td width="50%" valign="top">
**Criptografia de Banco de Dados:**
Backend armazenado como arquivos de banco de dados SQLite criptografados. Consulte a [documentacao](https://docs.termix.site/security) para mais informacoes.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Grafico de Rede:**
Personalize seu Dashboard para visualizar seu homelab baseado nas suas conexoes SSH com suporte de status.
</td>
<td width="50%" valign="top">
**Ferramentas SSH:**
Crie trechos de comandos reutilizaveis que sao executados com um unico clique. Execute um comando simultaneamente em multiplos terminais abertos.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Abas Persistentes:**
Sessoes SSH e abas permanecem abertas entre dispositivos/atualizacoes se habilitado no perfil do usuario.
</td>
<td width="50%" valign="top">
**Idiomas:**
Suporte integrado para aproximadamente 30 idiomas (gerenciado pelo [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Mais funcionalidades</b></summary>
<br />
- **Dashboard** - Visualize informacoes do servidor de relance no seu dashboard
- **Chaves de API** - Crie chaves de API com escopo de usuario e datas de expiracao para uso em automacao/CI
- **Exportacao/Importacao de Dados** - Exporte e importe hosts SSH, credenciais e dados do gerenciador de arquivos
- **Configuracao Automatica de SSL** - Geracao e gerenciamento integrado de certificados SSL com redirecionamentos HTTPS
- **Interface Moderna** - Interface limpa compativel com desktop/mobile construida com React, Tailwind CSS e Shadcn. Escolha entre muitos temas de interface diferentes, incluindo claro, escuro, Dracula, etc. Use rotas de URL para abrir qualquer conexao em tela cheia.
- **Historico de Comandos** - Autocompletar e visualizar comandos SSH executados anteriormente
- **Conexao Rapida** - Conecte-se a um servidor sem precisar salvar os dados de conexao
- **Paleta de Comandos** - Pressione duas vezes a tecla Shift esquerda para acessar rapidamente as conexoes SSH com seu teclado
- **Integracao com Proxmox** - Adicione automaticamente hosts ao Termix a partir da sua instancia Proxmox
- **SSH Rico em Funcionalidades** - Suporta jump hosts, Warpgate, conexoes baseadas em TOTP, SOCKS5, verificacao de chave do host, preenchimento automatico de senhas, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, registro de terminal, encaminhamento de agente SSH, agente SSH do Bitwarden, assinatura SSH do HashiCorp Vault, e mais.
- **Termix ID** - Um equivalente ao sshid.io integrado ao Termix. Reivindique um identificador, publique suas chaves SSH publicas em uma URL de resolucao e use uma CA integrada para emitir certificados SSH.
Visite a [documentacao](https://docs.termix.site/install) do Termix para instrucoes completas de instalacao em todas as plataformas.
Arquivo Docker Compose de exemplo (voce pode omitir o `guacd` e a rede se nao planeja usar recursos de area de trabalho remota):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Doar
Termix e gratuito e de codigo aberto, sem assinaturas ou planos pagos. Se o achar util, considere doar para ajudar a cobrir custos de servidor, dominios e tempo de desenvolvimento. As doacoes tambem ajudam a financiar o tempo de pesquisa e aprendizado necessario para construir funcionalidades como suporte a SAML, Kubernetes e Agent. Acompanhe o progresso e doe abaixo.
[Doar](https://donate.termix.site/)
<br />
## Patrocinadores
Interessado em um espaco pago para apoiar o desenvolvimento? Envie um email para [mail@termix.site](mailto:mail@termix.site).
Se voce precisa de ajuda ou deseja solicitar uma funcionalidade para o Termix, visite a pagina de [Issues](https://github.com/Termix-SSH/Support/issues), faca login e clique em `New Issue`. Por favor, seja o mais detalhado possivel no seu relato, preferencialmente escrito em ingles. Voce tambem pode entrar no servidor do [Discord](https://discord.gg/jVQGdvHDrf) e visitar o canal de suporte, porem, os tempos de resposta podem ser mais longos.
<sub>Alguns videos e imagens podem estar desatualizados ou podem nao mostrar perfeitamente as funcionalidades.</sub>
</div>
<br />
## Funcionalidades Planejadas
Consulte [Projetos](https://github.com/orgs/Termix-SSH/projects/5) para todas as funcionalidades planejadas. Se voce deseja contribuir, consulte [Contribuir](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Licenca
Distribuido sob a Licenca Apache Versao 2.0. Consulte `LICENSE` para mais informacoes.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix — бесплатный проект с открытым исходным кодом. Если он вам полезен, рассмотрите возможность [пожертвования](https://donate.termix.site/) для покрытия расходов на серверы и время разработки.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Достигнуто 1 сентября 2025 года</sub>
</p>
</div>
<br />
## Обзор
Termix - это платформа для управления серверами с открытым исходным кодом, навсегда бесплатная и размещаемая на собственном сервере. Она предоставляет мультиплатформенное решение для управления вашими серверами и инфраструктурой через единый интуитивно понятный интерфейс. Termix предлагает доступ к SSH-терминалу, управление удаленным рабочим столом (RDP, VNC, Telnet), возможности SSH-туннелирования, удаленное управление файлами SSH и множество других инструментов. Termix - это идеальная бесплатная альтернатива Termius с возможностью размещения на собственном сервере, доступная для всех платформ.
<br />
## Возможности
<table>
<tr>
<td width="50%" valign="top">
**Доступ к SSH-терминалу:**
Полнофункциональный терминал с поддержкой разделения экрана (до 4 панелей) и системой вкладок, как в браузере. Включает поддержку настройки терминала, включая популярные темы, шрифты и другие компоненты.
</td>
<td width="50%" valign="top">
**Доступ к удалённому рабочему столу:**
Поддержка RDP, VNC и Telnet через браузер с полной настройкой и разделением экрана.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Управление SSH-туннелями:**
Создание и управление межсерверными SSH-туннелями с автоматическим переподключением, мониторингом состояния и локальной, удалённой или динамической SOCKS-переадресацией. Настройки туннелей «десктопный клиент - сервер» хранятся локально для каждой установки; опциональные снимки C2S-пресетов можно сохранять на сервере, переименовывать, загружать или удалять, когда вы хотите перенести локальную конфигурацию туннеля между клиентами.
</td>
<td width="50%" valign="top">
**Удалённый файловый менеджер:**
Управление файлами непосредственно на удалённых серверах с поддержкой просмотра и редактирования кода, изображений, аудио и видео. Загрузка, скачивание, переименование, удаление и перемещение файлов с поддержкой sudo. Включает поддержку перемещения файлов с сервера на сервер.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Управление Docker и Podman:**
Запуск, остановка, приостановка, удаление контейнеров. Просмотр статистики контейнеров. Управление контейнером через терминал docker exec. Поддерживает как Docker, так и Podman в качестве среды выполнения контейнеров. Не предназначен для замены Portainer или Dockge, а скорее для простого управления контейнерами по сравнению с их созданием.
</td>
<td width="50%" valign="top">
**Менеджер SSH-хостов:**
Сохранение, организация и управление SSH-подключениями с помощью тегов и папок (с настройкой папок и поддержкой вложенных папок), с возможностью сохранения данных для повторного входа и автоматизации развёртывания SSH-ключей.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Метрики хоста:**
Просмотр использования CPU, памяти и диска, сети, времени работы, информации о системе, файрвола, монитора портов, просмотрщика логов, пользователей/прав доступа, сертификатов и многого другого на большинстве серверов на базе Linux. Включает графики истории временных рядов и оповещения на основе пороговых значений с поддержкой ntfy и вебхуков.
</td>
<td width="50%" valign="top">
**Аутентификация пользователей:**
Безопасное управление пользователями с административным контролем (может редактировать информацию других пользователей) и поддержкой OIDC/LDAP/SSO (с контролем доступа), 2FA (TOTP) и поддержкой ключей доступа (WebAuthn). Просмотр активных сессий пользователей на всех платформах и отзыв прав доступа. Связывание аккаунтов OIDC/локальных аккаунтов. Просмотр журнала аудита действий всех пользователей.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Интеграция с Tailscale:**
Список устройств вашей сети Tailscale для быстрого добавления их в качестве хостов и подключение через Tailscale SSH в качестве метода аутентификации, позволяя ACL вашей сети управлять авторизацией без хранения учётных данных.
</td>
<td width="50%" valign="top">
**RBAC/Общий доступ:**
Создание ролей и предоставление общего доступа к хостам для пользователей/ролей. Поддерживает все типы аутентификации и все протоколы хостов.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Последовательные подключения:**
Подключение к последовательным устройствам (маршрутизаторы, коммутаторы, микроконтроллеры и т. д.) напрямую из браузера или приложения для рабочего стола. Настройка скорости передачи данных, битов данных, стоп-битов и чётности. Использует Web Serial API в поддерживаемых браузерах или нативный бэкенд в приложении Electron.
</td>
<td width="50%" valign="top">
**Оповещения:**
Настройте правила оповещений на основе пороговых значений для метрик хоста (CPU, память, диск и т. д.) и получайте уведомления через ntfy или вебхуки при их срабатывании. Просматривайте активные и разрешённые оповещения в журнале истории.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Домашняя страница:**
Полностью настраиваемая домашняя страница с сеткой виджетов с перетаскиванием. Добавляйте виджеты для статуса хоста, ссылок на сервисы, часов, заметок, RSS-лент, погоды, контейнеров Docker, графиков метрик хоста, встроенных терминалов, iframe и многого другого.
</td>
<td width="50%" valign="top">
**Шифрование базы данных:**
Бэкенд хранится в виде зашифрованных файлов базы данных SQLite. Подробнее в [документации](https://docs.termix.site/security).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Сетевой граф:**
Настройте панель управления для визуализации вашей домашней лаборатории на основе SSH-подключений с поддержкой статусов.
</td>
<td width="50%" valign="top">
**Инструменты SSH:**
Создание переиспользуемых фрагментов команд, выполняемых одним нажатием. Запуск одной команды одновременно в нескольких открытых терминалах.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Постоянные вкладки:**
SSH-сессии и вкладки остаются открытыми на всех устройствах/при обновлении страницы, если включено в профиле пользователя.
</td>
<td width="50%" valign="top">
**Языки:**
Встроенная поддержка около 30 языков (управляется через [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Больше возможностей</b></summary>
<br />
- **Панель управления** - Просмотр информации о сервере на панели управления одним взглядом
- **API-ключи** - Создание API-ключей с областью видимости пользователя и сроками действия для использования в автоматизации/CI
- **Экспорт/импорт данных** - Экспорт и импорт SSH-хостов, учётных данных и данных файлового менеджера
- **Автоматическая настройка SSL** - Встроенная генерация и управление SSL-сертификатами с перенаправлением на HTTPS
- **Современный интерфейс** - Чистый интерфейс для десктопа и мобильных устройств, построенный на React, Tailwind CSS и Shadcn. Выбор между множеством различных тем интерфейса, включая светлую, тёмную, Dracula и т. д. Использование URL-маршрутов для открытия любого подключения в полноэкранном режиме.
- **История команд** - Автодополнение и просмотр ранее выполненных SSH-команд
- **Быстрое подключение** - Подключение к серверу без необходимости сохранения данных подключения
- **Командная палитра** - Двойное нажатие левого Shift для быстрого доступа к SSH-подключениям с клавиатуры
- **Интеграция с Proxmox** - Автоматическое добавление хостов в Termix из вашего экземпляра Proxmox
- **Богатый функционал SSH** - Поддержка jump-хостов, Warpgate, подключений на основе TOTP, SOCKS5, верификации ключей хоста, автозаполнения паролей, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, логирования терминала, переадресации SSH-агента, SSH-агента Bitwarden, подписи SSH через HashiCorp Vault и многого другого.
- **Termix ID** - Аналог sshid.io, встроенный в Termix. Зарегистрируйте имя пользователя, опубликуйте свои публичные SSH-ключи по URL резолвера и используйте встроенный ЦС для выдачи SSH-сертификатов.
</details>
<br />
## Поддержка платформ
<table align="center">
<tr>
<th align="center">Платформа</th>
<th align="center">Дистрибутив</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Любой современный браузер (Chrome, Safari, Firefox) · Поддержка PWA</td>
Посетите [документацию](https://docs.termix.site/install) Termix для получения полных инструкций по установке на всех платформах.
Пример файла Docker Compose (вы можете опустить `guacd` и сеть, если не планируете использовать функции удаленного рабочего стола):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Пожертвование
Termix бесплатен и имеет открытый исходный код, без подписок или платных тарифов. Если он вам полезен, рассмотрите возможность пожертвования, чтобы помочь покрыть расходы на серверы, домены и время разработки. Пожертвования также помогают финансировать время на исследование и изучение того, что необходимо для создания таких функций, как поддержка SAML, Kubernetes и Agent. Отслеживайте прогресс и делайте пожертвования ниже.
[Пожертвовать](https://donate.termix.site/)
<br />
## Спонсоры
Заинтересованы в платном размещении для поддержки разработки? Напишите на [mail@termix.site](mailto:mail@termix.site).
Если вам нужна помощь или вы хотите запросить новую функцию для Termix, посетите страницу [Проблемы](https://github.com/Termix-SSH/Support/issues), войдите в систему и нажмите `New Issue`. Пожалуйста, опишите вашу проблему как можно подробнее, предпочтительно на английском языке. Вы также можете присоединиться к серверу [Discord](https://discord.gg/jVQGdvHDrf) и обратиться в канал поддержки, однако время ответа может быть дольше.
<sub>Некоторые видео и изображения могут быть устаревшими или не полностью отражать функциональность.</sub>
</div>
<br />
## Запланированные функции
Смотрите [Проекты](https://github.com/orgs/Termix-SSH/projects/5) для просмотра всех запланированных функций. Если вы хотите внести вклад, смотрите [Участие в разработке](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Лицензия
Распространяется по лицензии Apache License Version 2.0. Подробнее см. в файле `LICENSE`.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix ücretsiz ve açık kaynaklıdır. Faydalı buluyorsanız, sunucu maliyetleri ve geliştirme süresine katkıda bulunmak için [bağış yapmayı](https://donate.termix.site/) düşünebilirsiniz.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>1 Eylül 2025'te kazanildi</sub>
</p>
</div>
<br />
## Genel Bakis
Termix, acik kaynakli, sonsuza kadar ucretsiz, kendi sunucunuzda barindirabileceginez hepsi bir arada sunucu yonetim platformudur. Sunucularinizi ve altyapinizi tek bir sezgisel arayuz uzerinden yonetmek icin cok platformlu bir cozum sunar. Termix, SSH terminal erisimi, uzak masaustu kontrolu (RDP, VNC, Telnet), SSH tunelleme yetenekleri, uzak dosya yonetimi ve daha bircok arac saglar. Termix, tum platformlarda kullanilabilen Termius'un mukemmel ucretsiz ve kendi barindirmali alternatifidir.
<br />
## Ozellikler
<table>
<tr>
<td width="50%" valign="top">
**SSH Terminal Erisimi:**
Tarayici benzeri sekme sistemiyle bolunmus ekran destegine sahip (4 panele kadar) tam ozellikli terminal. Yaygin terminal temalari, yazi tipleri ve diger bilesenleri iceren terminal ozellestirme destegi.
</td>
<td width="50%" valign="top">
**Uzak Masaustu Erisimi:**
Tam ozellestirme ve bolunmus ekran ile tarayici uzerinden RDP, VNC ve Telnet destegi.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**SSH Tunel Yonetimi:**
Otomatik yeniden baglanti, saglik izleme ve yerel, uzak veya dinamik SOCKS yonlendirme ile sunucular arasi SSH tunelleri olusturun ve yonetin. Masaustu istemci-sunucu tunel ayarlari her masaustu kurulumu icin yerel olarak depolanir; istege bagli C2S hazir ayar anlik goruntuleri, yerel bir tunel yapilandirmasini istemciler arasinda tasimak istediginizde sunucuya kaydedilebilir, yeniden adlandirilabilir, yuklenebilir veya silinebilir.
</td>
<td width="50%" valign="top">
**Uzak Dosya Yoneticisi:**
Uzak sunuculardaki dosyalari dogrudan yonetin; kod, goruntu, ses ve video goruntuleme ve duzenleme destegi ile. Sudo destegi ile dosyalari sorunsuzca yukleyin, indirin, yeniden adlandirin, silin ve tasiyin. Dosyalari sunucudan sunucuya tasima destegini de icerir.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Docker ve Podman Yonetimi:**
Konteynerleri baslatın, durdurun, duraklatın, kaldirin. Konteyner istatistiklerini goruntuleyin. Docker exec terminali kullanarak konteyneri kontrol edin. Docker ve Podman'i konteyner calisma ortami olarak destekler. Portainer veya Dockge'nin yerini almak icin degil, konteynerlerinizi olusturmak yerine basitce yonetmek icin tasarlanmistir.
</td>
<td width="50%" valign="top">
**SSH Ana Bilgisayar Yoneticisi:**
SSH baglantilarinizi etiketler ve klasorlerle (klasor ozellestirme ve ic ice klasor destegi ile) kaydedin, duzenleyin ve yonetin; yeniden kullanilabilir giris bilgilerini kolayca kaydedin ve SSH anahtarlarinin dagitimini otomatiklestirin.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Ana Bilgisayar Metrikleri:**
Cogu Linux tabanli sunucularda calisan CPU, bellek, disk kullanimi, ag, calisma suresi, sistem bilgisi, guvenlik duvari, port izleme, gunluk goruntuleyici, kullanicilar/izinler, sertifikalar ve daha fazlasini goruntuleyin. Zaman serisi gecmis grafiklerini ve ntfy ile webhook destekli esik tabanli uyarilari icerir.
</td>
<td width="50%" valign="top">
**Kullanici Kimlik Dogrulama:**
Yonetici kontrolleri (diger kullanicilarin bilgilerini duzenleyebilir), OIDC/LDAP/SSO (erisim kontrollu), 2FA (TOTP) ve passkey (WebAuthn) destegi ile guvenli kullanici yonetimi. Tum platformlardaki aktif kullanici oturumlarini goruntuleyin ve izinleri iptal edin. OIDC/Yerel hesaplarinizi birbirine baglayin. Tum kullanicilarin islemlerinin denetim gunlugunu goruntuleyin.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscale Entegrasyonu:**
Tailscale aginizdaki cihazlari listeleyerek hizlica ana bilgisayar olarak ekleyin ve kimlik dogrulama yontemi olarak Tailscale SSH kullanarak baglanin; bu sayede ag ACL'leriniz kimlik bilgileri depolamadan yetkilendirmeyi yonetir.
</td>
<td width="50%" valign="top">
**RBAC/Paylasim:**
Roller olusturun ve ana bilgisayarlari kullanicilar/roller arasinda paylasin. Tum kimlik dogrulama turlerini ve tum ana bilgisayar protokollerini destekler.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Seri Baglantilar:**
Seri cihazlara (router, switch, mikrodenetleyici vb.) dogrudan tarayici veya masaustu uygulamasindan baglanin. Baud hizi, veri bitleri, durdurma bitleri ve parite yapilandirin. Desteklenen tarayicilarda Web Serial API, Electron uygulamasinda yerel arka ucu kullanir.
</td>
<td width="50%" valign="top">
**Uyarilar:**
Ana bilgisayar metrikleri (CPU, bellek, disk vb.) icin esik tabanli uyari kurallari belirleyin ve tetiklendiklerinde ntfy veya webhook araciligiyla bildirim alin. Gecmis gunlugunde tetiklenen ve cozulen uyarilari goruntuleyin.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Ana Sayfa:**
Surukleme ve birakma widget izgarasina sahip tamamen ozellestirilebilir bir ana sayfa. Ana bilgisayar durumu, hizmet baglantilari, saatler, notlar, RSS besleme, hava durumu, Docker konteynerleri, ana bilgisayar metrik grafikleri, gomulu terminaller, iframe ve daha fazlasi icin widget ekleyin.
</td>
<td width="50%" valign="top">
**Veritabani Sifreleme:**
Arka uc, sifrelenmis SQLite veritabani dosyalari olarak depolanir. Daha fazla bilgi icin [belgelere](https://docs.termix.site/security) bakin.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Ag Grafigi:**
Kontrol panelinizi, SSH baglantilariniza dayali olarak ev laboratuvarinizi durum destegi ile gorselletirmek icin ozellestirin.
</td>
<td width="50%" valign="top">
**SSH Araclari:**
Tek tiklamayla calistirilan yeniden kullanilabilir komut parcaciklari olusturun. Birden fazla acik terminalde ayni anda tek bir komut calistirin.
- **Modern Arayuz** - React, Tailwind CSS ve Shadcn ile olusturulmus temiz masaustu/mobil uyumlu arayuz. Isik, karanlik, Dracula vb. dahil olmak uzere bircok farkli UI temasi arasından secim yapin. Herhangi bir baglantıyı tam ekranda acmak icin URL yollarini kullanin.
- **Komut Gecmisi** - Daha once calistirilan SSH komutlarini otomatik tamamlayin ve goruntuleyin
- **Hizli Baglanti** - Baglanti verilerini kaydetmeden bir sunucuya baglanin
- **Komut Paleti** - Sol shift tusuna iki kez basarak SSH baglantilariniza klavyenizle hizlica erisin
- **Proxmox Entegrasyonu** - Proxmox ornekinizden Termix'e otomatik olarak ana bilgisayar ekleyin
- **SSH Zengin Ozellikler** - Atlama ana bilgisayarlari, Warpgate, TOTP tabanli baglantilar, SOCKS5, ana bilgisayar anahtar dogrulama, otomatik sifre doldurma, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, terminal gunlukleme, SSH agent forwarding, Bitwarden SSH agent, HashiCorp Vault SSH imzalama ve dahasini destekler.
- **Termix ID** - Termix'e entegre edilmis bir sshid.io esdegeri. Bir kullanici adi edinin, genel SSH anahtarlarinizi bir cozumleyici URL'sinde yayinlayin ve SSH sertifikalari vermek icin yerlesik bir CA kullanin.
</details>
<br />
## Platform Destegi
<table align="center">
<tr>
<th align="center">Platform</th>
<th align="center">Dagitim</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Herhangi bir modern tarayici (Chrome, Safari, Firefox) · PWA destegi</td>
Termix'i tum platformlara nasil kuracaginiz hakkinda daha fazla bilgi icin Termix [Belgelerine](https://docs.termix.site/install) bakin.
Ornek bir Docker Compose dosyasi (uzak masaustu ozelliklerini kullanmayi planlamiyorsaniz `guacd` ve agi cikarabilirsiniz):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Bağış Yapın
Termix ücretsiz ve açık kaynaklıdır, abonelik veya ücretli plan yoktur. Faydalı buluyorsaniz, sunucu maliyetleri, alan adlari ve gelistirme suresine katkida bulunmak icin bagis yapmayi dusunebilirsiniz. Bagislar ayrica SAML, Kubernetes ve Agent destegi gibi ozellikleri gelistirmek icin gereken arastirma ve ogrenme suresini finanse etmeye yardimci olur. Ilerlemeyi takip edin ve asagidan bagis yapin.
[Bağış Yapın](https://donate.termix.site/)
<br />
## Sponsorlar
Gelistirmeyi desteklemek icin ucretli bir yerlesim ile ilgileniyor musunuz? [mail@termix.site](mailto:mail@termix.site) adresine e-posta gonderin.
Termix ile ilgili yardima ihtiyaciniz varsa veya bir ozellik talep etmek istiyorsaniz, [Sorunlar](https://github.com/Termix-SSH/Support/issues) sayfasini ziyaret edin, giris yapin ve `New Issue` butonuna basin. Lutfen sorununuzu mumkun oldugunca ayrintili yazin, tercihen Ingilizce olarak. Ayrica [Discord](https://discord.gg/jVQGdvHDrf) sunucusuna katilabilir ve destek kanalini ziyaret edebilirsiniz, ancak yanit sureleri daha uzun olabilir.
<a href="https://donate.termix.site/"><img alt="Donations this month" src="https://img.shields.io/badge/dynamic/json?style=for-the-badge&label=Donations%20this%20month&query=%24.fiatTotal&prefix=%24&url=https%3A%2F%2Ftermix.site%2Fdonation-snapshot.json&color=F39044&labelColor=1a1a1a" /></a>
</p>
<br />
Termix là dự án miễn phí và mã nguồn mở. Nếu bạn thấy hữu ích, hãy cân nhắc [quyên góp](https://donate.termix.site/) để giúp trang trải chi phí máy chủ và thời gian phát triển.
<img src="../repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Dat duoc vao ngay 1 thang 9 nam 2025</sub>
</p>
</div>
<br />
## Tong Quan
Termix la nen tang quan ly may chu tat ca trong mot, ma nguon mo, mien phi vinh vien, tu luu tru. No cung cap giai phap da nen tang de quan ly may chu va co so ha tang cua ban thong qua mot giao dien truc quan duy nhat. Termix cung cap quyen truy cap terminal SSH, dieu khien may tinh tu xa (RDP, VNC, Telnet), kha nang tao duong ham SSH, quan ly tep tu xa va nhieu cong cu khac. Termix la giai phap thay the mien phi va tu luu tru hoan hao cho Termius, kha dung tren tat ca cac nen tang.
<br />
## Tinh Nang
<table>
<tr>
<td width="50%" valign="top">
**Truy Cap Terminal SSH:**
Terminal day du tinh nang voi ho tro chia man hinh (len den 4 bang) voi he thong tab kieu trinh duyet. Bao gom ho tro tuy chinh terminal bao gom cac chu de terminal pho bien, phong chu va cac thanh phan khac.
</td>
<td width="50%" valign="top">
**Truy Cap Man Hinh Tu Xa:**
Ho tro RDP, VNC va Telnet qua trinh duyet voi day du tuy chinh va chia man hinh.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Quan Ly Duong Ham SSH:**
Tao va quan ly duong ham SSH giua cac may chu voi tu dong ket noi lai, giam sat suc khoe va chuyen tiep cuc bo, tu xa hoac SOCKS dong. Cai dat duong ham tu may khach desktop den may chu duoc luu tru cuc bo cho moi ban cai dat desktop; cac snapshot C2S preset tuy chon co the duoc luu tren may chu, doi ten, tai hoac xoa khi ban muon di chuyen mot cau hinh duong ham cuc bo giua cac may khach.
</td>
<td width="50%" valign="top">
**Trinh Quan Ly Tep Tu Xa:**
Quan ly tep truc tiep tren may chu tu xa voi ho tro xem va chinh sua ma, hinh anh, am thanh va video. Tai len, tai xuong, doi ten, xoa va di chuyen tep lien mach voi ho tro sudo. Bao gom ho tro di chuyen tep tu may chu nay sang may chu khac.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Quan Ly Docker va Podman:**
Khoi dong, dung, tam dung, xoa container. Xem thong ke container. Dieu khien container bang terminal docker exec. Ho tro ca Docker va Podman lam moi truong chay container. Khong duoc tao ra de thay the Portainer hay Dockge ma don gian la de quan ly container cua ban thay vi tao moi chung.
</td>
<td width="50%" valign="top">
**Trinh Quan Ly May Chu SSH:**
Luu, sap xep va quan ly cac ket noi SSH cua ban voi the va thu muc (ho tro tuy chinh thu muc va thu muc long nhau), de dang luu thong tin dang nhap co the tai su dung dong thoi co the tu dong hoa viec trien khai khoa SSH.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Chi So May Chu:**
Xem muc su dung CPU, bo nho, o dia, mang, thoi gian hoat dong, thong tin he thong, tuong lua, giam sat cong, trinh xem nhat ky, nguoi dung/quyen, chung chi va nhieu hon nua tren hau het cac may chu chay Linux. Bao gom bieu do lich su theo chuoi thoi gian va canh bao dua tren nguong voi ho tro ntfy va webhook.
</td>
<td width="50%" valign="top">
**Xac Thuc Nguoi Dung:**
Quan ly nguoi dung an toan voi quyen quan tri (co the chinh sua thong tin cua nguoi dung khac) va ho tro OIDC/LDAP/SSO (co kiem soat truy cap), 2FA (TOTP) va passkey (WebAuthn). Xem phien hoat dong cua nguoi dung tren tat ca cac nen tang va thu hoi quyen. Lien ket tai khoan OIDC/Noi bo cua ban voi nhau. Xem nhat ky kiem toan cac hanh dong cua tat ca nguoi dung.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tich Hop Tailscale:**
Liet ke cac thiet bi trong mang Tailscale de nhanh chong them vao lam may chu, va ket noi bang Tailscale SSH lam phuong thuc xac thuc, de cac ACL mang xu ly uy quyen ma khong can luu tru thong tin xac thuc.
</td>
<td width="50%" valign="top">
**RBAC/Chia Se:**
Tao vai tro va chia se may chu giua nguoi dung/vai tro. Ho tro tat ca cac loai xac thuc va tat ca cac giao thuc may chu.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Ket Noi Noi Tiep:**
Ket noi voi cac thiet bi noi tiep (router, switch, vi dieu khien, v.v.) truc tiep tu trinh duyet hoac ung dung may tinh. Cau hinh toc do baud, bit du lieu, bit dung va chan le. Su dung Web Serial API tren trinh duyet duoc ho tro hoac backend ban dia trong ung dung Electron.
</td>
<td width="50%" valign="top">
**Canh Bao:**
Dat cac quy tac canh bao dua tren nguong cho chi so may chu (CPU, bo nho, o dia, v.v.) va nhan thong bao qua ntfy hoac webhook khi chung kich hoat. Xem canh bao dang kich hoat va da giai quyet trong nhat ky lich su.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Trang Chu:**
Trang chu co the tuy chinh hoan toan voi luoi widget keo va tha. Them widget cho trang thai may chu, lien ket dich vu, dong ho, ghi chu, feed RSS, thoi tiet, container Docker, bieu do chi so may chu, terminal nhung, iframe va nhieu hon nua.
</td>
<td width="50%" valign="top">
**Ma Hoa Co So Du Lieu:**
Backend duoc luu tru duoi dang tep co so du lieu SQLite duoc ma hoa. Xem [tai lieu](https://docs.termix.site/security) de biet them.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Bieu Do Mang:**
Tuy chinh Bang Dieu Khien de truc quan hoa homelab cua ban dua tren cac ket noi SSH voi ho tro trang thai.
</td>
<td width="50%" valign="top">
**Cong Cu SSH:**
Tao doan lenh co the tai su dung, thuc thi chi voi mot cu nhap chuot. Chay mot lenh dong thoi tren nhieu terminal dang mo.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tab Lien Tuc:**
Cac phien SSH va tab van mo tren cac thiet bi/lan lam moi neu duoc bat trong ho so nguoi dung.
</td>
<td width="50%" valign="top">
**Ngon Ngu:**
Ho tro tich hop khoang 30 ngon ngu (duoc quan ly boi [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
<details>
<summary><b>Them tinh nang</b></summary>
<br />
- **Bang Dieu Khien** - Xem thong tin may chu trong nháy mat tren bang dieu khien cua ban
- **Khoa API** - Tao khoa API theo pham vi nguoi dung voi ngay het han de su dung cho tu dong hoa/CI
- **Xuat/Nhap Du Lieu** - Xuat va nhap may chu SSH, thong tin xac thuc va du lieu trinh quan ly tep
- **Thiet Lap SSL Tu Dong** - Tao va quan ly chung chi SSL tich hop voi chuyen huong HTTPS
- **Giao Dien Hien Dai** - Giao dien sach se, than thien voi may tinh/di dong duoc xay dung bang React, Tailwind CSS va Shadcn. Chon giua nhieu chu de UI khac nhau bao gom sang, toi, Dracula, v.v. Su dung duong dan URL de mo bat ky ket noi nao o che do toan man hinh.
- **Lich Su Lenh** - Tu dong hoan thanh va xem cac lenh SSH da chay truoc do
- **Ket Noi Nhanh** - Ket noi den may chu ma khong can luu du lieu ket noi
- **Bang Lenh** - Nhan dup phim shift trai de truy cap nhanh cac ket noi SSH bang ban phim
- **Tich Hop Proxmox** - Tu dong them may chu vao Termix tu instance Proxmox cua ban
- **SSH Giau Tinh Nang** - Ho tro jump host, Warpgate, ket noi dua tren TOTP, SOCKS5, xac minh khoa may chu, tu dong dien mat khau, [OPKSSH](https://github.com/openpubkey/opkssh), tmux, port knocking, ghi nhat ky terminal, chuyen tiep SSH agent, Bitwarden SSH agent, ky SSH bang HashiCorp Vault va nhieu hon nua.
- **Termix ID** - Mot tuong duong cua sshid.io duoc tich hop san trong Termix. Dang ky mot ten dinh danh, cong bo khoa SSH cong khai cua ban tai mot URL phan giai va su dung CA tich hop san de cap chung chi SSH.
</details>
<br />
## Ho Tro Nen Tang
<table align="center">
<tr>
<th align="center">Nen tang</th>
<th align="center">Phan phoi</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Bat ky trinh duyet hien dai nao (Chrome, Safari, Firefox) · Ho tro PWA</td>
Truy cap [Tai Lieu](https://docs.termix.site/install) Termix de biet them thong tin ve cach cai dat Termix tren tat ca cac nen tang.
Tep Docker Compose mau (ban co the bo qua `guacd` va mang neu khong co y dinh su dung cac tinh nang dieu khien may tinh tu xa):
```yaml
services:
termix:
image:ghcr.io/lukegus/termix:latest
container_name:termix
restart:unless-stopped
ports:
- "8080:8080"
volumes:
- termix-data:/app/data
environment:
PORT:"8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image:guacamole/guacd:1.6.0
container_name:guacd
restart:unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver:local
networks:
termix-net:
driver:bridge
```
<br />
## Quyên góp
Termix là dự án miễn phí và mã nguồn mở, không có gói đăng ký hay trả phí. Nếu bạn thấy hữu ích, hãy cân nhắc quyên góp để giúp trang trải chi phí máy chủ, tên miền và thời gian phát triển. Các khoản quyên góp cũng giúp tài trợ thời gian nghiên cứu và tìm hiểu những gì cần thiết để xây dựng các tính năng như SAML, Kubernetes và hỗ trợ Agent. Theo dõi tiến độ và quyên góp bên dưới.
[Quyên góp](https://donate.termix.site/)
<br />
## Nha Tai Tro
Ban quan tam den viec dat quang cao tra phi de ho tro phat trien? Gui email toi [mail@termix.site](mailto:mail@termix.site).
Neu ban can tro giup hoac muon yeu cau tinh nang voi Termix, hay truy cap trang [Van De](https://github.com/Termix-SSH/Support/issues), dang nhap va nhan `New Issue`. Vui long mo ta van de cang chi tiet cang tot, uu tien viet bang tieng Anh. Ban cung co the tham gia may chu [Discord](https://discord.gg/jVQGdvHDrf) va truy cap kenh ho tro, tuy nhien thoi gian phan hoi co the lau hon.
<sub>Mot so video va hinh anh co the da loi thoi hoac khong the hien chinh xac hoan toan cac tinh nang.</sub>
</div>
<br />
## Tinh Nang Du Kien
Xem [Du An](https://github.com/orgs/Termix-SSH/projects/5) de biet tat ca cac tinh nang du kien. Neu ban muon dong gop, xem [Dong Gop](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
## Giay Phep
Duoc phan phoi theo Giay Phep Apache Phien Ban 2.0. Xem `LICENSE` de biet them thong tin.
Loaded 100 of 2276 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.