Compare commits

...
Author SHA1 Message Date
+3 a64c956c5b release-2.6.1 (#1161)
* 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.

Fixes Termix-SSH/Support#1016
Fixes Termix-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.

Closes Termix-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.

Closes Termix-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.

Closes Termix-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.

Closes Termix-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.

Closes Termix-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.

Closes Termix-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.

Closes Termix-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.

Fixes Termix-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.

Fixes Termix-SSH/Support#1050
Fixes Termix-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.

Closes Termix-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>
2026-08-06 14:41:39 -05:00
Luke Gustafson 188380e8e7 Update README.md 2026-08-01 00:20:07 -05:00
Luke Gustafson 9effddaba8 Update README to remove Tailscale and add Ginernet
Removed Tailscale logo and link from the README. Added Ginernet logo and link.
2026-08-01 00:16:11 -05:00
Luke Gustafson ad0e62434b Fix formatting issue in package-lock.json 2026-07-29 16:06:15 -05:00
Luke Gustafson d831b46cb3 Bump version from 2.6.0 to 2.6.1 in package-lock.json 2026-07-29 16:06:03 -05:00
LukeGus 4a7117b67f fix: macOS appstore submission encryption fix 2026-07-26 20:29:38 -05:00
LukeGus 9ae48a8c6c fix: macOS appstore IAP error 2026-07-26 20:05:38 -05:00
LukeGus e5b0db60c4 fix: macOS appstore versioning error 2026-07-26 19:54:12 -05:00
LukeGus 0327f9020c fix: macOS appstore upload failure 2026-07-26 19:42:11 -05:00
LukeGus 9f066d814a chore: bump Homebrew cask to 2.6.0 2026-07-27 00:25:42 +00:00
+2
Luke GustafsonXtraLarge <>dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>L.H.default-studentBrad Bakercopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Copilot Autofix powered by AIBrennan NeohbrennanneohXtraLargeZacharyZcR
1a26628a48 release-2.6.0 (#1085)
* fix: general bug fixes

* fix: general qol additions

* ci(deps): bump actions/setup-node in the github-actions group (#1068)

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


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)

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

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

* chore(deps-dev): bump the dev-patch-updates group with 28 updates (#1069)

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

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.2` | `2.5.4` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.5` | `6.43.6` |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.15` | `1.2.17` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.6` | `1.3.8` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.19` | `2.1.21` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.11` | `2.1.12` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.18` | `1.1.20` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.11` | `1.1.13` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.13` | `1.2.15` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.2` | `2.3.4` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.11` | `1.1.12` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.2` | `1.4.4` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.2` | `1.3.4` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.16` | `1.1.18` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.11` | `1.2.13` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.2` | `4.3.3` |
| [@uiw/codemirror-extensions-langs](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@uiw/codemirror-theme-github](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror) | `4.25.10` | `4.25.11` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.1.9` | `4.1.10` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `4.1.9` | `4.1.10` |
| [i18next](https://github.com/i18next/i18next) | `26.3.4` | `26.3.6` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.6.1` | `1.6.3` |
| [react-i18next](https://github.com/i18next/react-i18next) | `17.0.8` | `17.0.10` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.2` | `4.3.3` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.9` | `4.1.10` |


Updates `@biomejs/biome` from 2.5.2 to 2.5.4
- [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.4/packages/@biomejs/biome)

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

Updates `@radix-ui/react-accordion` from 1.2.15 to 1.2.17
- [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.18 to 1.1.20
- [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.6 to 1.3.8
- [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.18 to 1.1.20
- [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.19 to 2.1.21
- [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.11 to 2.1.12
- [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.18 to 1.1.20
- [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.11 to 1.1.13
- [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.13 to 1.2.15
- [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.2 to 2.3.4
- [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.11 to 1.1.12
- [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.2 to 1.4.4
- [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-switch` from 1.3.2 to 1.3.4
- [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.16 to 1.1.18
- [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.11 to 1.2.13
- [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 `@tailwindcss/vite` from 4.3.2 to 4.3.3
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite)

Updates `@uiw/codemirror-extensions-langs` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@uiw/codemirror-theme-github` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@uiw/react-codemirror` from 4.25.10 to 4.25.11
- [Release notes](https://github.com/uiwjs/react-codemirror/releases)
- [Commits](https://github.com/uiwjs/react-codemirror/compare/v4.25.10...v4.25.11)

Updates `@vitest/coverage-v8` from 4.1.9 to 4.1.10
- [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.10/packages/coverage-v8)

Updates `@vitest/ui` from 4.1.9 to 4.1.10
- [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.10/packages/ui)

Updates `i18next` from 26.3.4 to 26.3.6
- [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.4...v26.3.6)

Updates `radix-ui` from 1.6.1 to 1.6.3
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

Updates `react-i18next` from 17.0.8 to 17.0.10
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v17.0.8...v17.0.10)

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

Updates `vitest` from 4.1.9 to 4.1.10
- [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.10/packages/vitest)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.6
  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.17
  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.20
  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.8
  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.20
  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.21
  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.12
  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.20
  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.13
  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.15
  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.4
  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.12
  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.4
  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.4
  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.18
  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.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/codemirror-extensions-langs"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/codemirror-theme-github"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@uiw/react-codemirror"
  dependency-version: 4.25.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitest/ui"
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: i18next
  dependency-version: 26.3.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: radix-ui
  dependency-version: 1.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react-i18next
  dependency-version: 17.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: vitest
  dependency-version: 4.1.10
  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 ws in the prod-patch-updates group (#1071)

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


Updates `ws` from 8.21.0 to 8.21.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.21.0...8.21.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 8.21.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 the major-updates group with 2 updates (#1072)

Bumps the major-updates group with 2 updates: [nanoid](https://github.com/ai/nanoid) and [typescript](https://github.com/microsoft/TypeScript).


Updates `nanoid` from 5.1.16 to 6.0.0
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/5.1.16...6.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: nanoid
  dependency-version: 6.0.0
  dependency-type: direct:production
  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>

* fix: general qol additions and new analytics/telemetrics feature

* fix: incorrect version sent to posthog

* feat: add multiplayer/shared sessions for terminal and guacd

* feat: rework Electron desktop app to run standalone-first with optional two-way sync to a remote Termix server

* Fix Guacamole tab visibility lifecycle (#1074)

Co-authored-by: default-student <default-student@github.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>

* fix(alerts): send channel config as object payload instead of JSON string (#1075)

* fix tmux-monitor tailscale issue (#1076)

* Initial plan

* fix(tmux-monitor): explicitly handle tailscale auth in PanePreview hostConfig

For Tailscale-auth hosts the pane-preview attach path was building the
Terminal hostConfig with only the generic spread of host fields.  This
could omit or mismap auth-critical details and trigger a plain TCP/SSH
reachability path that doesn't work with Tailscale-only SSH endpoints.

The fix branches on `host.authType === "tailscale"` and:
- Carries `authType: "tailscale"` explicitly so the backend always selects
  the Tailscale-aware PTY path regardless of how the host object evolves.
- Derives `port` from `host.sshPort ?? host.port` so Tailscale SSH
  endpoints on a non-default SSH port are reached correctly.
- Leaves all non-tailscale auth types on the unchanged code path.

Reattach (bumping instanceId + attachNonce) continues to work because
terminalHostConfig is recomputed on every render with the latest
instanceIdRef.current value.

* refactor(tmux-monitor): simplify tailscale port logic with extracted variable

Address code review feedback: extract resolvedPort into a local variable
to avoid the duplicated `host.sshPort ?? host.port` expression that was
assigned to both `port` and `sshPort`.  Restructure as an if/else block
instead of an IIFE for readability.

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* chore: run prettier

* chore: update beta release text

* fix: cant update credential of an RDP host

* feat: add custom key shortcuts

* feat: add support for MFA over SSH

* fix: Invalid websocket frame causing code 10006 crash triggering restart loop

* fix(net): correct SSRF blocklist false-positive blocking all outbound IPv4 (#1079)

* fix: correct IPv4-mapped-IPv6 blocklist entry blocking all outbound IPv4

::ffff:0:0/96 in the IPv6 blocklist matches every IPv4 address once
mapped, since Node's BlockList compares addresses in their mapped form
internally regardless of the declared family. This caused
safeOutboundFetch to reject all IPv4-resolved destinations as private,
breaking outbound requests (e.g. ntfy/webhook notifications) whenever
DNS resolved to IPv4. Replaced with individual mapped ranges mirroring
the existing IPv4 blocklist.

* test: cover isBlockedAddress and link the Node BlockList citation

Exports isBlockedAddress so its family-crossing behavior around
IPv4-mapped-IPv6 addresses can actually be asserted, instead of relying
on manual container debugging to notice a regression. Also swaps the
prior "Node's BlockList compares addresses in mapped form" comment for
one citing the documented example in the Node docs
(https://nodejs.org/api/net.html#class-netblocklist), since that
behavior isn't otherwise obvious from the addSubnet/check call sites.

Related: Termix-SSH/Support#1024

* refactor: derive IPv6 mirror from IPv4 list, split DNS error messages

Two follow-ups from review:

- The IPv6 blocklist previously hand-duplicated each IPv4 range as its
  IPv4-mapped-IPv6 equivalent. Nothing enforced the two stayed in sync,
  which is exactly how the original bug (a mismatched ::ffff:0:0/96
  entry blocking all IPv4) was introduced in the first place. Now
  derived from a single blockedIpv4Ranges list in one loop.

- The connect.lookup hook threw the same "Private destinations are not
  allowed" for both an empty DNS result and an actually-blocked address.
  An empty result is a resolution failure, not a privacy decision, and
  conflating the two is the same kind of opaque-error problem that made
  this bug slow to diagnose in production. Split into distinct messages.

Also extracted the lookup hook itself (createDnsLookupHook) so it can be
unit-tested against a fake resolver directly, instead of only through a
real fetch()/Agent call — the bug lived entirely in this callback, and
undici wraps any error thrown here as a generic "fetch failed" TypeError,
which is why isolating it matters for testability.

---------

Co-authored-by: brennanneoh <497569+brennanneoh@users.noreply.github.com>

* fix(ssh): do not offer chacha20-poly1305 without the native ssh2 binding (#1081)

The availability probe treated a working OpenSSL "chacha20" cipher as proof
that chacha20-poly1305@openssh.com is usable. It is not: ssh2 pure-JS
chacha20-poly1305 corrupts the transport, so the peer aborts the KEX
("incomplete message [preauth]") and the connection times out. Easy to hit
on jump-host connections whose target sshd negotiates chacha20-poly1305
first.

Only trust the native binding (sshcrypto.node); otherwise leave the cipher
disabled so filterCiphers() drops it and AES-GCM is negotiated instead.

Co-authored-by: XtraLarge <>

* fix: add Swiss German server layout (#1078)

* chore: update release notes

* feat: continue improving desktop app 2-way sync with logic fixes and a migration dialog

* fix: dekstop app showing auth form without syncing

* feat: create desktop auto sessions for existing setups

* feat: add electron backend killing

* fix: electron login and session related bugs and updated readme for v2.6.0

* chore: finalize release notes

* fix: click to expand hosts including extra bottom margin

* fix: desktop auth modal failing to log users in

* fix: desktop app failing to sync

* fix: reverse proxy causing sync error

* chore: lint, format, and bump version to 2.6.0

* chore: sync Crowdin translations for 2.6.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: L.H. <117188168+default-student@users.noreply.github.com>
Co-authored-by: default-student <default-student@github.com>
Co-authored-by: Brad Baker <xyzulu@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Brennan Neoh <brennanneoh@users.noreply.github.com>
Co-authored-by: brennanneoh <497569+brennanneoh@users.noreply.github.com>
Co-authored-by: XtraLarge <eMail@WilliWerres.de>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
2026-07-26 18:47:27 -05:00
LukeGus cf3e2cb499 fix: overlapping hosts when using click to expand 2026-07-19 14:24:38 -05:00
LukeGus 845bb494a6 Merge remote-tracking branch 'origin/main' 2026-07-19 13:09:05 -05:00
LukeGus 1b3a805010 chore: release workflow cleanup errors 2026-07-19 13:08:45 -05:00
LukeGus b2d1441164 chore: bump Homebrew cask to 2.5.1 2026-07-19 18:07:13 +00:00
+7 ddbdd5c437 release-2.5.1 (#1067)
* chore(deps): bump node from 24-slim to 26-slim in /docker in the docker-major-updates group (#1021)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore(deps): bump node in /docker in the docker-major-updates group

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>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.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-dev): bump the dev-patch-updates group with 24 updates (#1023)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore(deps-dev): bump the dev-patch-updates group with 24 updates

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

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.1` | `2.5.2` |
| [@codemirror/commands](https://github.com/codemirror/commands) | `6.10.3` | `6.10.4` |
| [@codemirror/view](https://github.com/codemirror/view) | `6.43.1` | `6.43.5` |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.14` | `1.2.15` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.17` | `1.1.18` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.5` | `1.3.6` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.17` | `1.1.18` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.18` | `2.1.19` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.10` | `2.1.11` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.17` | `1.1.18` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.10` | `1.1.11` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.12` | `1.2.13` |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.3.1` | `2.3.2` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.10` | `1.1.11` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.4.1` | `1.4.2` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.3.1` | `1.3.2` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.10` | `1.2.11` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.1` | `4.3.2` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.0.2` | `6.0.3` |
| [i18next](https://github.com/i18next/i18next) | `26.3.1` | `26.3.4` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.6.0` | `1.6.1` |
| [sharp](https://github.com/lovell/sharp) | `0.35.2` | `0.35.3` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.1` | `4.3.2` |


Updates `@biomejs/biome` from 2.5.1 to 2.5.2
- [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.2/packages/@biomejs/biome)

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

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

Updates `@radix-ui/react-accordion` from 1.2.14 to 1.2.15
- [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.17 to 1.1.18
- [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.5 to 1.3.6
- [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.17 to 1.1.18
- [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.18 to 2.1.19
- [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.10 to 2.1.11
- [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.17 to 1.1.18
- [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.10 to 1.1.11
- [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.12 to 1.2.13
- [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.1 to 2.3.2
- [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.10 to 1.1.11
- [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.1 to 1.4.2
- [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-switch` from 1.3.1 to 1.3.2
- [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.15 to 1.1.16
- [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.10 to 1.2.11
- [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 `@tailwindcss/vite` from 4.3.1 to 4.3.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.2/packages/@tailwindcss-vite)

Updates `@vitejs/plugin-react` from 6.0.2 to 6.0.3
- [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.3/packages/plugin-react)

Updates `i18next` from 26.3.1 to 26.3.4
- [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.1...v26.3.4)

Updates `radix-ui` from 1.6.0 to 1.6.1
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

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

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

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@codemirror/commands"
  dependency-version: 6.10.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@codemirror/view"
  dependency-version: 6.43.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.15
  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.18
  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.6
  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.18
  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.19
  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.11
  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.18
  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.11
  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.13
  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.2
  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.11
  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.2
  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.2
  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.16
  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.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: i18next
  dependency-version: 26.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: radix-ui
  dependency-version: 1.6.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: sharp
  dependency-version: 0.35.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-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: 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-patch-updates group with 2 updates (#1025)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore(deps): bump the prod-patch-updates group with 2 updates

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


Updates `axios` from 1.18.0 to 1.18.1
- [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.0...v1.18.1)

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

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: nanoid
  dependency-version: 5.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-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: 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 (#1026)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore(deps): bump the prod-minor-updates group with 3 updates

Bumps the prod-minor-updates group with 3 updates: [js-yaml](https://github.com/nodeca/js-yaml), [motion](https://github.com/motiondivision/motion) and [undici](https://github.com/nodejs/undici).


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

Updates `motion` from 12.40.0 to 12.42.2
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v12.40.0...v12.42.2)

Updates `undici` from 8.5.0 to 8.7.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.5.0...v8.7.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: motion
  dependency-version: 12.42.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: undici
  dependency-version: 8.7.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: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump electron from 42.4.1 to 43.0.0 in the major-updates group (#1027)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore(deps-dev): bump electron in the major-updates group

Bumps the major-updates group with 1 update: [electron](https://github.com/electron/electron).


Updates `electron` from 42.4.1 to 43.0.0
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v42.4.1...v43.0.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-version: 43.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: major-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: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Fix MC syntax highlighting artifacts (#996)

* Filter dashboard status hosts (#997)

* Persist dashboard service link changes (#999)

* Fix snippet text overflow (#1000)

* Persist remote desktop credential auth (#1001)

* Guard language switching failures (#1002)

* Resolve tunnel source credentials (#1003)

* Support Vault auth for monitors (#1004)

* Fix Windows file delete command (#1005)

* Fix release artifact checkout ref (#1006)

* Fix command palette escape in fullscreen (#1008)

* Fix alerts and audit log data normalization (#1010)

* Fix macOS VNC protocol negotiation (#1012)

* Fix port knocking before SSH connect (#1013)

* Allow Escape to close link confirmation (#1014)

* Prevent Electron modifier wheel zoom (#1016)

* Fix credential auth optional password (#1009)

* Retry transient terminal DNS lookups (#1011)

* Retry transient terminal DNS lookups

* Apply DNS retry to SSH entry points

* Fix OIDC redirect forwarded port handling (#1007)

* Preserve recent open tabs on startup (#1015)

* Fix fish prompt OSC highlighting (#998)

* Fix terminal font selection (#1018)

* fix: font legibility (#1019)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* fix: font legibility

Text was entirely unreadable in places for me. Especially with themes
like Catppuccin. The muted-foreground text and the tags too similiar to
the background.

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: russell <git@0896c69e.com>

* fix(file-manager): chunked uploads fail with 'Expected multipart/form-data request' (#1020)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* fix(file-manager): use postForm for chunked uploads so multipart content-type is sent

The fileManagerApi axios instance defaults to Content-Type:
application/json. Axios 1.x's default transformRequest converts a
FormData body to JSON whenever the request content type is
application/json, so every chunk POSTed to /ssh/uploadFileChunk
arrived as a JSON body like {"chunk":{}} and the backend rejected
it with 400 'Expected multipart/form-data request'. This breaks all
uploads of files larger than the 1.5 GiB chunking threshold.

The non-chunked path already uses postForm for /ssh/uploadFileStream;
use it for the chunk path too so axios keeps the FormData intact and
the browser sets the multipart boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: implement OIDC back-channel logout support with session management (#1028)

* feat: implement OIDC back-channel logout support with session management

* Fix OIDC back-channel logout handling

* Require logout token replay identifiers

---------

Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>

* Add API key host enrollment endpoint (#1029)

* Fix tmux detection for non-POSIX shells (#1030)

* Fix OPKSSH js-yaml ESM import (#1031)

* Fix Android Vietnamese IME input (#1032)

* Fix Firefox RDP clipboard paste (#1033)

* Fix Proxmox discovery over HTTPS (#1041)

* Fix external editor actions in file preview (#1042)

* Allow pinned hosts with name sorting (#1043)

* Fix Firefox desktop OIDC callback (#1044)

* feat(session): add recording and replay (#1049)

* Fix status checks through jump hosts (#1045)

* Add terminal font size shortcuts (#1047)

* feat: add Open File Manager to tab right-click menu (#1051)

Co-authored-by: SankeerthNara <sankeerthnara@gmail.com>

* perf: frontend request cache, poll pause, and code-split shell (#1052)

Host/status caching, shell code-split, SSH pool waits, host-metrics concurrency, background-tab idle, per-host status subscriptions, homepage poll quieting, and virtualized host sidebar + file manager lists.

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* feat: save quick connect sessions as hosts (#1055)

* fix: restore sudo password autofill settings (#1056)

* fix: preserve file editor position on save (#1057)

* fix: sync cloud preference storage mode (#1058)

* fix: render RDP sessions at native pixel density (#1059)

* fix: restore database import in embedded desktop mode (#1060)

* Update Auto-complete.tsx (#1061)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore: donation bar reporting wrong result

* feat: add Open File Manager to tab right-click menu (#1046)

* Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050)

This reverts commit 0712fdd731.

* Remove donation badge from README

Removed donation badge from README.

* Delete .github/workflows/donation-goal.yml

* Update Auto-complete.tsx

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>

* feat(auth): opt-in OIDC DEK unlock for API-key requests (ALLOW_APIKEY_DATA_UNLOCK) (#1064)

* chore: fix release workflow to merge docs branch

* fix: svg donation generator push fail

* fix: svg donation generator push fail

* Update termix.rb

* fix: svg donation generator push fail

* chore: move donation badge to badges branch to avoid ruleset conflicts

* chore: remove unneeded token from donation badge workflow

* chore: debug donation badge commit step

* fix: escape < character in donation SVG

* fix: point donation badge to badges branch

* chore: remove unused donation badge svg from main

* Add Rack Genius logo to README

Added Rack Genius logo to the README.

* chore: improve donation goal svg generator to include stablecoins

* chore: donation goal generator syntax error

* chore: donation goal generator incorrect docs url usage

* chore: donation bar reporting wrong result

* feat: add Open File Manager to tab right-click menu (#1046)

* Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050)

This reverts commit 0712fdd731.

* Remove donation badge from README

Removed donation badge from README.

* Delete .github/workflows/donation-goal.yml

* feat(auth): opt-in OIDC DEK unlock for API-key requests

API keys authenticate but cannot touch the encrypted credential/host store
('User data not unlocked') unless the user has a live interactive session,
making them unusable for headless automation. For OIDC users the DEK is
server-derivable (deriveOIDCSystemKey), so handleApiKeyAuth can unlock it
without a password.

Gated behind ALLOW_APIKEY_DATA_UNLOCK (default off) because enabling it widens
the blast radius of a leaked API key. OIDC-only; password users are untouched.

Refs #1063

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Luke Gustafson <88517757+LukeGus@users.noreply.github.com>
Co-authored-by: Sankeerth Nara <sankeerthnara@gmail.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>

* chore: package lock sync

* Add Proxmox guest auto sync (#1053)

* draft: database layer refactor (#1054)

* feat(sshid) - sshid.io equivalent for termix (#919)

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

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

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

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

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

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

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

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

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

---------

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

Bumps the major-updates group with 5 updates:

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


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

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

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

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

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

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

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

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

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

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

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

Two bugs introduced in v2.4.1:

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

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

Fixes #928

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

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

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

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

* fix: resolve tunnel endpoints reliably (#935)

* Fix Electron OIDC browser auth failures (#936)

* Allow RDP connections without stored credentials (#937)

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

* Fix terminal link dialog layering (#940)

* Confirm large files before opening editor (#942)

* Confirm closing active host connections (#943)

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

* fix: preserve unicode guacamole tokens (#933)

* Persist VNC authentication settings (#944)

* Fix Guacamole websocket base path (#946)

* Promote file manager terminals to tabs (#939)

* Guard Guacamole disconnect during startup (#945)

* chore: increment ver

* feat: bitwarden ssh agent integration

* feat: serial connections support

* fix: various small bug fixes

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

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

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

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

* feat: new grid and widget based homepage function

* feat: new donate button in dashboard

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

* chore: start database layer refactor

* docs: plan database layer refactor

* docs: audit database layer refactor phase zero

* chore: add database runtime adapter skeleton

* chore: add settings repository skeleton

* chore: add user session repository skeleton

* chore: add host credential repository skeleton

* chore: add field encryption boundary

* chore: migrate settings route slice

* chore: migrate user settings routes

* chore: migrate host metrics settings routes

* chore: migrate acme settings route

* chore: migrate terminal settings route

* chore: migrate tailscale settings read

* chore: migrate guacamole settings reads

* chore: migrate session timeout settings reads

* chore: migrate auth route settings reads

* chore: migrate host metrics settings reads

* chore: migrate startup settings reads

* chore: migrate user settings cleanup

* chore: migrate password reset settings

* chore: migrate oidc legacy settings read

* chore: migrate user route settings slice

* chore: migrate oidc state settings

* chore: migrate user login settings reads

* chore: migrate user crypto settings

* chore: consolidate startup settings defaults

* chore: consolidate database settings import export

* chore: migrate core session auth paths

* chore: migrate remaining session auth paths

* chore: migrate admin user routes

* chore: migrate user route admin checks

* chore: migrate user lifecycle routes

* chore: migrate auth user lookups

* chore: migrate oidc user routes

* chore: migrate api key repository paths

* docs: add database gray rollout guide

* chore: migrate trusted device paths

* chore: migrate user session route user lookups

* chore: add database repository rollout guard

* chore: expose repository rollout status

* chore: warn on repository rollout misconfiguration

* chore: migrate remaining user lookup helpers

* chore: migrate ssh user lookups

* chore: migrate user settings admin lookups

* chore: migrate acme ssl user lookups

* chore: migrate audit log admin checks

* chore: migrate oidc account user updates

* chore: migrate password reset user updates

* chore: migrate user deletion core records

* chore: migrate snippet audit user lookups

* chore: migrate ldap user sync paths

* chore: migrate totp user updates

* chore: migrate rbac user checks

* chore: migrate rbac role paths

* chore: migrate permission role lookups

* chore: migrate rbac access list reads

* chore: migrate shared rbac reads

* chore: migrate rbac access writes

* chore: migrate permission host access

* chore: migrate role host access lookup

* chore: migrate snippet access lookup

* chore: migrate shared credential access lookups

* chore: migrate host access cleanup writes

* chore: migrate host list access checks

* chore: migrate host access cleanup routes

* chore: migrate shared credential role lookups

* chore: migrate user role cleanup

* chore: migrate admin role sync

* chore: migrate ldap role sync

* chore: migrate user role assignment

* chore: migrate sso provider access

* chore: migrate audit log access

* chore: migrate user preference access

* chore: migrate open tab access

* chore: migrate dismissed alert access

* chore: migrate homepage layout access

* chore: migrate network topology access

* chore: migrate dashboard service link access

* chore: migrate command history access

* chore: migrate recent activity cleanup

* chore: migrate ssh credential usage access

* chore: migrate transfer recent access

* chore: migrate file manager bookmark access

* chore: migrate c2s tunnel preset access

* chore: migrate homepage item access

* chore: migrate session recording access

* chore: migrate tmux session tag access

* chore: migrate opkssh token access

* chore: migrate vault token access

* chore: migrate vault profile access

* chore: migrate host metrics preference access

* chore: migrate host health access

* chore: migrate host metrics history access

* chore: migrate alert persistence access

* chore: route alert host lookup through repository

* chore: migrate user data export reads

* chore: route host metrics stats sync through repository

* chore: migrate host folder persistence

* chore: migrate host resolution reads

* chore: route jump host resolution reads

* chore: route docker console jump host reads

* chore: route docker ssh resolution reads

* chore: route proxmox discovery resolution reads

* chore: route file manager activity host reads

* chore: route host metrics resolution reads

* chore: route ssh auth credential reads

* chore: route tunnel endpoint credential reads

* chore: route credential deployment resolution reads

* chore: route command history host flag reads

* chore: route snippet execution resolution reads

* chore: route terminal host resolution reads

* chore: route vault oidc host resolution reads

* chore: route wake on lan host reads

* chore: route internal host list reads

* chore: route host key verification persistence

* chore: route credential read paths

* chore: route credential host usage reads

* chore: route credential folder rename

* chore: route host owner access checks

* chore: route shared credential source reads

* chore: route user host credential cleanup

* chore: route credential delete reads

* chore: route credential update reads

* chore: route host credential reads

* chore: route host read paths

* chore: route host projection reads

* chore: route host list reads

* chore: route snippet read paths

* chore: route snippet folder writes

* chore: route snippet crud paths

* chore: route snippet bulk import

* chore: route rbac ownership reads

* chore: route user count reads

* chore: route cleanup snippets folders

* chore: route shared credential persistence

* chore: route dashboard activity

* chore: route guacamole host reads

* chore: route host bulk lookups

* chore: remove unlock-only simple db ops

* chore: route host autostart persistence

* chore: route ldap provisioning through users

* chore: route credential encrypted writes

* chore: route host encrypted writes

* chore: route bulk host encrypted writes

* chore: route termix id credentials

* chore: route termix id ca persistence

* chore: route termix identity persistence

* chore: route credential system migration

* chore: isolate user encryption migration storage

* chore: remove legacy simple db ops

* chore: isolate legacy sqlite migration copy

* chore: route database settings import export

* chore: route database host credential export

* chore: route database host credential import

* chore: route database file-manager import export

* chore: route database alert usage import export

* chore: route database user checks

* chore: isolate auth lazy migration storage

* chore: route explicit database saves

* chore: initialize database save boundary

* chore: route migration snapshot saves

* chore: isolate sqlite import constraints

* chore: route import sqlite boundary

* chore: route user encryption migration store

* chore: centralize current repository runtime

* chore: route more current repositories

* chore: route activity repository runtimes

* chore: route token repository runtimes

* chore: route health repository runtimes

* chore: route identity repository runtimes

* chore: route rbac repository runtime

* chore: centralize current sqlite runtime access

* chore: route user deletion key cleanup

* chore: route user deletion vault cleanup

* chore: route user deletion homepage cleanup

* chore: route user deletion health cleanup

* chore: route user deletion alert cleanup

* chore: route user deletion identity cleanup

* chore: add database layer preupgrade backup

* Fix database repository type errors

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

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

---------

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

* refactor(db): collapse repository rollout scaffolding into single factory

Repositories are now the only data path. Replaces the 41 current-*-repository
wrapper files, the DATABASE_LAYER_REPOSITORY_ROLLOUT flag/alias map and the
unused database/runtime adapter with repositories/factory.ts, a plain
DatabaseContext type and an in-memory TestSqliteDatabase test harness.

* refactor(db): route remaining raw DB access through repositories

proxmox, session-log, oidc-utils, webauthn and guacamole recording now use
repositories (new WebauthnCredentialRepository; SsoProviderRepository
listEnabled; HostRepository findDecryptedByIdAs/listProxmoxEnabled).
Remaining raw access: db boot code, simple-db-ops and docker.ts, which are
removed/restructured in later phases.

* feat(crypto): add UserKeyManager with system-wrapped per-user DEKs

New utils/user-keys.ts: one random 32-byte DEK per user, wrapped
AES-256-GCM under an HKDF key derived from the system ENCRYPTION_KEY
(per-user info string + AAD binding, versioned v3 wrap format stored in
settings). Synchronous unwrap-on-demand with a 15-minute cache so the
existing DataCrypto facade keeps its sync call sites. Not wired up yet.

* feat(crypto): boot-time DEK migration to system-wrapped v3 format

utils/crypto-migration/dek-migration.ts carries the legacy unwrap paths
(PBKDF2 password KEK, OIDC/WebAuthn system keys, hardcoded-default
fallback) and migrates every server-unwrappable DEK to the v3 wrap at
startup. Password-wrapped DEKs migrate at next login or from a live
session via adoptRecoveredDEK. Legacy rows are kept for now; cleanup
flips on once the new path is authoritative.

* refactor(crypto): make system-wrapped DEKs the authoritative key path

DataCrypto and AuthManager now read keys through UserKeyManager: DEKs are
always unwrappable server-side, so the in-memory unlock session, DEK-in-JWT
wrapping, session-expiry data locks and ALLOW_APIKEY_DATA_UNLOCK are gone.
utils/user-crypto.ts is deleted; boot migration now cleans legacy wraps.
A one-release shim adopts DEKs from legacy dataKeyWrap tokens so active
password users migrate without re-login. Password login migrates legacy
password-wrapped DEKs via migratePasswordUserAtLogin.

* refactor(crypto): remove pending share queue and credential sharing key

With server-unwrappable DEKs both sides of a share are always available,
so the needsReEncryption queue, CREDENTIAL_SHARING_KEY and the system_*
shadow columns on ssh_credentials are gone. A one-time boot cleanup
re-creates legacy pending share copies where possible (dropping
unresolvable ones with a warning) and drops the legacy columns.

* feat(auth): non-destructive password resets and admin reset endpoint

Password resets no longer destroy user data: the DEK is system-wrapped, so
forgot-password and admin resets are just a hash update plus session revoke.
The wipe branch survives only for accounts that never logged in since the
encryption upgrade and now requires explicit confirmDataWipe (surfaced as a
409 DATA_WIPE_REQUIRED; the reset UI asks for confirmation). Adds
POST /users/admin/reset-password and removes the dead re-encryption paths.

* refactor(ssh): consolidate four jump-host chain copies into one module

terminal, host-metrics and docker now use ssh/jump-host-chain.ts (already
shared by file-manager, tmux-monitor and docker-console); docker's inline
copy also drops its raw SimpleDBOps host/credential lookups in favor of
repositories.

* refactor(ssh): single shared createConnectionLog helper

file-manager-log.ts becomes ssh/connection-log.ts; the copies in docker.ts
and host-metrics-helpers.ts are gone.

* refactor(ssh): split docker module into layered directory

ssh/docker/{index,routes,session-manager,container-routes,console}.ts:
server boot and wiring in index, HTTP handlers in routes, SSH session
registry and command execution in session-manager. Code motion only;
port 30007/30009 and endpoints unchanged. Swagger now scans ssh
subdirectories.

* refactor(ssh): split tunnel module into layered directory

ssh/tunnel/{index,routes,manager}.ts: server boot in index, HTTP handlers
in routes, tunnel state and engine (connect/retry/autostart) in manager.
Code motion only; port 30003 and endpoints unchanged.

* refactor(backend): reorganize top-level layout

- ssh/ renamed to hosts/ (it covers SSH, RDP, VNC, Telnet, Docker, metrics)
- serial/serial.ts and guacamole/ moved inside hosts/
- dashboard.ts and homepage.ts moved to services/
- swagger.ts moved to utils/ with adjusted scan globs

Import paths and the generate:openapi script updated; ports and endpoints
unchanged.

* refactor(tests): move backend tests into src/backend/tests mirror tree

Backend *.test.ts files (and the test-support harness) no longer sit next
to source files; they live under src/backend/tests/ mirroring the source
layout. Imports rewritten accordingly; CLAUDE.md convention updated.

* refactor(hosts): group host modules into per-feature directories

file-manager/, metrics/ (incl. widgets, managers, alert-engine),
terminal/, tmux/ and tunnel/ each own their files; docker/ gains
container-runtime. Genuinely shared helpers (jump-host chain, host
resolver, connection pool, opkssh, vault, serial) stay at hosts/ root.
Pure file moves with import path updates; mirrored test paths follow.

* refactor(backend): final cleanup pass

- re-register WebAuthn passkey routes (registration was dropped in the
  #1054 merge, breaking passkey login) and document all six endpoints
- delete utils/simple-db-ops.ts (last caller migrated to DataCrypto)
- starter: use the typed serverReady export, collapse the four-way
  version lookup to env then package.json candidates
- add OpenAPI JSDoc to c2s-tunnel-presets endpoints
- strip block-divider comment banners

* feat: remove legacy "data_unlocked" field

* feat: refactor rbac/sharing to support new permissions and auth types

* feat: refactor rbac/sharing to support new permissions and auth types

* feat: add "id" to user profile hide list

* chore: root cleanup

* feat: add more donation references and a 30-day donation reminder

* chore: update readme

* feat: automate beta tests

* feat: add links to milestones

* fix: hoist github/google SSO defaults to module scope (#1065)

* fix(ssh-tools): allow clipboard paste in key recording field (#1066)

The broadcast key-recording input was marked readOnly, which makes
browsers block paste entirely (no context-menu Paste, Ctrl+V does
nothing). handleKeyDown also called preventDefault() unconditionally,
swallowing the Ctrl+V shortcut before a paste event could even fire.

Let Ctrl/Cmd+V pass through in handleKeyDown, drop readOnly, and add
an onPaste handler that reads the clipboard text and broadcasts it to
the selected terminals like any other captured keystroke.

Signed-off-by: emreumar <emreumar@users.noreply.github.com>
Co-authored-by: emreumar <emreumar@users.noreply.github.com>

* chore: write release notes

* chore: update release notes

* chore: update readmes

* chore: add crypto only reminder in en.json

* fix: macOS and cask errors on release workflow

* chore: sync Crowdin translations for 2.5.1

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: emreumar <emreumar@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: Russell Poovey <09.our_seekers@icloud.com>
Co-authored-by: russell <git@0896c69e.com>
Co-authored-by: Subedi Bibek <77529535+questbibek@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Alexander Elsner <101340634+Bensonheimer992@users.noreply.github.com>
Co-authored-by: SankeerthNara <sankeerthnara@gmail.com>
Co-authored-by: Stephan Groth <96803994+Kalvalax@users.noreply.github.com>
Co-authored-by: DivByZero <mr.oplus@yahoo.fr>
Co-authored-by: devdanetra <46488477+devdanetra@users.noreply.github.com>
Co-authored-by: Aleksandr Fominykh <neoformalex@users.noreply.github.com>
Co-authored-by: sash <sash@fominykh.io>
Co-authored-by: lhojun <ldgs3324@gmail.com>
Co-authored-by: Yunus Emre Umar <77045015+emre155@users.noreply.github.com>
Co-authored-by: emreumar <emreumar@users.noreply.github.com>
2026-07-19 12:29:52 -05:00
Luke Gustafson fba645e92e Add FUNDING.json for Ethereum drips configuration 2026-07-18 17:14:01 -05:00
LukeGus a6d0658e41 chore: display donations in readme 2026-07-15 15:41:29 -05:00
Luke Gustafson 1aea3603a7 Delete .github/workflows/donation-goal.yml 2026-07-13 20:04:16 -05:00
Luke Gustafson 972e27eb64 Remove donation badge from README
Removed donation badge from README.
2026-07-13 20:03:42 -05:00
ZacharyZcR 38e128bd16 Revert "feat: add Open File Manager to tab right-click menu (#1046)" (#1050)
This reverts commit 0712fdd731.
2026-07-14 01:36:12 +08:00
Sankeerth Nara 0712fdd731 feat: add Open File Manager to tab right-click menu (#1046) 2026-07-14 01:16:11 +08:00
LukeGus bb5559c696 chore: donation bar reporting wrong result 2026-07-06 15:22:13 -05:00
LukeGus b2ff35e106 chore: donation goal generator incorrect docs url usage 2026-07-01 00:41:45 -05:00
LukeGus 19a2cb8eed chore: donation goal generator syntax error 2026-07-01 00:36:45 -05:00
LukeGus 078e6d5de0 chore: improve donation goal svg generator to include stablecoins 2026-06-30 22:41:32 -05:00
Luke Gustafson 794714368a Add Rack Genius logo to README
Added Rack Genius logo to the README.
2026-06-30 13:32:22 -05:00
LukeGus 4fbaf50e08 chore: remove unused donation badge svg from main 2026-06-29 15:16:16 -05:00
LukeGus 4ec4cfcdb7 fix: point donation badge to badges branch 2026-06-29 15:14:29 -05:00
LukeGus a46f2f1343 fix: escape < character in donation SVG 2026-06-29 15:11:29 -05:00
LukeGus 72e5ff5a64 chore: debug donation badge commit step 2026-06-29 15:07:34 -05:00
LukeGus 63cfdfda9e chore: remove unneeded token from donation badge workflow 2026-06-29 15:01:06 -05:00
LukeGus 3a3b51d1ae chore: move donation badge to badges branch to avoid ruleset conflicts 2026-06-29 14:59:29 -05:00
LukeGus 3f4280c2ff Merge remote-tracking branch 'origin/main' 2026-06-29 14:52:33 -05:00
LukeGus 97124bc3b6 fix: svg donation generator push fail 2026-06-29 14:52:14 -05:00
Luke Gustafson 253be0077e Update termix.rb 2026-06-29 14:45:46 -05:00
LukeGus fe96e68872 fix: svg donation generator push fail 2026-06-29 14:26:30 -05:00
LukeGus 30acbed1a7 fix: svg donation generator push fail 2026-06-29 14:05:47 -05:00
LukeGus 7416734f68 chore: fix release workflow to merge docs branch 2026-06-29 13:34:00 -05:00
Luke Gustafsondependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>sashLukeGusDivByZerodevdanetraAleksandr FominykhZacharyZcR
9de904dd4c release-2.5.0 (#994)
* feat(sshid) - sshid.io equivalent for termix (#919)

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

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

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

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

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

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

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

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

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

---------

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

Bumps the major-updates group with 5 updates:

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


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

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

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

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

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

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

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

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

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

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

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

Two bugs introduced in v2.4.1:

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

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

Fixes #928

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

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

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

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

* fix: resolve tunnel endpoints reliably (#935)

* Fix Electron OIDC browser auth failures (#936)

* Allow RDP connections without stored credentials (#937)

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

* Fix terminal link dialog layering (#940)

* Confirm large files before opening editor (#942)

* Confirm closing active host connections (#943)

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

* fix: preserve unicode guacamole tokens (#933)

* Persist VNC authentication settings (#944)

* Fix Guacamole websocket base path (#946)

* Promote file manager terminals to tabs (#939)

* Guard Guacamole disconnect during startup (#945)

* chore: increment ver

* feat: bitwarden ssh agent integration

* feat: serial connections support

* fix: various small bug fixes

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

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

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

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

* feat: new grid and widget based homepage function

* feat: new donate button in dashboard

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

* chore: fix ci checks (#966)

* Fix dashboard service link creation (#950)

* Fix jump host SOCKS5 proxy selection (#951)

* Fix jump host SOCKS5 proxy selection

* fix: type jump host socks proxy config

* Fix tmux detection path handling (#949)

* Support GUACD_URL environment config (#952)

* Retry autostart tunnel host fetches (#953)

* Retry autostart tunnel host fetches

* chore: format tunnel route

* Fix PUID html ownership in Docker entrypoint (#954)

* feat: allow custom tunnel endpoints (#977)

* fix: skip metrics start for non-ssh hosts (#976)

* Fix Proxmox import auth fallback (#956)

* Fix Proxmox import auth fallback

* chore: format proxmox import auth

* Fix SSH heading syntax highlighting (#955)

* Fix SSH heading syntax highlighting

* chore: format terminal highlighter

* Initialize auth before fullscreen terminal routes (#957)

* Add WebAuthn passkey authentication (#959)

* chore: add Biome tooling (#965)

* chore: add biome tooling

* chore: support tailwind syntax in biome

* Fix VNC required argument handshake (#968)

* Prioritize host results in command palette search (#969)

* Prevent sidebar host hover layout shift (#970)

* Add terminal font zoom with mouse wheel (#971)

* Add host temperature metrics card (#972)

* Make file downloads reliable in desktop app (#973)

* Add app rail hover expansion setting (#974)

* fix: use correct translation key for nav.close (#964)

* fix(tunnel): skip endpoint credential validation for direct tunnels (#963)

* fix: SSH port connection bug (#975)

* fix: chunked upload for files >=1.5GB to bypass browser ArrayBuffer limit (#948)

* feat: support SSH agent auth across SSH features (#960)

* feat: add Podman container runtime support (#958)

* chore: update readme and release notes

* fix: stabilize Windows app icon (#978)

* Add safe host sharing export (#979)

* Add external editor support for file manager (#985)

* Rework SSH credential password handling (#984)

* Support password fallback for SSH key credentials

* Complete SSH credential password fallback

* Fix runtime base path for auth callbacks (#982)

* Fix TUI terminal output highlighting (#983)

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

* Add app fullscreen mode (#993)

* Fix host metrics startup polling (#986)

* chore: update release notes

* fix: line chart text overlap

* chore: update RELEASE_NOTES.md

* chore: add donation goal to readme

* chore: update readme

* fix: hide full-screen button in electron app

* fix: failed unit tests

* chore: lint, format, and bump version to 2.5.0

* chore: remove timeout from crowdin translate

* chore: sync Crowdin translations for 2.5.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: DivByZero <mr.oplus@yahoo.fr>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: devdanetra <46488477+devdanetra@users.noreply.github.com>
Co-authored-by: Aleksandr Fominykh <neoformalex@users.noreply.github.com>
Co-authored-by: sash <sash@fominykh.io>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
2026-06-29 13:28:26 -05:00
Luke Gustafson fd27a366d0 Add funding configuration to GitHub 2026-06-27 02:32:20 -05:00
LukeGus eb49f197ca fix: alert ui incorrectly using termix css and fixed issue with alert system not loading 2026-06-26 03:28:57 -05:00
LukeGus 98195ec5c3 feat: new donate button in dashboard 2026-06-26 03:28:12 -05:00
LukeGus 9c317251ca chore: update readme to include new donation links 2026-06-26 02:36:07 -05:00
Luke Gustafson 6194a58b1a Update termix.rb 2026-06-21 16:40:11 -05:00
LukeGus b1ec2bcd2b fix: skip YouTube publish if video is already public 2026-06-21 16:17:18 -05:00
LukeGus 0354401640 fix: add contents:write permission and GHCR_TOKEN for create-release job 2026-06-21 16:07:40 -05:00
Luke Gustafsonanddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> d1a8dcf3c6 release-2.4.1 (#921)
* chore(deps-dev): bump the dev-minor-updates group across 1 directory with 12 updates (#910)

Bumps the dev-minor-updates group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-select](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/select) | `2.2.6` | `2.3.1` |
| [@radix-ui/react-slider](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slider) | `1.3.6` | `1.4.1` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.5` | `1.3.0` |
| [@radix-ui/react-switch](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/switch) | `1.2.6` | `1.3.1` |
| [cytoscape](https://github.com/cytoscape/cytoscape.js) | `3.33.4` | `3.34.0` |
| [electron](https://github.com/electron/electron) | `42.2.0` | `42.4.1` |
| [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder) | `26.8.1` | `26.15.3` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.16.0` | `1.20.0` |
| [radix-ui](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/radix-ui) | `1.4.3` | `1.6.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.76.1` | `7.79.0` |
| [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.60.0` | `8.61.1` |



Updates `@radix-ui/react-select` from 2.2.6 to 2.3.1
- [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-slider` from 1.3.6 to 1.4.1
- [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.2.5 to 1.3.0
- [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.2.6 to 1.3.1
- [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 `cytoscape` from 3.33.4 to 3.34.0
- [Release notes](https://github.com/cytoscape/cytoscape.js/releases)
- [Commits](https://github.com/cytoscape/cytoscape.js/compare/v3.33.4...v3.34.0)

Updates `electron` from 42.2.0 to 42.4.1
- [Release notes](https://github.com/electron/electron/releases)
- [Commits](https://github.com/electron/electron/compare/v42.2.0...v42.4.1)

Updates `electron-builder` from 26.8.1 to 26.15.3
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-builder@26.15.3/packages/electron-builder)

Updates `lucide-react` from 1.16.0 to 1.20.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.20.0/packages/lucide-react)

Updates `radix-ui` from 1.4.3 to 1.6.0
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/radix-ui/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/radix-ui)

Updates `react-hook-form` from 7.76.1 to 7.79.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.76.1...v7.79.0)

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

Updates `typescript-eslint` from 8.60.0 to 8.61.1
- [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.61.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-select"
  dependency-version: 2.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-slider"
  dependency-version: 1.4.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@radix-ui/react-switch"
  dependency-version: 1.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: cytoscape
  dependency-version: 3.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: electron
  dependency-version: 42.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: electron-builder
  dependency-version: 26.15.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: lucide-react
  dependency-version: 1.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: radix-ui
  dependency-version: 1.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.79.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.61.1
  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 node in /docker in the docker-major-updates group (#914)

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>

* ci(deps): bump the github-actions group with 2 updates (#915)

Bumps the github-actions group with 2 updates: [actions/checkout](https://github.com/actions/checkout) and [actions/setup-node](https://github.com/actions/setup-node).


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

Updates `actions/setup-node` from 4 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: '6'
  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): bump the prod-minor-updates group across 1 directory with 5 updates (#916)

Bumps the prod-minor-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [axios](https://github.com/axios/axios) | `1.17.0` | `1.18.0` |
| [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) | `12.10.0` | `12.11.1` |
| [body-parser](https://github.com/expressjs/body-parser) | `2.2.2` | `2.3.0` |
| [multer](https://github.com/expressjs/multer) | `2.1.1` | `2.2.0` |
| [undici](https://github.com/nodejs/undici) | `8.4.0` | `8.5.0` |



Updates `axios` from 1.17.0 to 1.18.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.17.0...v1.18.0)

Updates `better-sqlite3` from 12.10.0 to 12.11.1
- [Release notes](https://github.com/WiseLibs/better-sqlite3/releases)
- [Commits](https://github.com/WiseLibs/better-sqlite3/compare/v12.10.0...v12.11.1)

Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.2...v2.3.0)

Updates `multer` from 2.1.1 to 2.2.0
- [Release notes](https://github.com/expressjs/multer/releases)
- [Changelog](https://github.com/expressjs/multer/blob/main/CHANGELOG.md)
- [Commits](https://github.com/expressjs/multer/compare/v2.1.1...v2.2.0)

Updates `undici` from 8.4.0 to 8.5.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.4.0...v8.5.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: better-sqlite3
  dependency-version: 12.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: multer
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: undici
  dependency-version: 8.5.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>

* feat: add unlink button for dual auth

* fix: general bug fixes

* fix: general qol/small feature additions

* fix: 100mb max upload size

* fix: terminal syntax not handling carriage returns or shell prompt lines properly

* feat: continued general improvements

* fix: terminal outputting success right after folder path

* chore: update release notes

* feat: continued fixes and improvements

* chore: lint, format, and bump version to 2.4.1

* chore: sync Crowdin translations for 2.4.1

* fix: rebase dev branch onto main before Crowdin download

* fix: use merge instead of rebase to sync main before Crowdin

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-21 15:55:41 -05:00
Luke Gustafson 8072b4ede9 Update Discord server description in config.yml 2026-06-19 18:07:48 -05:00
LukeGus 688e662042 fix: restore translations to pre-2.4.0 state 2026-06-16 17:00:24 -05:00
LukeGus 612ba046a8 Merge remote-tracking branch 'origin/main' 2026-06-16 16:58:59 -05:00
LukeGus 27d613e50c chore: automated release updates 2026-06-16 16:58:52 -05:00
Luke Gustafson b02e9876ae Update termix.rb 2026-06-16 16:40:15 -05:00
LukeGus e2db514173 chore: fix automated docs release 2026-06-16 16:25:27 -05:00
+2
Luke GustafsonClaude Fable 5i/o agentLukeGusXtraLargeverybadsoldierFélix MARQUETRabindra Kumar Meherdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Jose Tenggrencopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Kang
ac24fdcba8 release-2.4.0 (#913)
* feat: continued adding features to the release.yml

* feat: added auto youtube submit

* feat: add dry run to release workflow

* feat: improve release workflow mode system with new dropdown and overwrite release mode

* fix: make credentials start collapsed

* fix: make hostname fill entire column and truncate at proper spot in dashboard

* fix: guacamole display using an incorrect height

* feat: add folder management (nested folders, folder icons, folder colors, improved folder selection, etc.)

* feat: move ssh host config outside of top tab bar and into new tab bar visible when on SSH tab

* fix: 2FA failing to disable

* feat: Proxmox guest discovery and import (#881)

* feat(db): add Proxmox host fields and schema migration

Add enableProxmox flag and proxmoxConfig JSON to the hosts table so an
SSH host can be marked as a Proxmox node and carry per-host discovery
settings (default credential, Windows/Docker name patterns, preferred
IP prefixes). Includes addColumnIfNotExists migration entries and the
matching SSHHost / HostData / Host type definitions.

* feat(api): add Proxmox guest discovery endpoint

Add POST /proxmox/discover which connects to a Proxmox node over SSH
using the host's existing stored credentials (no separate API token),
runs pvesh to enumerate cluster guests, and returns VMs and LXC
containers ready to import. Guest IPs are resolved with bounded
concurrency (LXC from net config, QEMU via guest agent) to stay within
the node's SSH MaxSessions; configured preferred prefixes pick the
address when a guest has several. Name patterns flag Windows guests as
RDP and enable Docker. Node names are validated before use in shell
commands and the SSH connection is always closed via try/finally. The
endpoint carries an OpenAPI annotation. Persists the new host fields
across host create/update/select, the normalizers (transformHostResponse)
and the bulk routes, with normalizer test coverage.

* feat(ui): add Proxmox host settings and guest import

Add a Proxmox tab to the host editor (following the Docker tab pattern)
to enable the integration and configure the default credential, name
patterns and preferred IP ranges. Proxmox-enabled hosts show a discover
action in the sidebar that opens an import dialog listing the cluster's
guests with multi-select; the import reuses the host's credential, maps
Windows guests to RDP, and groups imported guests into a folder named
after the Proxmox host. Also exposes the import via the hosts
import/export menu. All user-facing strings are added to the en.json
locale and rendered via i18n. Built with the existing shadcn Select,
dialog and section-card components to match the dark theme.

* feat(ui): add Proxmox to the bulk feature toggle

Proxmox was the only host feature missing from the multi-select
"Features" menu. Enabling it in bulk is special: unlike the other
toggles it needs a credential to be useful, so each host now defaults
its Proxmox credential to the credential already stored on that host
(host.credentialId). Discovery then works right away without picking
one by hand, and per-host settings can still be adjusted afterwards.

- SidebarTree: Enable/Disable Proxmox entries (Boxes icon) in the
  bulk Features dropdown
- host-bulk-routes: enabling Proxmox runs per-host so each host keeps
  its own default credential; existing proxmoxConfig is preserved;
  disabling stays a simple flag flip
- en.json: enableProxmoxFeature / disableProxmoxFeature

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix: reliable OIDC group syncing for admin roles (#882)

- always fetch from the userinfo endpoint to get extra claims (like `groups` from Authelia) instead of skipping it if the id_token was already parsed
- handle cases where the OIDC provider sends groups as a single string, a comma-separated string
- add debug logging

* fix: incorrect guacd screen height

* fix: guacd black screen on connnection

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* feat: initial server stats rewrite (renamed to host metrics)

* feat: continue renaming and improving host metrics features and UI

* fix: clipboard copy fails in Brave/non-HTTPS, dashboard total credentials stuck at 0, host username ignored when credential attached, cloned host can't switch auth method, file-manager context menu off-screen, file delete affecting inactive tabs, silent delete failure on Windows hosts, iPad host tap does nothing, OIDC custom group claim support

* chore: update dependabot for my app and add auto merge/branch update

* chore: update dependabot for my app and add auto merge/branch update

* Feat/docker release tag (#894)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* feat(docker): add version tag to production Docker image tags

---------

Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>

* fix: disable hardware acceleration on Windows to prevent startup crash (#895)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* fix: disable hardware acceleration on Windows to prevent startup crash

---------

Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>

* chore(deps-dev): bump the dev-patch-updates group across 1 directory with 23 updates (#893)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps-dev): bump the dev-patch-updates group across 1 directory with 23 updates

Bumps the dev-patch-updates group with 22 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@codemirror/autocomplete](https://github.com/codemirror/autocomplete) | `6.20.2` | `6.20.3` |
| [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) | `21.0.1` | `21.0.2` |
| [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) | `21.0.1` | `21.0.2` |
| [@radix-ui/react-accordion](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/accordion) | `1.2.12` | `1.2.13` |
| [@radix-ui/react-alert-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/alert-dialog) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.3` | `1.3.4` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.16` | `2.1.17` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.8` | `2.1.9` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.8` | `1.1.9` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.10` | `1.2.11` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.8` | `1.1.9` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.13` | `1.1.14` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.8` | `1.2.9` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `25.9.2` |
| [i18next](https://github.com/i18next/i18next) | `26.3.0` | `26.3.1` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `17.0.5` | `17.0.7` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.7` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.17` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.7` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.14` | `8.0.16` |



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

Updates `@commitlint/cli` from 21.0.1 to 21.0.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.0.2/@commitlint/cli)

Updates `@commitlint/config-conventional` from 21.0.1 to 21.0.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.0.2/@commitlint/config-conventional)

Updates `@radix-ui/react-accordion` from 1.2.12 to 1.2.13
- [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.15 to 1.1.16
- [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.3 to 1.3.4
- [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.15 to 1.1.16
- [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.16 to 2.1.17
- [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.8 to 2.1.9
- [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.15 to 1.1.16
- [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.8 to 1.1.9
- [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.10 to 1.2.11
- [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-separator` from 1.1.8 to 1.1.9
- [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-slot` from 1.2.4 to 1.2.5
- [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-tabs` from 1.1.13 to 1.1.14
- [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.8 to 1.2.9
- [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 `@types/node` from 25.9.1 to 25.9.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `i18next` from 26.3.0 to 26.3.1
- [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.0...v26.3.1)

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

Updates `react` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

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

Updates `react-dom` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `vite` from 8.0.14 to 8.0.16
- [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.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: "@codemirror/autocomplete"
  dependency-version: 6.20.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@commitlint/cli"
  dependency-version: 21.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 21.0.2
  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.13
  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.16
  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.4
  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.16
  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.17
  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.9
  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.16
  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.9
  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.11
  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.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.2.5
  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.14
  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.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/node"
  dependency-version: 25.9.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: i18next
  dependency-version: 26.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: lint-staged
  dependency-version: 17.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump undici from 7.26.0 to 8.4.0 (#891)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump undici from 7.26.0 to 8.4.0

Bumps [undici](https://github.com/nodejs/undici) from 7.26.0 to 8.4.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.26.0...v8.4.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump the prod-minor-updates group with 2 updates (#890)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump the prod-minor-updates group with 2 updates

Bumps the prod-minor-updates group with 2 updates: [axios](https://github.com/axios/axios) and [js-yaml](https://github.com/nodeca/js-yaml).


Updates `axios` from 1.16.1 to 1.17.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.16.1...v1.17.0)

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

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: js-yaml
  dependency-version: 4.2.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: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/setup-node from 4 to 6 (#887)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump actions/setup-node from 4 to 6

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/checkout from 4 to 6 (#886)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump actions/checkout from 4 to 6

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/upload-artifact from 4 to 7 (#885)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump actions/upload-artifact from 4 to 7

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/download-artifact from 4 to 8 (#884)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* chore(deps): bump actions/download-artifact from 4 to 8

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore: automate rebasing dependabot

* feat: add admin settings audit log feature

* feat: add customizability of app rail tabs

* fix: host metrics giving sudo password error

* feat: add terminal session logging

* feat: sso system redesign (multiple oidc, ldap support, etc.)

* feat: tailscale auth support

* feat: improve syntax highlighting system

* feat: improve nested folder system/ui

* feat: improve nested folder system/ui

* fix: move tailscale i18n and add scrollbar to ssh host manager customization

* feat: improve syntax highlighting, fix tailscale bugs and host manger UX

* fix: admin page not loading user admin information

* chore: update crowdin workflow

* fix: guacd getting passed incorrect protocol for app view

* fix: add SFTP jump-host fallback from host data (#902)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* fix: add SFTP jump-host fallback from host data

---------

Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* feat(tmux-monitor): add per-host tmux session monitor (#896)

* fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)

* Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"

This reverts commit 4d04559ca5.

* feat(tmux-monitor): add per-host tmux session monitor

New isolated feature module following the docker/server-stats pattern:

- Backend Express service on port 30010 (src/backend/ssh/tmux-monitor.ts)
  listing tmux sessions/windows/panes, pane capture, cross-session
  output search, and per-pane CPU/RAM/GPU metrics over pooled SSH
  connections. Read-only; reuses host-resolver, jump-host-chain,
  connection pool and RBAC access checks.
- tmux_session_tags table for per-user project tags on sessions.
- Frontend fullscreen app at ?view=tmux-monitor with host selector,
  session/window/pane tree, live pane preview (2s polling), search
  bar, metrics display, and tag editing.
- nginx location blocks for /tmux_monitor in Docker deployments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): live streaming backend and shell integration

- WebSocket service on port 30011 (tmux-monitor-live.ts) bridging tmux
  control mode (-C attach-session) to the browser: subscribe per pane,
  decoded %output events streamed as JSON, structure-change and detach
  notifications. Pure control-mode parser with 17 unit tests.
- Register tmux_monitor as a singleton shell tab: AppRail entry below
  Network Graph, global Command Palette action, and per-host palette
  action (gated on enableTerminal) that opens with the host preselected.
- nginx WebSocket location /tmux_monitor/live/ for Docker deployments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): xterm live preview and UX polish

- Pane preview is now a read-only xterm.js terminal with ANSI colors
  (capture-pane -e via new ansi=1 param) fed by the live WebSocket;
  status chip shows live/connecting/polling and the original 2s REST
  polling remains as automatic fallback.
- useTmuxLive hook: survives React StrictMode remounts (unmounted flag
  reset on mount), force-closes sockets stuck in CONNECTING after 8s so
  retry/fallback always engages, uses import.meta.env.DEV for dev
  detection.
- UX: skeleton loading, error state with retry, actionable empty states,
  persisted host/expanded-session selection, "/" and Escape shortcuts,
  search match highlighting, fresh relative timestamps.

Verified end-to-end in Chrome against a local backend: live chip goes
green and pane output streams in real time over the control-mode bridge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(tmux-monitor): replace control-mode bridge with native PTY attach

The pane preview now embeds the real Terminal component and attaches to
the selected session through the existing terminal WebSocket (new
tmuxAttachSession handshake field in ssh/terminal.ts), making the
preview fully interactive — typing, mouse scrolling and tmux copy-mode
behave exactly like a normal terminal tab. Pane clicks within a session
use the new /focus endpoint (select-window/select-pane) instead of
remounting the terminal.

This removes the entire custom live-streaming stack: the control-mode
WebSocket service on port 30011 (tmux-monitor-live.ts), the control
sequence parser and its tests, the useTmuxLive hook, the nginx
/tmux_monitor/live/ location blocks, and the now-unused live/polling
status chip locale keys.

Verified end-to-end against a dockerized SSH host (Alpine + tmux 3.6):
session tree, interactive preview with ANSI colors, cross-session
search with jump-to-pane, per-pane CPU/RAM metrics, session tags, and
attach-in-new-tab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): use printable field separator in tmux format strings

The unit separator control character (\x1f) used to delimit fields in
tmux -F output gets sanitized to "_" by tmux itself — tmux replaces
control characters (and, under non-UTF-8 locales, any multibyte
character) in expanded format output. On such hosts every list-sessions
line came back as a single unsplittable field, so the monitor showed
session names like "build_1781066459_1781066459_0" and windows/panes
failed to parse entirely.

Switch SEP to the printable ASCII token "<<TMX>>", which survives every
tmux version and locale. Reproduced and verified against tmux 3.6 on
Alpine (C locale) in a docker container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): create sessions and split panes from the monitor

- POST /tmux_monitor/:hostId/sessions creates a detached session
  (tmux new-session -d), starting the tmux server if needed. Session
  names are validated against a conservative charset on both ends
  (tmux forbids ":" and "."), and a duplicate name returns 409 with
  tmux's own message surfaced in the toast.
- POST /tmux_monitor/:hostId/split splits the window containing a pane
  (tmux split-window -h/-v), with the new pane starting in the source
  pane's working directory via -c '#{pane_current_path}'.
- UI: a "+" button next to the host selector opens a name popover
  (Enter or Create); the pane preview header gains split-right and
  split-down buttons. Splits appear instantly in the attached preview
  terminal and a silent overview reload updates the tree; new sessions
  are auto-expanded.

Command syntax verified against tmux 3.6 in a dockerized SSH host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): VSCode tmux-manager style tree with window actions

Restyle the session tree after the VSCode tmux manager view:

- Header toolbar: new-session (+) popover and refresh moved next to the
  panel title; the host selector row is back to a plain full-width
  select.
- Session rows gain hover actions — attach (play) and new window (+) —
  revealed with the opacity pattern used by docker's ContainerCard so
  they stay keyboard-focusable, with aria-labels on all icon buttons.
- Window rows are collapsible (chevron, "index: name" with a window
  icon); panes show "index: command" plus the pane's working directory
  in muted text with a full-path tooltip.
- New POST /tmux_monitor/:hostId/windows route runs tmux new-window
  against an exact-match ("=name") shell-escaped target; rejects names
  containing ":"/"."/newline and returns 404 for missing sessions.
- Pane preview header groups the split buttons apart from close with a
  separator.

Reviewed by SA/BA/UX subagent panel; keyboard accessibility and
aria-label findings applied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): rename/kill sessions, collapse-all, no-flash refresh

- Session rows gain a "…" menu with Rename (dialog, validated name) and
  Kill (destructive confirmation). Rename migrates saved tags to the
  new name and re-attaches an open preview; kill closes the preview and
  drops the session's tags. Both routes use exact-match ("=name")
  shell-escaped targets and map tmux's "can't find session" /
  "duplicate session" errors to 404/409.
- Header gains a VSCode-style collapse-all / expand-all toggle for the
  session tree.
- Refresh UX: manual refresh no longer blanks the tree into skeletons —
  the skeleton is reserved for a host's first load; refresh keeps the
  tree, spins the icon (header and toolbar) and reports failures with a
  toast instead of silently keeping stale data.

tmux command syntax verified against tmux 3.6 in the docker test host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): single-row sessions, overlay hover actions, tag dialog

UI cleanup after desktop testing showed the tree looked cluttered:

- Sessions are now a single row. The permanently visible tags row (a
  mostly empty line with a lone tag icon under every session) is gone:
  tags render as inline badges after the name (max 2, then "+n" with a
  full-list tooltip) and editing moved to the "…" menu, which opens a
  dialog like rename.
- Session metrics merged into the right-hand meta label
  ("just now · 0% · 7 MB"). Hover actions (attach, new window, …) are
  stacked over that label in a grid cell with an opacity cross-fade, so
  the row never shifts and the buttons remain keyboard-focusable
  (focus-within reveals them).
- Dropdown items no longer wrap: short labels (Rename / Edit tags /
  Kill), whitespace-nowrap, min-w-36, align="end".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): kill pane; fix session actions in the desktop app

- Kill pane: hover ✕ on pane rows and a trash button in the preview
  header, both behind a confirmation dialog (kill-pane terminates the
  pane's process; the last pane closes its window). New
  POST /tmux_monitor/:hostId/kill-pane route with pane-id validation.
- Fix attach (▷) doing nothing in the Electron build: window.open of
  internal file:// URLs is denied by the window-open handler, so on
  desktop the button now attaches inline — expands the session and
  selects its active pane in the embedded terminal. Browsers keep the
  open-in-new-tab behavior.
- Enlarge the hover-action hit areas (-m-1 p-1) so clicks land on the
  buttons instead of toggling the session row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): session hover actions were unclickable

The meta label (time · cpu · mem) fades to opacity-0 on hover, which
turns it into a stacking context painted above its plain-flow sibling —
the invisible label sat on top of the attach/new-window/menu buttons
and swallowed their clicks, so clicking them toggled the session row
instead. Give the actions overlay z-10 and make the label
pointer-events-none.

Reproduced and verified in Chrome with elementFromPoint before/after;
create → rename → kill all work end-to-end through the UI now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): resizable session tree panel

The tree panel was fixed at w-72; long session names and pane paths had
no room. Its right edge is now a drag handle (200–520px, same pattern
and styling as the AppShell host sidebar), double-click resets to the
default width, and the width persists in localStorage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): split buttons on pane rows

Pane rows' hover actions gain split-right and split-down next to the
kill button, so a pane can be split straight from the tree. splitPane
now takes the target pane id instead of implicitly using the selected
pane; the preview header passes its own pane through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): repair garbled preview after layout changes

External split / kill-pane / new-window actions can leave the attached
preview client with stale borders (the redraw races the xterm size).
Two-part fix:

- After any layout-changing action the monitor nudges the embedded
  terminal (fit + notifyResize + refresh) — the PTY resize forces tmux
  to fully redraw the client.
- The preview header gains a re-attach button that remounts the
  terminal with a fresh PTY as a one-click rescue when the view is
  garbled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): kill window, compact mode, session status tooltip

- Kill window: hover ✕ on window rows behind a confirmation dialog
  (kills every pane; the last window ends the session). New
  POST /tmux_monitor/:hostId/kill-window route with exact-match target
  and 404 mapping.
- Narrow tree panel (<280px) hides the inline "time · cpu · mem" label,
  which used to crush long session names into "bui…".
- Hovering a session row now shows a status board tooltip (attached
  state, activity, window/pane counts, CPU/RAM/GPU, tags) — the numbers
  stay reachable in compact mode and richer in normal mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): keep preview attached after killing the viewed pane

Killing the selected pane (or its window) dumped the user back to the
"select a pane" empty state even though the session was still alive and
the attached terminal had already focused a sibling pane. After the
kill the monitor now refetches the overview and selects the session's
new active pane, so the preview stays on the same attach connection
(same session key → no remount); the empty state only appears when the
whole session died with the pane.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): require execute permission, audit destructive actions, persist tag mutations

- Mutating endpoints (kill/kill-window/kill-pane/rename/split/focus,
  create session/window) now require "execute" host access like docker;
  read-only shared hosts could previously kill sessions and spawn shells
- Log kill/rename actions to the audit log (same pattern as host.ts)
- Trigger a DB save after tag updates in rename/kill handlers; the dirty
  flag was never set, so a crash could lose or resurrect tags
- Add host_id -> ssh_data FK with ON DELETE CASCADE to tmux_session_tags
  (drizzle schema + SQL migration), with a rebuild migration for
  pre-release tables created without the FK

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): stop polling and key handling while the tab is hidden, guard stale host responses

- Pass isVisible into TmuxMonitor (like DockerManager/HostMetricsTab);
  the global "/"+Escape keydown listener and both SSH polling loops now
  pause while the tab is mounted but hidden
- Split the overview polling interval out of the host-change effect so
  resuming a tab does not reset state or flash the skeleton
- Ignore overview responses for a host the user has switched away from;
  a slow response could overwrite the selected host's tree
- Format SearchResults/SessionTree/TmuxMonitorApp/SidebarTree with
  prettier (pr-check was failing on these)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): show an empty state when no SSH hosts exist

With zero hosts the host dropdown opened an empty popover and the
preview area showed the unrelated "select a pane" hint. Disable the
select (its placeholder now says no hosts are available) and show a
central no-hosts message with a pointer to the Host Manager.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): refresh the host list when hosts change elsewhere

The list was fetched once on mount, but the tab stays mounted forever,
so adding the first host in Host Manager left the monitor stuck on
"no hosts" until the tab was closed and reopened. Listen to the
app-wide termix:hosts-changed event (same as AppShell/HostManager),
keep the current selection when still valid, auto-select the first
host when one appears, and clear the session tree when the selected
host is deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): keep pane/window actions reachable on a narrow tree

Pane rows kept the split/kill buttons and the "index: command" label at
fixed width, so dragging the panel toward the 200px minimum pushed the
buttons past the right edge; Radix ScrollArea's display:table viewport
wrapper made it worse by stretching every row to the widest pane path,
clipping the window kill button too.

Stack the cpu label and the hover actions in one grid cell (the same
overlay pattern as the session rows), let the command and path labels
truncate, and force the viewport wrapper back to display:block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): reskin the session status tooltip as a popover card

The base TooltipContent is the inverted one-line chip (bg-foreground,
sharp corners, arrow), so the multi-row session board inherited a light
surface that washed out its muted-foreground labels and clashed with
every other floating card in the app. Restyle this one instance with the
popover surface tokens and hide the chip arrow (important is needed
because Radix sets display on the arrow svg inline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): disable new-session and search when tmux is missing

On a host without tmux the + button still opened the create popover
(submitting just surfaced the backend TMUX_NOT_INSTALLED toast) and
search silently returned "0 matches". Gate both on overview.available
like the neighboring refresh/expand buttons; the search input carries
the unavailable message as its tooltip.

Verified against a real no-tmux host (alpine+sshd container): both
controls disable on it and stay enabled on a host with tmux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): restyle floating surfaces to match the square UI

Review feedback on #896: the host selector, the new-session popover,
the session status tooltip and the three kill confirmations rendered
with the stock rounded shadcn look while the rest of the app moved to
square corners, ring borders and blurred dialog overlays. Convert the
kill confirmations from AlertDialog to the already-restyled Dialog and
override the remaining surfaces with rounded-none + ring-border.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(host-editor): format the tailscale device options with project prettier

The block arrived unformatted in the dev-2.4.0 merge; committing the
reformat separately keeps the tmux-monitor commits free of unrelated
churn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tmux-monitor): per-host opt-in flag, disabled by default

Review feedback on #896: the tmux actions appeared on every SSH host's
sidebar entry even though most users do not use tmux. Add an
enable_tmux_monitor column (default off) mirroring the enableDocker
plumbing, expose it as an "Enable Tmux Monitor" toggle in the host
editor's Terminal tab, and gate every per-host tmux surface on it:
the sidebar hover action and copy-URL menu item, the command palette
entry, the monitor's host selector, and the tmux-monitor API itself
(refusing like docker.ts does when the feature is off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): drop the redundant navbar rail entry

The monitor is per-host opt-in, so the global rail item opened a
host-less view. Hosts with enableTmuxMonitor still expose it via the
host action in the sidebar tree and the command palette.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tmux-monitor): fill the status popover card width

TooltipContent is an inline-flex container, so the inner block sized
to its natural content width and left a dead gutter on the right of
the w-56 card. Stretch it with w-full so the values align to the edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: remove unused dropdown, seperate 3 line menu, and fix a few small UI bugs

---------

Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix: add i18n for tmux

* feat: continue improving terminal highlighint with more options and more consistency

* feat: add per host configuration of history tracking with overarching admin toggle

* feat: add storage preference to user profile settings (local or DB)

* chore: update release notes

* fix: return user password reset code to logs

* fix: missing tailscale errors and i18n

* fix: sso/browser preference fixes

* fix: remove SSL_ENABLED references

* feat: add filter/sort to credentials list and fixed some JSDoc comments

* chore: update release notes

* chore: update readmes

* chore: update readmes and add docs links

* feat: continue improving SSO system

* fix: host metrics bugs

* fix: session logging api endpoint fail

* chore: make electron.yml auto deliver to macos app store

* chore: fix linting issues

* chore: fix linting issues

* chore: lint, format, and bump version to 2.4.0

* chore: crowdin using incorrect api key name

* chore: sync Crowdin translations for 2.4.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: XtraLarge <eMail@WilliWerres.de>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: verybadsoldier <364469+verybadsoldier@users.noreply.github.com>
Co-authored-by: i/o agent <273482092+claw-io@users.noreply.github.com>
Co-authored-by: Félix MARQUET <72651575+BreizhHardware@users.noreply.github.com>
Co-authored-by: Rabindra Kumar Meher <rabindrameher116@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jose Tenggren <kf16el42@students.muetkhp.edu.pk>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Kang <78076796+khengyun@users.noreply.github.com>
2026-06-16 15:59:53 -05:00
Luke Gustafson 580c284065 Update README.md 2026-06-12 15:16:31 -05:00
LukeGus 2d5da0439e Revert "fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883)"
This reverts commit 4d04559ca5.
2026-06-07 02:57:08 -05:00
i/o agent 4d04559ca5 fix(docker): use resolveTermixThemeColors for Docker console terminal background (#883) 2026-06-06 15:01:53 -04:00
LukeGus f0f3e0b063 chore: bump Homebrew cask to 2.3.2 2026-06-04 20:05:47 +00:00
Luke Gustafson c3282b5dca New Crowdin updates (#876)
* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Afrikaans)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Norwegian)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Serbian (Cyrillic))

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Hindi)

[ci skip]
2026-06-04 14:24:54 -05:00
LukeGus dccaae07ab chore: fix lint warnings and run formating 2026-06-04 14:23:53 -05:00
52f4e51ae0 v2.3.2 (#874)
* fix: patch critical security vulnerabilities (GHSA-5fqh, GHSA-ccm8, GHSA-wqfw, GHSA-xmjh)

- Remove passwordHash from /users/list API response
- Require both password and TOTP code for MFA-critical operations
- Restrict tunnel kill commands to tunnelMarker-only matching
- Add session ownership middleware for file manager endpoints

* fix: allow navigating away from split-view to non-pane tabs

Show the normal view container on top of the split view when the active
tab is not assigned to any pane, so users can switch to dashboard or
other tabs while split mode is active.

Closes #739

* fix: add inline quick-action buttons on host name row

Show Terminal, Files, RDP, and VNC shortcut icons on the host name row
on hover, so users can launch connections with a single click without
expanding the full action tray.

Closes #736

* fix: restore SSH keepalive interval to 30s to prevent random disconnects

Revert keepalive defaults from 60s/5 to 30s/3 across terminal, tunnel,
and server-stats SSH connections. The 60s interval introduced in 2.3.0
causes firewalls and NAT devices to drop idle connections before the
next keepalive probe.

Closes #733

* fix: apply guacamole-lite protocol patch in Docker builds

The Dockerfile uses --ignore-scripts which skips the postinstall hook
that patches guacamole-lite for guacd 1.6.0 protocol VERSION_1_5_0.
Without this patch, the timezone handshake instruction is not sent for
protocol versions above 1.1.0, causing VNC connections to fail
immediately on connect.

Closes #734

* fix: show correct icons for network interface types

Detect interface type from name pattern and show appropriate icons:
WiFi for wlan/wl*, Ethernet (Cable) for eth/en*, Container for
docker/bridge/virtual, generic Network for others.

Closes #720

* fix: resolve sudo password for shared host users

The password endpoint required hosts.userId to match the requesting
user, which fails for shared hosts. Now falls back to decrypting with
the owner's key when the requesting user doesn't own the host.

Closes #717

* fix: use jump hosts for online status check and metrics collection

Status polling now pings the first jump host instead of the unreachable
target when jump hosts are configured. The /metrics/start endpoint now
tunnels through the jump host chain to reach the target host.

Closes #716

* fix: broaden sudo prompt detection for newer distros

Add patterns for 'password for <user>:' and bare 'Password:' prompts
in addition to the existing [sudo] and sudo: patterns. Covers Ubuntu
26.04 and other distros that use different sudo prompt formats.

Closes #718

* fix: recalculate terminal layout after web fonts load

xterm.js measures character widths at open() time. If custom fonts
haven't loaded yet, measurements use the fallback font and spacing
becomes incorrect. Now refresh and re-fit the terminal once
document.fonts.ready resolves.

Closes #710

* fix: improve terminal cwd detection and initial directory command

Remove '&& pwd' from initial directory command — the shell prompt
shows the new directory naturally. Fixes PowerShell 5.1 which doesn't
support '&&' as a statement separator.

Prepend Ctrl+U to get_cwd command to clear any pending input before
injecting the cwd probe, reducing interference with foreground programs.

Closes #713, #714

* fix: decode base64 file content as UTF-8 in file manager

Replace bare atob() with TextDecoder('utf-8') for base64 content
decoding. atob() only handles Latin-1, so multi-byte UTF-8 characters
like 'é' were decoded as 'é'.

Closes #719

* fix: normalize lazy import default exports for iOS compatibility

Wrap all lazy() imports with explicit .then(m => ({ default: m.default }))
to ensure consistent module resolution across platforms. iOS Safari/WebView
may handle bare lazy(() => import(...)) differently, returning the module
object instead of extracting the default export.

Closes #721

* fix: prevent RDP display from snapping back after container resize

Remove immediate rescaleDisplay() from ResizeObserver callback. The
display.onresize event already triggers rescaling when the RDP server
responds with the new resolution. Calling rescaleDisplay before the
server responds uses stale display dimensions, causing the bottom of
the screen to be truncated.

Closes #725

* fix: add portal Desktop DBus permission for Flatpak URL opening

Flatpak sandbox blocks window.open() without the portal permission,
causing terminal link clicks to open about:blank. Add talk-name for
org.freedesktop.portal.Desktop to enable xdg-desktop-portal URL
handling.

Closes #704

* chore: remove unused code and fix PR checks (#851)

* chore: remove unused frontend code

* chore: prune unused theme exports

* ci: fix pr check failures

* chore: reduce lint warnings

* feat(oidc): expose admin_group via OIDC_ADMIN_GROUP env var (#828)

The admin-group OIDC sync added in 2.3.0 (#782) reads `config.admin_group`
to sync the user's admin flag from OIDC group membership on each login.
That field is only populated when the OIDC config is stored in the
in-app DB — `getOIDCConfigFromEnv()` does not expose it, so deployments
using the env-var config path (declarative IaC: Helm/Compose/Puppet)
cannot enable the feature without abandoning env vars and pasting the
client_secret into the admin UI.

Add `admin_group: process.env.OIDC_ADMIN_GROUP || ""` to the env-config
return type and object. Backward compatible: when unset, the existing
`if (config.admin_group)` guard at users.ts:1336 keeps the sync block
skipped, matching today's behavior.

* chore: reduce explicit-any warnings

* chore: reduce more explicit-any warnings

* chore: reduce lint warnings

* chore: silence intentional hook dependency warnings

* chore: clean dependency tooling

* chore: narrow frontend tsconfig scope

* chore: reduce type assertion debt

* refactor: split host manager components

* refactor: split host editor sections

* refactor: split api client modules

* refactor: split more api clients

* refactor: split user settings api clients

* refactor: split tab and history api clients

* refactor: split tunnel api clients

* refactor: split server stats api client

* refactor: split file manager data api

* refactor: split ssh file operations api

* refactor: split host editor general tab

* refactor: split host editor guacamole tabs

* refactor: split ssh host management api

* refactor: split admin general settings sections

* refactor: split admin database section

* refactor: split admin management sections

* refactor: split admin keys and dialogs

* refactor: split system status api clients

* refactor: split user route helpers

* refactor: split host route helpers

* refactor: split file manager ssh helpers

* refactor: split file manager session helpers

* refactor: split file manager listing routes

* refactor: split host opkssh routes

* refactor: split file manager content routes

* refactor: split user api key routes

* refactor: split host folder routes

* refactor: split user settings routes

* refactor: split user totp routes

* refactor: split host file manager bookmark routes

* refactor: split file manager operation routes

* refactor: split server stats settings routes

* refactor: split user session routes

* refactor: split host command history routes

* refactor: split server stats viewer routes

* refactor: split docker container routes

* refactor: split user oidc account routes

* refactor: split host autostart routes

* refactor: split host internal routes

* refactor: split host network routes

* refactor: split user password reset routes

* refactor: split user admin routes

* refactor: split user data access routes

* refactor: split credential key routes

* refactor: split credential deploy routes

* refactor: split host bulk routes

* refactor: split server stats connection helpers

* refactor: split tunnel helpers

* refactor: split file manager action routes

* refactor: split terminal auth helpers

* refactor: split terminal jump host helpers

* refactor: split tunnel relay helpers

* refactor: split tunnel socks relay helpers

* refactor: split tunnel c2s relay handlers

* refactor: split server stats session helpers

* refactor: split terminal presentation helpers

* refactor: split file manager presentation helpers

* refactor: split file manager toolbar

* fix(guacamole-lite): send name instruction for protocol >= 1.3.0

The Guacamole protocol added the `name` handshake instruction in 1.3.0
(an optional human-readable identifier for the joining user). guacd 1.6.0
began requiring it during the VNC handshake even when negotiating older
protocol versions, causing connections to silently drop right after the
"User joined" log line with no client-visible error.

This patch extends scripts/patch-guacamole-lite.cjs with a third
idempotent string-replacement that injects the `name` instruction send
when guacamole-lite has negotiated protocol VERSION_1_3_0 or VERSION_1_5_0.

Verified end-to-end: guacd debug logs now show `Processing instruction:
name` and `Client is using protocol version "VERSION_1_5_0"` (previously
stuck at VERSION_1_1_0). VNC session connects successfully against
guacd 1.5.5 / macOS Tahoe target.

Related: Termix-SSH/Support#567, #734

* fix: resolve recent support bugs

* fix(admin): wire up OIDC-to-password link dialog submit + visibility

The admin user-management UI already shipped a link icon and a "Link
Account" dialog, but two things blocked the flow:

1. The submit button had no onClick handler and the username input was
   uncontrolled (no value/onChange). Clicking "Link Accounts" was a
   no-op — no network request, no console error, no toast.
2. The link icon's visibility condition was `user.isOidc &&
   !user.passwordHash`, which hid the button on OIDC users that had
   been auto-provisioned with a passwordHash. Termix's OIDC provisioning
   sets a passwordHash by default, so the button was hidden on virtually
   every OIDC-provisioned user.

This change:
- Adds `linkOIDCToPasswordAccount` to the imports from `@/main-axios`.
- Adds two pieces of dialog state: `linkAccountTargetUsername` and
  `linkAccountSubmitting`.
- Makes the dialog's Input field a controlled component.
- Wires the submit Button's onClick to call `linkOIDCToPasswordAccount`,
  emit success/error toasts, refresh the local user list, and close
  the dialog.
- Loosens the visibility condition to `user.isOidc` (the backend
  handler already enforces all integrity checks).
- Adds `linkAccountSuccess`, `linkAccountFailed`, and
  `linkAccountInProgress` translation keys to `en.json`.

Verified locally: full Docker build via docker/Dockerfile passes;
`tsc --noEmit` is clean; `prettier --check .` is clean; ESLint produces
the same warning count as upstream (16 pre-existing `any`-type warnings,
0 errors).

* fix: support native oidc callbacks (#856)

* docs: add cloudflare tunnel guidance (#857)

* fix: sync appearance preferences (#858)

* fix: pass through terminal tab completion (#859)

* fix: resolve terminal jump hosts server-side (#860)

* fix(electron): auto-allow SSL certificates for private network hosts (#861)

Add private network IP detection (RFC 1918, link-local, loopback, IPv6
ULA) to the Electron certificate-error handler so that connections to
local/private servers like 192.168.x.x bypass SSL validation
automatically. Also add an explicit "Allow invalid certificate" toggle
in the server config UI for public HTTPS servers with self-signed certs.

* fix: restore host password copy actions (#862)

* feat: support single-host direct tunnels (ssh -L style) (#863)

Add direct tunnel mode that uses a single SSH host for port forwarding,
matching the behavior of ssh -L / ssh -R / ssh -D without requiring a
second endpoint host in the Termix database. The Termix server creates a
local TCP listener and forwards through the SSH channel directly.

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* Merge commit from fork

* fix: backend build errors (Type)

* fix: mobile auth failing to login with webview

* fix: mobile app geting incorrectly sent auth token

* feat: commit existing frontend/backend e2e/unit tests (skipped tests containing private info like OIDC and real server testing)

* feat: host-to-host file transfer via server relay

* feat: removed host management from command palette, fixed command palette opening wrong protocol, export/import failing for ssh key hosts, docker ssh2 native crypto not compiled, persisted terminal tabs attempt SSh on RDP hosts after migration, improved layout for click to expand hosts, show ip/username without having to hover over hosts

* fix: credentials not indexing into host manager until refresh

* feat: update credentials lists to match hosts list UI/UX

* feat: add rename folder UI

* feat: improve transfer to host UI/UX

* chore: increment ver

* feat: improve transfer to host UI

* feat: implement initial auto release system

---------

Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: nicodarge <43711429+nicodarge@users.noreply.github.com>
Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com>
Co-authored-by: luc <luc_cook@hotmail.co.uk>
2026-06-04 14:16:53 -05:00
LukeGus da79b01db4 chore: update cask 2026-05-28 22:45:53 -05:00
LukeGus bc386b1247 fix: uuid crypto error when creating a tab on http based setups 2026-05-28 22:29:30 -05:00
Luke Gustafson dd008e4d6b New Crowdin updates (#836)
* New translations en.json (Romanian)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Afrikaans)

[ci skip]

* New translations en.json (Arabic)

[ci skip]

* New translations en.json (Bulgarian)

[ci skip]

* New translations en.json (Catalan)

[ci skip]

* New translations en.json (Czech)

[ci skip]

* New translations en.json (Danish)

[ci skip]

* New translations en.json (German)

[ci skip]

* New translations en.json (Greek)

[ci skip]

* New translations en.json (Finnish)

[ci skip]

* New translations en.json (Hebrew)

[ci skip]

* New translations en.json (Hungarian)

[ci skip]

* New translations en.json (Italian)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Korean)

[ci skip]

* New translations en.json (Dutch)

[ci skip]

* New translations en.json (Norwegian)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]

* New translations en.json (Serbian (Cyrillic))

[ci skip]

* New translations en.json (Swedish)

[ci skip]

* New translations en.json (Turkish)

[ci skip]

* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (Chinese Traditional)

[ci skip]

* New translations en.json (Vietnamese)

[ci skip]

* New translations en.json (Portuguese, Brazilian)

[ci skip]

* New translations en.json (Indonesian)

[ci skip]

* New translations en.json (Bengali)

[ci skip]

* New translations en.json (Thai)

[ci skip]

* New translations en.json (Hindi)

[ci skip]
2026-05-28 22:29:30 -05:00
Luke Gustafson 7370e8f3df v2.3.1 (#833)
* feat: add host list sort

* feat: fixed rdp truncating (taskbar invisible unless resizing window) and improved split screen system

* feat: revamp conneciton persistance system to save to backend with a new connections panel to restore old connections and view current ones. Also added new user profile toggle to reopen all tabs (saves and loads from backend). Added user profile toggle for host tray click vs hover.

* feat: added WOL button, added proper use of BASE_PATH, toggles/buttons in admin/user profile now are always visible regardless of sidebar width, duplicating hosts not adding jumphost/socks5, keepalive internal not mulitplying into seconds causing a keepalive error, and finally guacamole giving 1_0_0 and 1_1_0 errors

* feat: add filter host button, improve alert system UI, save sidebar width to localstorage, and fix host toolbar row overflow (add host going off screen on small sidebar width)

* feat: add pin rail toggle, fix command pallete toggle not working, fixed command pallete toggling when typing in a field, made file manager not uppercase, host manager custom ports not loading, guacd hosts made on >=2.2.1, fixed host tags toggling, added reorder snippet sfeature, made snippet folder clllapse and require confimration toggle work, removed file manager color toggle, and fixed macos not displaying GUI until switching to another app and coming back, and jump host servers failing.

* feat: use blacksmith caching for docker compile and improve keepalive system for ssh to all match the same implementation and use the data from the host config instead of a predefined value

* feat: reset host manager state if the form is left and remove file manager color logic from the removed toggle

* feat: update electron version check to use new ui

* feat: improve duplication system to proplery map all fields
2026-05-28 22:29:29 -05:00
LukeGus b5ab1479ce chore: update casks 2026-05-28 22:29:29 -05:00
LukeGus c060d668e0 chore: update cask 2026-05-28 22:29:29 -05:00
LukeGus 5777351145 v2.3.0 2026-05-28 22:29:20 -05:00
LukeGus 33dcde0827 feat: initial ui redesign from demo 2026-05-13 01:29:43 -05:00
LukeGus eaa758effe fix: rdp fails with readText undefined error 2026-05-13 01:06:34 -05:00
LukeGus 557a789a6c chore: update cask 2026-05-13 00:41:18 -05:00
Luke Gustafson d2e13cdfd8 fix: desktop local server failure (#756)
* fix: remote desktop blank screen

* fix: desktop local server failure
2026-05-12 23:54:51 -05:00
Luke Gustafson dc79d170b6 fix: remote desktop blank screen (#755) 2026-05-12 23:50:25 -05:00
LukeGus 604de8a683 fix: remote desktop blank screen 2026-05-12 22:50:51 -05:00
LukeGus 3b465a0747 chore: update casks 2026-05-12 22:33:36 -05:00
LukeGus f803fc83a4 chore: update casks 2026-05-12 22:33:28 -05:00
1260 changed files with 359875 additions and 196475 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ README.md
CONTRIBUTING.md
LICENSE
repo-images/
docs/repo-images/
uploads/
+1 -1
View File
@@ -5,4 +5,4 @@ contact_links:
about: Report any feature requests or bugs in the support center
- name: Discord
url: https://discord.gg/jVQGdvHDrf
about: Official Termix Discord server for general discussion and quick support
about: Official Termix Discord server for general discussion (not recommended for support)
+55 -4
View File
@@ -1,9 +1,19 @@
version: 2
updates:
# npm dependencies (single root package.json, no workspaces)
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
day: "monday"
open-pull-requests-limit: 15
labels:
- "dependencies"
- "npm"
commit-message:
prefix: "chore"
prefix-development: "chore"
include: "scope"
groups:
dev-patch-updates:
dependency-type: "development"
@@ -21,20 +31,61 @@ updates:
dependency-type: "production"
update-types:
- "minor"
# Major bumps grouped so they land as a single reviewable PR instead of
# one-per-package noise. These often need manual follow-up (Electron,
# React, Vite, Tailwind, Express 5, etc.).
major-updates:
update-types:
- "major"
ignore:
# typescript-eslint declares `typescript: >=4.8.4 <6.1.0`, and TypeScript 7
# removed `ts.Extension`, which @typescript-eslint/typescript-estree reads
# at import time. Bumping to 7 makes `eslint .` fail to load its own config,
# so `npm run lint` cannot run at all. Drop this once typescript-eslint
# supports TypeScript 7.
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
# Docker base images (docker/Dockerfile + docker-compose / compose-dev)
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "daily"
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "docker"
commit-message:
prefix: "chore"
include: "scope"
groups:
patch-updates:
docker-patch-updates:
update-types:
- "patch"
minor-updates:
docker-minor-updates:
update-types:
- "minor"
docker-major-updates:
update-types:
- "major"
# GitHub Actions used across the workflows in .github/workflows
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions:
update-types:
- "patch"
- "minor"
- "major"
+1
View File
@@ -0,0 +1 @@
custom: https://donate.termix.site/
+179
View File
@@ -0,0 +1,179 @@
name: Weekly Beta Release
on:
schedule:
- cron: "15 6 * * 1"
workflow_dispatch:
inputs:
dry_run:
description: "Build and test but do not push images, upload installers, or publish a release"
required: false
default: false
type: boolean
permissions:
contents: write
jobs:
prep:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
dev_branch: ${{ steps.dev.outputs.branch }}
beta_version: ${{ steps.dev.outputs.beta_version }}
sha: ${{ steps.dev.outputs.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Resolve newest dev branch and compute beta version
id: dev
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
REFS=$(gh api "repos/${{ github.repository }}/branches" --paginate -q '.[].name')
if DEV_BRANCH=$(printf '%s\n' "$REFS" | node scripts/latest-dev-branch.cjs 2>/dev/null); then
echo "Newest dev branch: $DEV_BRANCH"
else
echo "No dev-X.Y.Z branch open; nothing to snapshot for this week's beta."
echo "branch=" >> "$GITHUB_OUTPUT"
exit 0
fi
BASE_VERSION=$(node scripts/parse-dev-branch.cjs "$DEV_BRANCH")
BETA_VERSION="${BASE_VERSION}-beta.$(date -u +%Y%m%d)"
SHA=$(gh api "repos/${{ github.repository }}/branches/$DEV_BRANCH" -q .commit.sha)
echo "Beta version: $BETA_VERSION"
echo "branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
verify:
needs: [prep]
if: ${{ needs.prep.outputs.dev_branch != '' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npx eslint .
- name: Run Prettier check
run: npx prettier --check .
- name: Type check
run: npx tsc --noEmit
- name: Run unit tests
run: npm run test
- name: Build
run: npm run build
create-release:
needs: [prep, verify]
if: ${{ needs.prep.outputs.dev_branch != '' && inputs.dry_run != true }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 0
- name: Resolve previous beta commit
id: prev
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
PREV_SHA=$(gh release view beta --repo ${{ github.repository }} --json targetCommitish -q .targetCommitish 2>/dev/null || true)
if [ -n "$PREV_SHA" ] && git cat-file -e "$PREV_SHA" 2>/dev/null && git merge-base --is-ancestor "$PREV_SHA" "${{ needs.prep.outputs.sha }}"; then
echo "sha=$PREV_SHA" >> "$GITHUB_OUTPUT"
else
echo "sha=" >> "$GITHUB_OUTPUT"
fi
- name: Generate rolling beta release notes
run: |
if [ -n "${{ steps.prev.outputs.sha }}" ]; then
CHANGES=$(git log --oneline --no-merges "${{ steps.prev.outputs.sha }}..${{ needs.prep.outputs.sha }}" -- . ':!package-lock.json' | sed 's/^/- /')
fi
if [ -z "$CHANGES" ]; then
CHANGES="- No new commits since the last beta."
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 }}\`.
**Snapshot of:** \`${{ needs.prep.outputs.dev_branch }}\` @ \`${{ needs.prep.outputs.sha }}\`
**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.
**Built:** $(date -u +"%Y-%m-%d %H:%M UTC")
### Changes since last beta
$CHANGES
EOF
- name: Create or update rolling beta release
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
TAG="beta"
TITLE="Beta (rolling) - ${{ needs.prep.outputs.beta_version }}"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
gh release edit "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_RELEASE_BODY.md \
--prerelease --target "${{ needs.prep.outputs.sha }}"
else
gh release create "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_RELEASE_BODY.md \
--prerelease --target "${{ needs.prep.outputs.sha }}"
fi
docker:
needs: [prep, verify, create-release]
if: ${{ always() && needs.prep.outputs.dev_branch != '' && needs.verify.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.beta_version }}
build_type: Beta
dry_run: ${{ inputs.dry_run == true }}
source_ref: ${{ needs.prep.outputs.sha }}
secrets: inherit
electron-release:
needs: [prep, verify, create-release]
if: ${{ always() && needs.prep.outputs.dev_branch != '' && inputs.dry_run != true && needs.create-release.result == 'success' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: release
release_tag: beta
version_override: ${{ needs.prep.outputs.beta_version }}
source_ref: ${{ needs.prep.outputs.sha }}
secrets: inherit
+83
View File
@@ -0,0 +1,83 @@
name: Crowdin Sync
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
inputs:
branch:
description: "Branch to sync translations into"
required: false
type: string
permissions:
contents: write
jobs:
crowdin:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Resolve target branch
id: branch
run: |
BRANCH="${{ inputs.branch }}"
if [ -z "$BRANCH" ]; then
BRANCH="${{ github.event.repository.default_branch }}"
fi
echo "name=$BRANCH" >> "$GITHUB_OUTPUT"
- name: Checkout branch
uses: actions/checkout@v7
with:
ref: ${{ steps.branch.outputs.name }}
fetch-depth: 0
token: ${{ secrets.GHCR_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Upload sources to Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Machine pre-translate untranslated strings
env:
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
run: node scripts/crowdin-pretranslate.cjs
- name: Download translations from Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Commit translations
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git add src/ui/locales/translated
if git diff --cached --quiet; then
echo "No translation changes to commit."
exit 0
fi
git commit -m "chore: sync Crowdin translations"
git push origin HEAD:"${{ steps.branch.outputs.name }}"
+157
View File
@@ -0,0 +1,157 @@
name: Retarget and Merge Dependabot PRs
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
retarget-and-merge:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Resolve newest dev branch
id: dev
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
REFS=$(gh api "repos/${{ github.repository }}/branches" --paginate -q '.[].name')
# The helper exits non-zero when no dev-X.Y.Z branch exists; treat that
# as "nothing to do" rather than a workflow failure.
if DEV_BRANCH=$(printf '%s\n' "$REFS" | node scripts/latest-dev-branch.cjs 2>/dev/null); then
echo "Newest dev branch: $DEV_BRANCH"
echo "branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
else
echo "No dev-X.Y.Z branch open; nothing to retarget."
echo "branch=" >> "$GITHUB_OUTPUT"
fi
- name: Retarget and merge Dependabot PRs
if: ${{ steps.dev.outputs.branch != '' }}
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
DEV_BRANCH: ${{ steps.dev.outputs.branch }}
REPO: ${{ github.repository }}
run: |
set -uo pipefail
CONFLICT_LABEL="dependabot-rebase-requested"
# Ensure the bookkeeping label exists (no-op if it already does).
gh label create "$CONFLICT_LABEL" --repo "$REPO" \
--color "D93F0B" --description "Retarget workflow asked Dependabot to rebase a conflicting PR" \
2>/dev/null || true
# True if the PR already carries the conflict label.
has_conflict_label() {
gh pr view "$1" --repo "$REPO" --json labels \
-q '.labels[].name' | grep -qx "$CONFLICT_LABEL"
}
# Wait until GitHub has a definite mergeable verdict for a PR (it
# returns UNKNOWN while recomputing after a base change or a push).
# Echoes "<mergeable> <mergeStateStatus>".
wait_for_verdict() {
local pr="$1" mergeable state
for _ in $(seq 1 30); do
read -r mergeable state < <(gh pr view "$pr" --repo "$REPO" \
--json mergeable,mergeStateStatus \
-q '.mergeable + " " + .mergeStateStatus')
if [ "$mergeable" != "UNKNOWN" ] && [ "$state" != "UNKNOWN" ]; then
echo "$mergeable $state"
return 0
fi
sleep 20
done
echo "$mergeable $state"
}
# Phase 1: retarget every open Dependabot PR from main onto the dev
# branch. This kicks off a Dependabot rebase for each.
PR_NUMBERS=$(gh pr list --repo "$REPO" \
--author "app/dependabot" \
--base main \
--state open \
--json number -q '.[].number')
# Pick up PRs already sitting on the dev branch from a previous run too.
PR_NUMBERS="$PR_NUMBERS $(gh pr list --repo "$REPO" \
--author "app/dependabot" \
--base "$DEV_BRANCH" \
--state open \
--json number -q '.[].number')"
PR_NUMBERS=$(printf '%s\n' $PR_NUMBERS | sort -un)
if [ -z "$PR_NUMBERS" ]; then
echo "No open Dependabot PRs to process."
exit 0
fi
for PR in $PR_NUMBERS; do
BASE=$(gh pr view "$PR" --repo "$REPO" --json baseRefName -q .baseRefName)
if [ "$BASE" != "$DEV_BRANCH" ]; then
echo "Retargeting PR #$PR ($BASE -> $DEV_BRANCH)"
gh pr edit "$PR" --repo "$REPO" --base "$DEV_BRANCH"
fi
done
# Phase 2: merge one at a time. Each merge can make the remaining npm
# PRs stale, so re-check immediately before merging and rebase stragglers.
for PR in $PR_NUMBERS; do
echo "::group::PR #$PR"
read -r MERGEABLE STATE < <(wait_for_verdict "$PR")
echo " mergeable=$MERGEABLE mergeStateStatus=$STATE"
# BEHIND = clean but needs the latest base; ask Dependabot to rebase
# and skip for now (next run merges it once it is up to date).
if [ "$STATE" = "BEHIND" ]; then
echo " PR #$PR is behind $DEV_BRANCH; asking Dependabot to rebase."
gh pr comment "$PR" --repo "$REPO" --body "@dependabot rebase"
echo "::endgroup::"
continue
fi
# 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."
gh pr edit "$PR" --repo "$REPO" --add-label "$CONFLICT_LABEL"
gh pr comment "$PR" --repo "$REPO" --body "@dependabot rebase"
fi
echo "::endgroup::"
continue
fi
# A clean PR that was previously flagged has recovered - drop the label.
if has_conflict_label "$PR"; then
gh pr edit "$PR" --repo "$REPO" --remove-label "$CONFLICT_LABEL" || true
fi
echo " Squash-merging PR #$PR"
if gh pr merge "$PR" --repo "$REPO" --squash --admin; then
echo " Merged PR #$PR"
# Give GitHub a moment to mark the now-stale siblings BEHIND.
sleep 15
else
echo " Could not merge PR #$PR now; it will be retried next run."
fi
echo "::endgroup::"
done
+50 -12
View File
@@ -13,36 +13,72 @@ on:
type: choice
options:
- Development
- Beta
- Production
source_ref:
description: "Git ref/SHA to build (defaults to the workflow ref)"
required: false
default: ""
workflow_call:
inputs:
version:
description: "Version to build (e.g., 1.8.0)"
required: true
type: string
build_type:
description: "Build type (Development or Production)"
required: true
type: string
dry_run:
description: "Build the image but do not push to any registry"
required: false
type: boolean
default: false
source_ref:
description: "Git ref/SHA to build"
required: false
type: string
default: ""
jobs:
build:
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Resolve source revision
run: echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
platforms: linux/amd64,linux/arm64
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v4
uses: useblacksmith/setup-docker-builder@v2
- name: Determine tags
id: tags
run: |
VERSION=${{ github.event.inputs.version }}
BUILD_TYPE=${{ github.event.inputs.build_type }}
VERSION=${{ inputs.version }}
BUILD_TYPE=${{ inputs.build_type }}
TAGS=()
ALL_TAGS=()
if [ "$BUILD_TYPE" = "Production" ]; then
TAGS+=("release-$VERSION" "latest")
TAGS+=("release-$VERSION" "$VERSION" "latest")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
done
elif [ "$BUILD_TYPE" = "Beta" ]; then
TAGS+=("beta" "beta-$VERSION")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
@@ -57,35 +93,37 @@ jobs:
echo "ALL_TAGS=$(IFS=,; echo "${ALL_TAGS[*]}")" >> $GITHUB_ENV
- name: Login to GHCR
if: ${{ !inputs.dry_run }}
uses: docker/login-action@v4
with:
registry: ghcr.io
username: lukegus
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub (prod only)
if: ${{ github.event.inputs.build_type == 'Production' }}
- name: Login to Docker Hub (prod and beta only)
if: ${{ (inputs.build_type == 'Production' || inputs.build_type == 'Beta') && !inputs.dry_run }}
uses: docker/login-action@v4
with:
username: bugattiguy527
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push multi-arch image
uses: docker/build-push-action@v7
uses: useblacksmith/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile
push: true
push: ${{ !inputs.dry_run }}
platforms: linux/amd64,linux/arm64
tags: ${{ env.ALL_TAGS }}
build-args: |
BUILDKIT_INLINE_CACHE=1
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SOURCE_SHA }}
org.opencontainers.image.created=${{ github.run_id }}
outputs: type=registry,compression=gzip,compression-level=9
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: ${{ inputs.dry_run && 'type=cacheonly' || 'type=registry,compression=zstd' }}
- name: Cleanup Docker
if: always()
+320 -180
View File
@@ -23,48 +23,70 @@ on:
- file
- release
- submit
source_ref:
description: "Git ref/SHA to build (defaults to the workflow ref)"
required: false
default: ""
workflow_call:
inputs:
build_type:
description: "Platform to build for (all, windows, linux, macos)"
required: true
type: string
artifact_destination:
description: "What to do with the built app (none, file, release, submit)"
required: true
type: string
release_tag:
description: "Explicit release tag to upload assets to (defaults to latest release when empty)"
required: false
type: string
default: ""
version_override:
description: "Version string to stamp into built artifacts instead of package.json's version"
required: false
type: string
default: ""
source_ref:
description: "Git ref/SHA to build"
required: false
type: string
default: ""
outputs:
macos_universal_dmg_sha256:
description: "SHA256 of the universal macOS DMG (for Homebrew cask)"
value: ${{ jobs.build-macos.outputs.dmg_sha256 }}
jobs:
build-windows:
runs-on: windows-latest
if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit'
runs-on: blacksmith-4vcpu-windows-2025
if: (inputs.build_type == 'all' || inputs.build_type == 'windows' || inputs.build_type == '') && inputs.artifact_destination != 'submit'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: |
$maxAttempts = 3
$attempt = 1
while ($attempt -le $maxAttempts) {
try {
npm ci
break
} catch {
if ($attempt -eq $maxAttempts) {
Write-Error "npm ci failed after $maxAttempts attempts"
exit 1
}
Start-Sleep -Seconds 10
$attempt++
}
}
run: npm ci
- name: Get version
id: package-version
run: |
$VERSION = (Get-Content package.json | ConvertFrom-Json).version
$VERSION = "${{ inputs.version_override }}"
if ([string]::IsNullOrEmpty($VERSION)) {
$VERSION = (Get-Content package.json | ConvertFrom-Json).version
}
echo "version=$VERSION" >> $env:GITHUB_OUTPUT
- name: Build Windows (All Architectures)
@@ -73,32 +95,32 @@ jobs:
run: npm run build && npx electron-builder --win --x64 --ia32
- name: Upload Windows x64 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_nsis
path: release/termix_windows_x64_nsis.exe
retention-days: 30
- name: Upload Windows ia32 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_ia32_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_windows_ia32_nsis.exe') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_nsis
path: release/termix_windows_ia32_nsis.exe
retention-days: 30
- name: Upload Windows x64 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_x64_msi.msi') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_windows_x64_msi.msi') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_msi
path: release/termix_windows_x64_msi.msi
retention-days: 30
- name: Upload Windows ia32 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_windows_ia32_msi.msi') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_windows_ia32_msi.msi') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_msi
path: release/termix_windows_ia32_msi.msi
@@ -115,57 +137,48 @@ jobs:
Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "termix_windows_ia32_portable.zip"
- name: Upload Windows x64 Portable
uses: actions/upload-artifact@v4
if: hashFiles('termix_windows_x64_portable.zip') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('termix_windows_x64_portable.zip') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_x64_portable
path: termix_windows_x64_portable.zip
retention-days: 30
- name: Upload Windows ia32 Portable
uses: actions/upload-artifact@v4
if: hashFiles('termix_windows_ia32_portable.zip') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('termix_windows_ia32_portable.zip') != '' && inputs.artifact_destination != 'none'
with:
name: termix_windows_ia32_portable
path: termix_windows_ia32_portable.zip
retention-days: 30
build-linux:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit'
runs-on: blacksmith-8vcpu-ubuntu-2404
if: (inputs.build_type == 'all' || inputs.build_type == 'linux' || inputs.build_type == '') && inputs.artifact_destination != 'submit'
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install system dependencies for AppImage
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libfuse2
sudo apt-get install -y libfuse2 flatpak flatpak-builder imagemagick
- name: Install dependencies
run: |
for i in 1 2 3;
do
if npm ci; then
break
else
if [ $i -eq 3 ]; then
exit 1
fi
sleep 10
fi
done
npm ci
npm install --force @rollup/rollup-linux-x64-gnu
npm install --force @rollup/rollup-linux-arm64-gnu
npm install --force @rollup/rollup-linux-arm-gnueabihf
@@ -196,82 +209,77 @@ jobs:
cd ..
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_appimage
path: release/termix_linux_x64_appimage.AppImage
retention-days: 30
- name: Upload Linux arm64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_arm64_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_appimage
path: release/termix_linux_arm64_appimage.AppImage
retention-days: 30
- name: Upload Linux armv7l AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_armv7l_appimage.AppImage') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_appimage
path: release/termix_linux_armv7l_appimage.AppImage
retention-days: 30
- name: Upload Linux x64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_x64_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_deb
path: release/termix_linux_x64_deb.deb
retention-days: 30
- name: Upload Linux arm64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_arm64_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_deb
path: release/termix_linux_arm64_deb.deb
retention-days: 30
- name: Upload Linux armv7l DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_deb.deb') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_armv7l_deb.deb') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_deb
path: release/termix_linux_armv7l_deb.deb
retention-days: 30
- name: Upload Linux x64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_x64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_x64_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_x64_portable
path: release/termix_linux_x64_portable.tar.gz
retention-days: 30
- name: Upload Linux arm64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_arm64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_arm64_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_arm64_portable
path: release/termix_linux_arm64_portable.tar.gz
retention-days: 30
- name: Upload Linux armv7l tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_armv7l_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_armv7l_portable.tar.gz') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_armv7l_portable
path: release/termix_linux_armv7l_portable.tar.gz
retention-days: 30
- name: Install Flatpak builder and dependencies
run: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder imagemagick
- name: Add Flathub repository
run: |
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
@@ -285,7 +293,10 @@ jobs:
- name: Get version for Flatpak
id: flatpak-version
run: |
VERSION=$(node -p "require('./package.json').version")
VERSION="${{ inputs.version_override }}"
if [ -z "$VERSION" ]; then
VERSION=$(node -p "require('./package.json').version")
fi
RELEASE_DATE=$(date +%Y-%m-%d)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT
@@ -299,9 +310,9 @@ jobs:
CHECKSUM_ARM64=$(sha256sum "release/termix_linux_arm64_appimage.AppImage" | awk '{print $1}')
mkdir -p flatpak-build
cp flatpak/com.karmaa.termix.yml flatpak-build/
cp flatpak/com.karmaa.termix.desktop flatpak-build/
cp flatpak/com.karmaa.termix.metainfo.xml flatpak-build/
cp packaging/flatpak/com.karmaa.termix.yml flatpak-build/
cp packaging/flatpak/com.karmaa.termix.desktop flatpak-build/
cp packaging/flatpak/com.karmaa.termix.metainfo.xml flatpak-build/
cp public/icon.svg flatpak-build/com.karmaa.termix.svg
convert public/icon.png -resize 256x256 flatpak-build/icon-256.png
convert public/icon.png -resize 128x128 flatpak-build/icon-128.png
@@ -333,57 +344,50 @@ jobs:
- name: Create flatpakref file
run: |
VERSION="${{ steps.flatpak-version.outputs.version }}"
cp flatpak/com.karmaa.termix.flatpakref release/
cp packaging/flatpak/com.karmaa.termix.flatpakref release/
sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref
- name: Upload Flatpak bundle
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_flatpak
path: release/termix_linux_flatpak.flatpak
retention-days: 30
- name: Upload Flatpakref
uses: actions/upload-artifact@v4
if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && inputs.artifact_destination != 'none'
with:
name: termix_linux_flatpakref
path: release/com.karmaa.termix.flatpakref
retention-days: 30
build-macos:
runs-on: macos-latest
if: (github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all') && github.event.inputs.artifact_destination != 'submit'
runs-on: blacksmith-6vcpu-macos-latest
if: (inputs.build_type == 'macos' || inputs.build_type == 'all') && inputs.artifact_destination != 'submit'
needs: []
permissions:
contents: write
outputs:
dmg_sha256: ${{ steps.dmg-checksum.outputs.sha256 }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: |
for i in 1 2 3;
do
if npm ci; then
break
else
if [ $i -eq 3 ]; then
exit 1
fi
sleep 10
fi
done
npm ci
npm install --force @rollup/rollup-darwin-arm64
npm install dmg-license
@@ -490,16 +494,16 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
NODE_OPTIONS: --max-old-space-size=4096
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then
npm run build
fi
export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}"
npx electron-builder --mac dmg --universal --x64 --arm64 --publish never
- name: Upload macOS MAS PKG
if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release' || github.event.inputs.artifact_destination == 'submit')
uses: actions/upload-artifact@v4
if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit')
uses: actions/upload-artifact@v7
with:
name: termix_macos_universal_mas
path: release/termix_macos_universal_mas.pkg
@@ -507,24 +511,24 @@ jobs:
if-no-files-found: warn
- name: Upload macOS Universal DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_universal_dmg
path: release/termix_macos_universal_dmg.dmg
retention-days: 30
- name: Upload macOS x64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_x64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_macos_x64_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_x64_dmg
path: release/termix_macos_x64_dmg.dmg
retention-days: 30
- name: Upload macOS arm64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/termix_macos_arm64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none'
uses: actions/upload-artifact@v7
if: hashFiles('release/termix_macos_arm64_dmg.dmg') != '' && inputs.artifact_destination != 'none'
with:
name: termix_macos_arm64_dmg
path: release/termix_macos_arm64_dmg.dmg
@@ -533,11 +537,21 @@ jobs:
- name: Get version for Homebrew
id: homebrew-version
run: |
VERSION=$(node -p "require('./package.json').version")
VERSION="${{ inputs.version_override }}"
if [ -z "$VERSION" ]; then
VERSION=$(node -p "require('./package.json').version")
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Compute universal DMG checksum
id: dmg-checksum
if: hashFiles('release/termix_macos_universal_dmg.dmg') != ''
run: |
CHECKSUM=$(shasum -a 256 "release/termix_macos_universal_dmg.dmg" | awk '{print $1}')
echo "sha256=$CHECKSUM" >> $GITHUB_OUTPUT
- name: Generate Homebrew Cask
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release')
if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && inputs.version_override == '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release')
run: |
VERSION="${{ steps.homebrew-version.outputs.version }}"
DMG_PATH="release/termix_macos_universal_dmg.dmg"
@@ -554,15 +568,15 @@ jobs:
sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb
- name: Upload Homebrew Cask as artifact
uses: actions/upload-artifact@v4
if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'file'
uses: actions/upload-artifact@v7
if: hashFiles('homebrew-generated/termix.rb') != '' && inputs.artifact_destination == 'file'
with:
name: termix_macos_homebrew_cask
path: homebrew-generated/termix.rb
retention-days: 30
- name: Upload Homebrew Cask to release
if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'release'
if: hashFiles('homebrew-generated/termix.rb') != '' && inputs.version_override == '' && inputs.artifact_destination == 'release'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
@@ -583,15 +597,16 @@ jobs:
security delete-keychain $RUNNER_TEMP/dev-signing.keychain-db || true
submit-to-chocolatey:
runs-on: windows-latest
if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '')
runs-on: blacksmith-4vcpu-windows-2025
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'windows' || inputs.build_type == '')
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Get version from package.json
@@ -631,7 +646,7 @@ jobs:
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME"
New-Item -ItemType Directory -Force -Path "choco-build"
Copy-Item -Path "chocolatey\*" -Destination "choco-build" -Recurse -Force
Copy-Item -Path "packaging\chocolatey\*" -Destination "choco-build" -Recurse -Force
$installScript = Get-Content "choco-build\tools\chocolateyinstall.ps1" -Raw -Encoding UTF8
$installScript = $installScript -replace 'DOWNLOAD_URL_PLACEHOLDER', $DOWNLOAD_URL
@@ -681,23 +696,24 @@ jobs:
}
- name: Upload Chocolatey package as artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: chocolatey-package
path: choco-build/*.nupkg
retention-days: 30
submit-to-flatpak:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '')
runs-on: blacksmith-8vcpu-ubuntu-2404
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'linux' || inputs.build_type == '')
needs: []
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Get version from package.json
@@ -735,7 +751,7 @@ jobs:
echo "appimage_arm64_name=$APPIMAGE_ARM64_NAME" >> $GITHUB_OUTPUT
echo "checksum_arm64=$CHECKSUM_ARM64" >> $GITHUB_OUTPUT
- name: Install ImageMagick for icon generation
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y imagemagick
@@ -746,15 +762,13 @@ jobs:
CHECKSUM_X64="${{ steps.appimage-info.outputs.checksum_x64 }}"
CHECKSUM_ARM64="${{ steps.appimage-info.outputs.checksum_arm64 }}"
RELEASE_DATE="${{ steps.package-version.outputs.release_date }}"
APPIMAGE_X64_NAME="${{ steps.appimage-info.outputs.appimage_x64_name }}"
APPIMAGE_ARM64_NAME="${{ steps.appimage-info.outputs.appimage_arm64_name }}"
mkdir -p flatpak-submission
cp flatpak/com.karmaa.termix.yml flatpak-submission/
cp flatpak/com.karmaa.termix.desktop flatpak-submission/
cp flatpak/com.karmaa.termix.metainfo.xml flatpak-submission/
cp flatpak/flathub.json flatpak-submission/
cp packaging/flatpak/com.karmaa.termix.yml flatpak-submission/
cp packaging/flatpak/com.karmaa.termix.desktop flatpak-submission/
cp packaging/flatpak/com.karmaa.termix.metainfo.xml flatpak-submission/
cp packaging/flatpak/flathub.json flatpak-submission/
cp public/icon.svg flatpak-submission/com.karmaa.termix.svg
convert public/icon.png -resize 256x256 flatpak-submission/icon-256.png
@@ -768,23 +782,76 @@ jobs:
sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" flatpak-submission/com.karmaa.termix.metainfo.xml
- name: Upload Flatpak submission as artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: flatpak-submission
path: flatpak-submission/*
retention-days: 30
- name: Check for Flathub token
id: check_flathub_token
run: |
if [ -n "${{ secrets.FLATHUB_TOKEN }}" ]; then
echo "has_token=true" >> $GITHUB_OUTPUT
fi
- name: Open PR on Flathub repo
if: steps.check_flathub_token.outputs.has_token == 'true'
env:
GH_TOKEN: ${{ secrets.FLATHUB_TOKEN }}
VERSION: ${{ steps.package-version.outputs.version }}
run: |
FLATHUB_REPO="flathub/com.karmaa.termix"
BRANCH="release-$VERSION"
git config --global user.name "LukeGus"
git config --global user.email "bugattiguy527@gmail.com"
git clone "https://x-access-token:${GH_TOKEN}@github.com/${FLATHUB_REPO}.git" flathub-repo
cd flathub-repo
git checkout -b "$BRANCH"
cp ../flatpak-submission/com.karmaa.termix.yml ./
cp ../flatpak-submission/com.karmaa.termix.desktop ./
cp ../flatpak-submission/com.karmaa.termix.metainfo.xml ./
cp ../flatpak-submission/flathub.json ./
cp ../flatpak-submission/com.karmaa.termix.svg ./
cp ../flatpak-submission/icon-256.png ./
cp ../flatpak-submission/icon-128.png ./
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to submit for $VERSION; Flathub repo already up to date."
exit 0
fi
git add -A
git commit -m "Update to $VERSION"
git push origin "$BRANCH"
EXISTING_PR=$(gh pr list --repo "$FLATHUB_REPO" --head "$BRANCH" --state open --json number -q '.[0].number' || true)
if [ -z "$EXISTING_PR" ]; then
gh pr create --repo "$FLATHUB_REPO" \
--base master \
--head "$BRANCH" \
--title "Update to $VERSION" \
--body "Automated release update to version $VERSION."
else
echo "PR #$EXISTING_PR already open for $BRANCH."
fi
submit-to-homebrew:
runs-on: macos-latest
if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos')
runs-on: blacksmith-6vcpu-macos-latest
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'macos')
needs: []
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Get version from package.json
@@ -837,31 +904,36 @@ jobs:
ruby -c homebrew-submission/Casks/t/termix.rb
- name: Upload Homebrew submission as artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: homebrew-submission
path: homebrew-submission/*
retention-days: 30
upload-to-release:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event.inputs.artifact_destination == 'release'
runs-on: blacksmith-8vcpu-ubuntu-2404
if: inputs.artifact_destination == 'release'
needs: [build-windows, build-linux, build-macos]
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
path: artifacts
- name: Get latest release tag
- name: Resolve release tag
id: get_release
run: |
echo "RELEASE_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ github.token }}
INPUT_TAG: ${{ inputs.release_tag }}
run: |
if [ -n "$INPUT_TAG" ]; then
echo "RELEASE_TAG=$INPUT_TAG" >> $GITHUB_ENV
else
echo "RELEASE_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')" >> $GITHUB_ENV
fi
- name: Upload artifacts to latest release
run: |
@@ -879,38 +951,29 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
submit-to-testflight:
runs-on: macos-latest
if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos')
submit-to-app-store:
runs-on: blacksmith-6vcpu-macos-latest
if: inputs.artifact_destination == 'submit' && (inputs.build_type == 'all' || inputs.build_type == 'macos')
needs: []
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: |
for i in 1 2 3;
do
if npm ci; then
break
else
if [ $i -eq 3 ]; then
exit 1
fi
sleep 10
fi
done
npm ci
npm install --force @rollup/rollup-darwin-arm64
npm install dmg-license
@@ -953,18 +1016,6 @@ jobs:
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
BUILD_VERSION="${{ github.run_number }}"
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
- name: Check for App Store Connect API credentials
id: check_asc_creds
run: |
@@ -973,36 +1024,125 @@ jobs:
fi
- name: Setup Ruby for Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true'
if: steps.check_certs.outputs.has_certs == 'true' && steps.check_asc_creds.outputs.has_credentials == 'true'
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
ruby-version: "3.3"
bundler-cache: false
- name: Install Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true'
run: |
gem install fastlane -N
if: steps.check_certs.outputs.has_certs == 'true' && steps.check_asc_creds.outputs.has_credentials == 'true'
run: gem install fastlane -N
- name: Deploy to App Store Connect (TestFlight)
if: steps.check_asc_creds.outputs.has_credentials == 'true'
- name: Write App Store Connect API key
if: steps.check_certs.outputs.has_certs == 'true' && steps.check_asc_creds.outputs.has_credentials == 'true'
env:
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
run: |
# Write API key JSON that Fastlane expects; the PEM's newlines
# must be preserved as literal \n escapes, not stripped, or
# spaceship fails to parse the key (invalid curve name).
mkdir -p /tmp/asc_keys
KEY_P8_PATH="/tmp/asc_keys/AuthKey_${APPLE_KEY_ID}.p8"
API_KEY_JSON="/tmp/asc_keys/api_key.json"
echo "$APPLE_KEY_CONTENT" | base64 --decode > "$KEY_P8_PATH"
KEY_ID="$APPLE_KEY_ID" ISSUER_ID="$APPLE_ISSUER_ID" KEY_P8_PATH="$KEY_P8_PATH" \
node -e '
const fs = require("fs");
const key = fs.readFileSync(process.env.KEY_P8_PATH, "utf8");
process.stdout.write(JSON.stringify({
key_id: process.env.KEY_ID,
issuer_id: process.env.ISSUER_ID,
key,
in_house: false,
}, null, 2) + "\n");
' > "$API_KEY_JSON"
- name: Resolve next build number from App Store Connect
id: build_number
if: steps.check_certs.outputs.has_certs == 'true' && steps.check_asc_creds.outputs.has_credentials == 'true'
run: |
APP_VERSION=$(node -p "require('./package.json').version")
OUT_FILE="$RUNNER_TEMP/latest_build_number.txt"
LANE_DIR="$RUNNER_TEMP/asc_lane/fastlane"
mkdir -p "$LANE_DIR"
cat > "$LANE_DIR/Fastfile" <<EOF
default_platform(:mac)
lane :fetch_build_number do
live_number = app_store_build_number(
live: true,
platform: "osx",
api_key_path: "/tmp/asc_keys/api_key.json",
app_identifier: "com.karmaa.termix",
initial_build_number: 0,
)
pending_number = app_store_build_number(
live: false,
platform: "osx",
api_key_path: "/tmp/asc_keys/api_key.json",
app_identifier: "com.karmaa.termix",
initial_build_number: 0,
)
number = [live_number, pending_number].max
File.write("$OUT_FILE", number.to_s)
end
EOF
(cd "$RUNNER_TEMP/asc_lane" && fastlane fetch_build_number) 2>&1 || true
LATEST=""
if [ -f "$OUT_FILE" ]; then
LATEST=$(cat "$OUT_FILE" | tr -d '[:space:]')
fi
if ! [[ "$LATEST" =~ ^[0-9]+$ ]]; then
echo "Could not resolve latest build number from App Store Connect; falling back to run number."
LATEST="${{ github.run_number }}"
fi
echo "build_version=$((LATEST + 1))" >> "$GITHUB_OUTPUT"
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: |
BUILD_VERSION="${{ steps.build_number.outputs.build_version || github.run_number }}"
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
- name: Upload and submit to Mac App Store
if: steps.check_certs.outputs.has_certs == 'true' && steps.check_asc_creds.outputs.has_credentials == 'true'
run: |
PKG_FILE=$(find release -name "termix_macos_universal_mas.pkg" -type f | head -n 1)
if [ -z "$PKG_FILE" ]; then
echo "PKG file not found, exiting."
echo "PKG file not found in release/; aborting."
exit 1
fi
mkdir -p ~/private_keys
echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8
VERSION=$(node -p "require('./package.json').version")
API_KEY_JSON="/tmp/asc_keys/api_key.json"
xcrun altool --upload-app -f "$PKG_FILE" \
--type macos \
--apiKey "${{ secrets.APPLE_KEY_ID }}" \
--apiIssuer "${{ secrets.APPLE_ISSUER_ID }}"
continue-on-error: true
fastlane deliver \
--pkg "$PKG_FILE" \
--api_key_path "$API_KEY_JSON" \
--app_version "$VERSION" \
--skip_metadata true \
--skip_screenshots true \
--submit_for_review true \
--automatic_release true \
--precheck_include_in_app_purchases false \
--submission_information "{\"export_compliance_uses_encryption\": false}" \
--force true
- name: Clean up keychains
if: always()
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
rm -rf /tmp/asc_keys
+3 -3
View File
@@ -10,10 +10,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
@@ -25,7 +25,7 @@ jobs:
run: npm run generate:openapi
- name: Upload OpenAPI artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: openapi-spec
path: openapi.json
+80 -4
View File
@@ -13,18 +13,21 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npx eslint .
- name: Lint
# npm run lint, not npx eslint — the script also checks that the
# generated dialect schemas match schema.ts, which eslint cannot see.
run: npm run lint
- name: Run Prettier check
run: npx prettier --check .
@@ -34,3 +37,76 @@ jobs:
- name: Build
run: npm run build
database-dialects:
name: Postgres and MySQL
runs-on: blacksmith-2vcpu-ubuntu-2404
# The test suite only ever sees SQLite. Everything that differs per engine —
# the RETURNING replacements, the read-then-write transactions, the
# migrations themselves — is only covered here, against real servers.
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: termix
POSTGRES_PASSWORD: termix
POSTGRES_DB: termix_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
mysql:
image: mysql:8
env:
MYSQL_ROOT_PASSWORD: termix
MYSQL_DATABASE: termix_test
MYSQL_USER: termix
MYSQL_PASSWORD: termix
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -ptermix"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
# Each run applies the migrations to an empty database first, so a
# migration that does not apply cleanly fails the build.
- name: Verify Postgres
run: npm run verify:dialect -- postgres://termix:termix@127.0.0.1:5432/termix_test
- name: Verify MySQL
run: npm run verify:dialect -- mysql://termix:termix@127.0.0.1:3306/termix_test
# The same repository suite the SQLite run executes, pointed at each
# engine. This is where a dialect difference in a query shows up as a
# failing assertion rather than as a bug report.
- name: Repository tests on Postgres
env:
TEST_DIALECT: postgres
TEST_DATABASE_URL: postgres://termix:termix@127.0.0.1:5432/termix_test
run: npx vitest run src/backend/tests/database/repositories --no-file-parallelism
- name: Repository tests on MySQL
env:
TEST_DIALECT: mysql
TEST_DATABASE_URL: mysql://termix:termix@127.0.0.1:3306/termix_test
run: npx vitest run src/backend/tests/database/repositories --no-file-parallelism
+557
View File
@@ -0,0 +1,557 @@
name: Release
on:
workflow_dispatch:
inputs:
mode:
description: "Release mode"
required: true
default: Everything
type: choice
options:
- Everything
- Overwrite release
- Dry run
- Skip submit
permissions:
contents: write
jobs:
prep:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
version: ${{ steps.info.outputs.version }}
release_tag: ${{ steps.info.outputs.release_tag }}
mobile_version: ${{ steps.info.outputs.mobile_version }}
dev_branch: ${{ steps.info.outputs.dev_branch }}
steps:
- name: Guard branch (release modes)
if: ${{ inputs.mode != 'Overwrite release' && !startsWith(github.ref, 'refs/heads/dev-') }}
run: |
echo "This mode must be run from a dev branch (got ${{ github.ref }})."
exit 1
- name: Guard branch (overwrite mode)
if: ${{ inputs.mode == 'Overwrite release' && github.ref != 'refs/heads/main' }}
run: |
echo "Overwrite release must be run from main (got ${{ github.ref }})."
exit 1
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Resolve versions
id: info
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ inputs.mode }}" = "Overwrite release" ]; then
DEV_BRANCH=""
VERSION=$(node -p "require('./package.json').version")
else
DEV_BRANCH="${GITHUB_REF#refs/heads/}"
VERSION=$(node scripts/parse-dev-branch.cjs "$DEV_BRANCH")
fi
echo "dev_branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "release_tag=release-$VERSION-tag" >> "$GITHUB_OUTPUT"
MOBILE_RAW=$(gh release view -R Termix-SSH/Mobile --json tagName -q .tagName)
MOBILE_VERSION=$(echo "$MOBILE_RAW" | sed -E 's/^release-//; s/-tag$//')
if [ -z "$MOBILE_VERSION" ]; then
echo "Failed to resolve the latest Termix-SSH/Mobile release version."
exit 1
fi
echo "mobile_version=$MOBILE_VERSION" >> "$GITHUB_OUTPUT"
- name: Validate release notes
run: |
node scripts/generate-release-body.cjs \
--version "${{ steps.info.outputs.version }}" \
--mobile-version "${{ steps.info.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > /dev/null
normalize:
needs: [prep]
if: ${{ inputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 0
token: ${{ secrets.GHCR_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Lint and format
run: |
npm run lint:fix || true
npm run format
- name: Run unit tests
run: npm run test
- name: Sync version
run: node scripts/sync-version.cjs --version "${{ needs.prep.outputs.version }}"
- name: Commit changes to dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
if git diff --quiet; then
echo "No lint/format/version changes to commit."
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would commit and push the following changes to ${{ needs.prep.outputs.dev_branch }}:"
git --no-pager diff --stat
exit 0
fi
git add -A
git commit -m "chore: lint, format, and bump version to ${{ needs.prep.outputs.version }}"
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
crowdin:
needs: [prep, normalize]
if: ${{ inputs.mode != 'Dry run' && inputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 0
token: ${{ secrets.GHCR_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Merge main into dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git fetch origin main
git merge origin/main -X ours --no-edit || true
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
- name: Clean up stale Crowdin git integration branch
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head i18n_translate --state open --json number -q '.[0].number' || true)
if [ -n "$PR_NUMBER" ]; then
gh pr close "$PR_NUMBER" --repo ${{ github.repository }} --delete-branch || true
fi
git push origin --delete i18n_translate || true
- name: Upload sources to Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Machine pre-translate untranslated strings
env:
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
run: node scripts/crowdin-pretranslate.cjs
- name: Download translations from Crowdin
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Commit translations to dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git add src/ui/locales/translated
if git diff --cached --quiet; then
echo "No translation changes to commit."
exit 0
fi
git commit -m "chore: sync Crowdin translations for ${{ needs.prep.outputs.version }}"
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
merge-to-main:
needs: [prep, normalize, crowdin]
if: ${{ always() && needs.prep.result == 'success' && (needs.normalize.result == 'success' || needs.normalize.result == 'skipped') && (needs.crowdin.result == 'success' || needs.crowdin.result == 'skipped') }}
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
main_sha: ${{ steps.merge.outputs.main_sha }}
build_ref: ${{ steps.merge.outputs.build_ref }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Generate PR body
id: body
run: |
node scripts/generate-release-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--mobile-version "${{ needs.prep.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > PR_BODY.md
- name: Open and squash-merge PR to main
id: merge
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
DEV_BRANCH="${{ needs.prep.outputs.dev_branch }}"
TITLE="release-${{ needs.prep.outputs.version }}"
if [ "${{ inputs.mode }}" = "Overwrite release" ]; then
echo "OVERWRITE: no merge; building from main as-is."
echo "build_ref=main" >> "$GITHUB_OUTPUT"
echo "main_sha=$(gh api repos/${{ github.repository }}/commits/main -q .sha)" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would open and squash-merge a PR from $DEV_BRANCH into main."
echo "DRY RUN: downstream jobs will build from $DEV_BRANCH instead of main."
echo "build_ref=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "main_sha=" >> "$GITHUB_OUTPUT"
exit 0
fi
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head "$DEV_BRANCH" --base main --state open --json number -q '.[0].number' || true)
if [ -z "$PR_NUMBER" ]; then
PR_NUMBER=$(gh pr create --repo ${{ github.repository }} \
--base main --head "$DEV_BRANCH" \
--title "$TITLE" --body-file PR_BODY.md \
| grep -oE '[0-9]+$')
fi
gh pr merge "$PR_NUMBER" --repo ${{ github.repository }} --squash --admin
STATE=$(gh pr view "$PR_NUMBER" --repo ${{ github.repository }} --json state -q .state)
if [ "$STATE" != "MERGED" ]; then
echo "PR #$PR_NUMBER did not merge (state: $STATE)."
exit 1
fi
MAIN_SHA=$(gh api repos/${{ github.repository }}/commits/main -q .sha)
echo "main_sha=$MAIN_SHA" >> "$GITHUB_OUTPUT"
echo "build_ref=$MAIN_SHA" >> "$GITHUB_OUTPUT"
docker:
needs: [prep, merge-to-main]
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.version }}
build_type: Production
dry_run: ${{ inputs.mode == 'Dry run' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref }}
secrets: inherit
create-release:
needs: [prep, merge-to-main, docker]
if: ${{ inputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Clear existing release artifacts (overwrite mode)
if: ${{ inputs.mode == 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
TAG="${{ needs.prep.outputs.release_tag }}"
if ! gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
echo "Overwrite release: no existing release $TAG to overwrite."
exit 1
fi
echo "Clearing existing assets from $TAG..."
gh release view "$TAG" --repo ${{ github.repository }} --json assets -q '.assets[].name' | while read -r ASSET; do
[ -n "$ASSET" ] && gh release delete-asset "$TAG" "$ASSET" --repo ${{ github.repository }} --yes || true
done
- name: Generate release body
if: ${{ inputs.mode != 'Overwrite release' }}
run: |
node scripts/generate-release-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--mobile-version "${{ needs.prep.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > RELEASE_BODY.md
- name: Create or update GitHub release
if: ${{ inputs.mode != 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
TAG="${{ needs.prep.outputs.release_tag }}"
TITLE="release-${{ needs.prep.outputs.version }}"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
gh release edit "$TAG" --repo ${{ github.repository }} --title "$TITLE" --notes-file RELEASE_BODY.md
else
gh release create "$TAG" --repo ${{ github.repository }} --title "$TITLE" --notes-file RELEASE_BODY.md --target "${{ needs.merge-to-main.outputs.main_sha }}"
fi
electron-release:
needs: [prep, merge-to-main, create-release]
if: ${{ always() && needs.merge-to-main.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: ${{ inputs.mode == 'Dry run' && 'file' || 'release' }}
release_tag: ${{ needs.prep.outputs.release_tag }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref }}
secrets: inherit
electron-submit:
needs: [prep, merge-to-main, electron-release]
if: ${{ inputs.mode != 'Dry run' && inputs.mode != 'Skip submit' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: submit
source_ref: ${{ needs.merge-to-main.outputs.build_ref }}
secrets: inherit
cask-commit-back:
needs: [prep, electron-release]
if: ${{ inputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
fetch-depth: 1
token: ${{ secrets.GHCR_TOKEN }}
- name: Bump and commit Homebrew cask
env:
VERSION: ${{ needs.prep.outputs.version }}
DMG_SHA256: ${{ needs.electron-release.outputs.macos_universal_dmg_sha256 }}
run: |
if [ -z "$DMG_SHA256" ]; then
echo "No universal DMG checksum available (macOS build unsigned or skipped); leaving cask unchanged."
exit 0
fi
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git fetch origin main
git checkout -B main origin/main
sed -i "s|version \".*\"|version \"$VERSION\"|g" Casks/termix.rb
sed -i "s|sha256 \".*\"|sha256 \"$DMG_SHA256\"|g" Casks/termix.rb
git add Casks/termix.rb
if git diff --cached --quiet; then
echo "Cask already up to date."
exit 0
fi
git commit -m "chore: bump Homebrew cask to $VERSION"
git push origin HEAD:main
docs:
needs: [prep, merge-to-main]
if: ${{ always() && needs.merge-to-main.result == 'success' && inputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout Termix
uses: actions/checkout@v7
with:
ref: ${{ needs.merge-to-main.outputs.build_ref }}
fetch-depth: 1
path: termix
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: "termix/.nvmrc"
cache: "npm"
cache-dependency-path: termix/package-lock.json
- name: Generate OpenAPI spec
working-directory: termix
run: |
npm ci
npm run generate:openapi
- name: Checkout Docs repository
uses: actions/checkout@v7
with:
repository: Termix-SSH/Docs
ref: main
fetch-depth: 0
token: ${{ secrets.GHCR_TOKEN }}
path: docs-repo
- name: Create docs release branch
working-directory: docs-repo
run: git checkout -B "dev-${{ needs.prep.outputs.version }}"
- name: Overwrite OpenAPI spec and regenerate API docs
working-directory: docs-repo
run: |
cp ../termix/openapi.json static/openapi.json
npm ci
npm run docusaurus gen-api-docs termix
- name: Commit and push docs branch
working-directory: docs-repo
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
if git diff --quiet; then
echo "No docs changes to commit."
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would commit and push the following docs changes to dev-${{ needs.prep.outputs.version }}:"
git --no-pager diff --stat
exit 0
fi
git add -A
git commit -m "feat: update API docs for ${{ needs.prep.outputs.version }}"
git push --force origin "dev-${{ needs.prep.outputs.version }}"
- name: Open and squash-merge docs PR
if: ${{ inputs.mode != 'Dry run' }}
working-directory: docs-repo
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
BRANCH="dev-${{ needs.prep.outputs.version }}"
if ! git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "No docs branch pushed (nothing changed); skipping PR."
exit 0
fi
PR_NUMBER=$(gh pr list --repo Termix-SSH/Docs --head "$BRANCH" --base main --state open --json number -q '.[0].number' || true)
if [ -z "$PR_NUMBER" ]; then
PR_URL=$(gh pr create --repo Termix-SSH/Docs \
--base main --head "$BRANCH" \
--title "release-${{ needs.prep.outputs.version }}" \
--body "API docs for ${{ needs.prep.outputs.version }}")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
fi
if [ -z "$PR_NUMBER" ]; then
echo "Failed to find or create a PR for $BRANCH."
exit 1
fi
gh pr merge "$PR_NUMBER" --repo Termix-SSH/Docs --squash --admin
publish-youtube:
needs: [prep, electron-release]
if: ${{ always() && inputs.mode != 'Dry run' && inputs.mode != 'Overwrite release' && needs.electron-release.result == 'success' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Set release video to public
env:
YOUTUBE_CLIENT_ID: ${{ secrets.YOUTUBE_CLIENT_ID }}
YOUTUBE_CLIENT_SECRET: ${{ secrets.YOUTUBE_CLIENT_SECRET }}
YOUTUBE_REFRESH_TOKEN: ${{ secrets.YOUTUBE_REFRESH_TOKEN }}
run: node scripts/publish-youtube.cjs
cleanup:
needs:
[
prep,
merge-to-main,
electron-release,
cask-commit-back,
docs,
publish-youtube,
]
if: ${{ always() && (inputs.mode == 'Everything' || inputs.mode == 'Skip submit') && needs.merge-to-main.result == 'success' && needs.electron-release.result == 'success' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Delete dev branch in Termix
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
git push https://x-access-token:${{ secrets.GHCR_TOKEN }}@github.com/${{ github.repository }}.git \
--delete "${{ needs.prep.outputs.dev_branch }}" || true
- name: Delete dev branch in Docs
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
git push https://x-access-token:${{ secrets.GHCR_TOKEN }}@github.com/Termix-SSH/Docs.git \
--delete "dev-${{ needs.prep.outputs.version }}" || true
+5
View File
@@ -7,8 +7,10 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
src/mcp-server/node_modules
dist
dist-ssr
coverage
*.local
.vscode/
@@ -30,3 +32,6 @@ dist-ssr
electron/build-info.cjs
/.mcp.json
/CLAUDE.md
/old_db/
/scripts/fix-bugs.mjs
/scripts/fix-features.mjs
+4
View File
@@ -5,6 +5,7 @@ dist-ssr
release
node_modules
src/mcp-server/node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
@@ -16,3 +17,6 @@ db
*.min.js
*.min.css
openapi.json
# Generated by drizzle-kit; formatting is the tool's own
drizzle/
-128
View File
@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
mail@termix.site.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+2 -2
View File
@@ -1,6 +1,6 @@
cask "termix" do
version "2.2.0"
sha256 "62030994ecd8de264f7edae965caf57fabb9f9014007063a35db8cab4c6e818c"
version "2.6.0"
sha256 "1ea70f6d909ac844cae40e834d54f12151b20f4bd0da15cd624b8cf9ecc2555a"
url "https://github.com/Termix-SSH/Termix/releases/download/release-#{version}-tag/termix_macos_universal_dmg.dmg"
name "Termix"
+7
View File
@@ -0,0 +1,7 @@
{
"drips": {
"ethereum": {
"ownedBy": "0x67e0C779119D9BcC2187564A66B80a58767d05d1"
}
}
}
+366 -130
View File
@@ -1,97 +1,274 @@
# Repo Stats
<div align="center">
<p align="center">
🇺🇸 English · <a href="readme/README-CN.md">🇨🇳 中文</a> · <a href="readme/README-JA.md">🇯🇵 日本語</a> · <a href="readme/README-KO.md">🇰🇷 한국어</a> · <a href="readme/README-FR.md">🇫🇷 Français</a> · <a href="readme/README-DE.md">🇩🇪 Deutsch</a> · <a href="readme/README-ES.md">🇪🇸 Español</a> · <a href="readme/README-PT.md">🇧🇷 Português</a> · <a href="readme/README-RU.md">🇷🇺 Русский</a> · <a href="readme/README-AR.md">🇸🇦 العربية</a> · <a href="readme/README-HI.md">🇮🇳 हिन्दी</a> · <a href="readme/README-TR.md">🇹🇷 Türkçe</a> · <a href="readme/README-VI.md">🇻🇳 Tiếng Việt</a> · <a href="readme/README-IT.md">🇮🇹 Italiano</a>
<img src="./public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Self-hosted SSH management and remote desktop access</p>
<p>
English ·
<a href="docs/readme/README-CN.md">中文</a> ·
<a href="docs/readme/README-JA.md">日本語</a> ·
<a href="docs/readme/README-KO.md">한국어</a> ·
<a href="docs/readme/README-FR.md">Français</a> ·
<a href="docs/readme/README-DE.md">Deutsch</a> ·
<a href="docs/readme/README-ES.md">Español</a> ·
<a href="docs/readme/README-PT.md">Português</a> ·
<a href="docs/readme/README-RU.md">Русский</a> ·
<a href="docs/readme/README-AR.md">العربية</a> ·
<a href="docs/readme/README-HI.md">हिन्दी</a> ·
<a href="docs/readme/README-TR.md">Türkçe</a> ·
<a href="docs/readme/README-VI.md">Tiếng Việt</a> ·
<a href="docs/readme/README-IT.md">Italiano</a>
</p>
![GitHub Repo stars](https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars)
![GitHub forks](https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks)
![GitHub Release](https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&v=1)
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p align="center">
<img src="./repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
<br>
<small style="color: #666;">Achieved on September 1st, 2025</small>
<p>
<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 />
<p align="center">
<a href="https://github.com/Termix-SSH/Termix">
<img alt="Termix Banner" src=./repo-images/HeaderImage.png style="width: auto; height: auto;"> </a>
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.
<br />
<img src="./docs/repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
# Overview
</div>
<p align="center">
<a href="https://github.com/Termix-SSH/Termix">
<img alt="Termix Banner" src=./public/icon.svg style="width: 250px; height: 250px;"> </a>
</p>
<br />
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 SSH file management, and many other tools. Termix is the perfect
free and self-hosted alternative to Termius available for all platforms.
## Overview
# Features
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.
<br />
## Features
<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>
<tr>
<td width="50%" valign="top">
**Session Sharing:**
Share a live terminal, RDP, VNC, or Telnet session with others in real time. Share via a link (joined anonymously, no account needed) or with a specific Termix user, and choose read-only or read-write access. Shares can expire automatically or be revoked at any time, and session sharing can be toggled globally or per-host.
</td>
<td width="50%" valign="top">
**Desktop Standalone + 2-Way Sync:**
The Electron desktop app runs fully standalone with its own local backend and database, no server required. Optionally connect it to a remote Termix server for automatic two-way sync of hosts, credentials, snippets, and more, and choose whether SSH connections are started locally or through the remote server.
</td>
</tr>
</table>
<br />
<details>
<summary><b>More features</b></summary>
<br />
- **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.
- **Remote Desktop Access** - RDP, VNC, and Telnet support over the browser with complete customization and split screening
- **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.
- **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.
- **Docker Management** - Start, stop, pause, remove containers. View container stats. Control container using docker exec terminal. It was not made to replace Portainer or Dockge but rather to simply manage your containers compared to creating them.
- **SSH Host Manager** - Save, organize, and manage your SSH connections with tags and folders, and easily save reusable login info while being able to automate the deployment of SSH keys
- **Server Stats** - View CPU, memory, and disk usage along with network, uptime, system information, firewall, port monitor, on most Linux based servers
- **Dashboard** - View server information at a glance on your dashboard
- **RBAC** - Create roles and share hosts across users/roles
- **User Authentication** - Secure user management with admin controls and OIDC (with access control) and 2FA (TOTP) support. View active user sessions across all platforms and revoke permissions. Link your OIDC/Local accounts together.
- **Database Encryption** - Backend stored as encrypted SQLite database files. View [docs](https://docs.termix.site/security) for more.
- **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.
- **Languages** - Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/translations))
- **Platform Support** - Available as a web app, desktop application (Windows, Linux, and macOS, can be run standalone without Termix backend), PWA, and dedicated mobile/tablet app for iOS and Android.
- **SSH Tools** - Create reusable command snippets that execute with a single click. Run one command simultaneously across multiple open terminals.
- **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
- **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, etc.
- **Network Graph** - Customize your Dashboard to visualize your homelab based off your SSH connections with status support
- **Persistent Tabs** - SSH sessions and tabs stay open across devices/refreshes if enabled in user profile
- **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.
# Planned Features
</details>
See [Projects](https://github.com/orgs/Termix-SSH/projects/2) for all planned features. If you are looking to contribute, see [Contributing](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md).
<br />
# Installation
## Platform Support
Supported Devices:
<table align="center">
<tr>
<th align="center">Platform</th>
<th align="center">Distribution</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Any modern browser (Chrome, Safari, Firefox) · PWA support</td>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable · MSI Installer · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portable · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
- Website (any modern browser on any platform like Chrome, Safari, and Firefox) (includes PWA support)
- Windows (x64/ia32)
- Portable
- MSI Installer
- Chocolatey Package Manager
- Linux (x64/ia32)
- Portable
- AUR
- AppImage
- Deb
- Flatpak
- macOS (x64/ia32 on v12.0+)
- Apple App Store
- DMG
- Homebrew
- iOS/iPadOS (v15.1+)
- Apple App Store
- IPA
- Android (v7.0+)
- Google Play Store
- APK
<br />
Visit the Termix [Docs](https://docs.termix.site/install) for more information on how to install Termix on all platforms. Otherwise, view
a sample Docker Compose file here (you can omit guacd and the network if you don't plan on using remote desktop features):
## Installation
Visit the [Termix Docs](https://docs.termix.site/install) for full installation instructions across all platforms.
Sample Docker Compose file (you can omit `guacd` and the network if you don't plan on using remote desktop features):
```yaml
services:
@@ -128,80 +305,139 @@ networks:
driver: bridge
```
# Sponsors
### Cloud Hosting
<p align="left">
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="50" alt="DigitalOcean">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="50" alt="Crowdin">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="50" alt="Blacksmith">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="50" alt="Crowdflare">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="50" alt="TailScale">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="50" alt="Akamai">
</a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="50" alt="AWS">
</a>
</p>
You can also run the Termix server on a cloud VPS instead of inside your own network. If Termix runs on the network it manages, an outage takes Termix with it, and your hosts and saved sessions are stuck inside the system you are trying to fix. Hosting it externally keeps it reachable no matter what happens to your network, and gives you a static IP and access from anywhere without a VPN or port forward.
# Support
[GINERNET](https://docs.termix.site/install/ginernet) is a sponsor of Termix, and there is a full step by step guide for deploying to their VPS platform in the docs.
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.
<br />
# Screenshots
## Telemetry
[![YouTube](./repo-images/YouTube.jpg)](https://www.youtube.com/@TermixSSH/videos)
Termix sends a small anonymous usage ping once every 24 hours to help understand how many instances are running and which features are actually used. This only includes a randomly generated instance ID, a count of users and hosts, the app version, and whether certain features (terminal, file manager, tunnels, docker, etc.) were used in the last 24 hours. It never includes usernames, hostnames, IP addresses, credentials, or any other identifying or connection data.
<p align="center">
<img src="./repo-images/Image 1.png" width="400" alt="Termix Demo 1"/>
<img src="./repo-images/Image 2.png" width="400" alt="Termix Demo 2"/>
</p>
This is opt-out and enabled by default. You can disable it at any time in Admin Settings under General, or set `ENABLE_TELEMETRY=false` to turn it off before you ever spin-up Termix.
<p align="center">
<img src="./repo-images/Image 3.png" width="400" alt="Termix Demo 3"/>
<img src="./repo-images/Image 4.png" width="400" alt="Termix Demo 4"/>
</p>
<br />
<p align="center">
<img src="./repo-images/Image 5.png" width="400" alt="Termix Demo 5"/>
<img src="./repo-images/Image 6.png" width="400" alt="Termix Demo 6"/>
</p>
## Donate
<p align="center">
<img src="./repo-images/Image 7.png" width="400" alt="Termix Demo 7"/>
<img src="./repo-images/Image 8.png" width="400" alt="Termix Demo 8"/>
</p>
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.
<p align="center">
<img src="./repo-images/Image 9.png" width="400" alt="Termix Demo 9"/>
<img src="./repo-images/Image 10.png" width="400" alt="Termix Demo 10"/>
</p>
[Donate](https://donate.termix.site/)
<p align="center">
<img src="./repo-images/Image 11.png" width="400" alt="Termix Demo 11"/>
<img src="./repo-images/Image 12.png" width="400" alt="Termix Demo 12"/>
</p>
<br />
Some videos and images may be out of date or may not perfectly showcase features.
## Sponsors
# License
Interested in a paid placement to support development? Email [mail@termix.site](mailto:mail@termix.site).
Distributed under the Apache License Version 2.0. See LICENSE for more information.
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://ginernet.com/">
<img src="https://ginernet.com/img/logo-web.png" height="40" alt="Ginernet" />
</a>
</div>
<br />
## 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.
<br />
## Screenshots
<div align="center">
<br />
[![YouTube](./docs/repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Watch update overviews on YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="./docs/repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="./docs/repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="./docs/repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="./docs/repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="./docs/repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="./docs/repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="./docs/repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="./docs/repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="./docs/repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="./docs/repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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).
<br />
## License
Distributed under the Apache License Version 2.0. See `LICENSE` for more information.
+70
View File
@@ -0,0 +1,70 @@
<!-- SUMMARY -->
Standalone-first Electron desktop app with optional remote sync, shared/multiplayer terminal and remote desktop sessions, improved SSH MFA support, custom key shortcuts, bug fixes across terminal, RDP/VNC, mobile, and auth.
<!-- /SUMMARY -->
<!-- YOUTUBE -->
https://youtu.be/g0QjNdV3YYY
<!-- /YOUTUBE -->
<!-- UPDATE_LOG -->
- Added support for multi disk usage in file manager/host metrics
- Added better Ctrl + F terminal search
- Added right click menu on app rail to pin sidebar faster
- Support for overriding shared SSH credential
- Added mapping for OIDC provider groups to RBAC roles
- Added host export dialog for more customizable host exporting
- Initial groundwork for supporting more database types (postgres and mysql)
- Added audit log export (CSV/NDJSON) and optional live forwarding to a SIEM
- Added configurable audit log retention by age and row count
- Audit entries for file manager, RDP/VNC/Telnet, Docker and tunnel sessions
- Encrypted SSO secrets instead of BASE64 encoding them
- Added support for Tailscale SSH check mode with in-terminal browser authentication
<!-- /UPDATE_LOG -->
<!-- BUG_FIXES -->
- Hardened nginx headers/asset caching
- Deleting an account no longer deletes its audit entries and session recordings
- Made logger display expanded error messages
- Removed phantom port knocking
- Fixed Proxmox guest discovery failures over jump host
- Compare sync cursors independently of timestamp layout
- Fixed sync deleting not reaching other side
- Remote sync stalling after first pass and never propagating deletions
- DB_FILE_ENCRYPTION variable loading DB file as empty
- Removed unneeded field encryption boundaries
- SSH login alerts being dropped silently
- Honor lookupOptions.all in custom DNS lookup hook
- Jump host SOCKS proxy settings being ignored
- Jump host tunnels not reachable by guacd
- Per-host RDP/VNC recording flags being ignored
- RDP sessions not using the configured resolution
- OIDC login failing with unverifiable ID tokens or JWKs without alg
- Refuse to start with an empty database when data exists elsewhere
- Database not persisting during container shutdown
- Host command history setting not saving
- Desktop preference sync and remote sync account identity
- Desktop guacd calls not routed to the connected remote server
- File manager navigation getting stuck after permission errors
- Read-only shared hosts could be dragged into folders
- Terminal highlighting breaking inside split control strings
- Windows terminal Tab key and Android hardware keyboard keys
- tmux monitor failing on Tailscale-authenticated hosts
- Database export not staying same-origin on localhost
- Snippet execution results not reported correctly
- Shared hosts appearing twice
- Wake-on-LAN broadcast address being dropped
- Sharing an empty folder was rejected
- Remote sync losing references between linked records
- Desktop app failing to find its backend on some architectures
- Centralized outbound address validation for homepage proxy requests
- Default font size to medium instead of large
- Tailscale hosts hanging on connect when the tailnet ACL requires a periodic check
<!-- /BUG_FIXES -->
+58
View File
@@ -0,0 +1,58 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "dev-2.6.1"
},
"files": {
"ignoreUnknown": true,
"includes": [
"**",
"!!build",
"!!coverage",
"!!dist",
"!!dist-ssr",
"!!release",
"!!node_modules",
"!!src/mcp-server/node_modules",
"!!db",
"!!.env",
"!!**/*.min.js",
"!!**/*.min.css",
"!!openapi.json"
]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80,
"lineEnding": "lf"
},
"linter": {
"enabled": false
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always"
}
},
"json": {
"formatter": {
"trailingCommas": "none"
}
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"assist": {
"enabled": false
}
}
+7 -3
View File
@@ -1,15 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"style": "radix-lyra",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "zinc",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
@@ -17,5 +19,7 @@
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
+5 -2
View File
@@ -1,3 +1,6 @@
project_id: "858252"
api_token: "env:CROWDIN_API_TOKEN"
files:
- source: /src/locales/en.json
translation: /src/locales/translated/%locale_with_underscore%.json
- source: /src/ui/locales/en.json
translation: /src/ui/locales/translated/%locale_with_underscore%.json
+17 -9
View File
@@ -1,5 +1,5 @@
# Stage 1: Install dependencies
FROM node:24-slim AS deps
FROM node:26-slim AS deps
WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
@@ -8,7 +8,10 @@ COPY package*.json ./
COPY .npmrc ./
COPY vendor ./vendor
COPY scripts/patch-guacamole-lite.cjs ./scripts/
RUN npm ci --ignore-scripts && \
node scripts/patch-guacamole-lite.cjs && \
npm cache clean --force
# Stage 2: Build frontend
@@ -33,7 +36,7 @@ RUN npm rebuild better-sqlite3
RUN npm run build:backend
# Stage 4: Production dependencies only
FROM node:24-slim AS production-deps
FROM node:26-slim AS production-deps
WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
@@ -42,19 +45,23 @@ COPY package*.json ./
COPY .npmrc ./
COPY vendor ./vendor
COPY scripts/patch-guacamole-lite.cjs ./scripts/
RUN npm ci --omit=dev --ignore-scripts && \
npm rebuild better-sqlite3 bcryptjs && \
node scripts/patch-guacamole-lite.cjs && \
npm rebuild better-sqlite3 bcryptjs ssh2 && \
npm cache clean --force
# Stage 5: Final optimized image
FROM node:24-slim
FROM node:26-slim
WORKDIR /app
ENV DATA_DIR=/app/data \
PORT=8080 \
NODE_ENV=production
NODE_ENV=production \
POSTHOG_API_KEY=phc_xM8UznirsFxUkGE68gH4jzeqevf4kh76wGw7Ci7hH2dd
RUN apt-get update && apt-get install -y nginx gettext-base openssl ca-certificates gosu wget && \
RUN apt-get update && apt-get install -y nginx gettext-base openssl ca-certificates gosu wget certbot python3-certbot-dns-cloudflare && \
update-ca-certificates && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /app/data /app/uploads /app/data/.opk /app/nginx /tmp/nginx && \
@@ -65,16 +72,17 @@ COPY docker/nginx.conf /app/nginx/nginx.conf.template
COPY docker/nginx-https.conf /app/nginx/nginx-https.conf.template
COPY --chown=node:node --from=frontend-builder /app/dist /app/html
COPY --chown=node:node --from=frontend-builder /app/src/locales /app/html/locales
COPY --chown=node:node --from=frontend-builder /app/public/fonts /app/html/fonts
COPY --chown=node:node --from=production-deps /app/node_modules /app/node_modules
COPY --chown=node:node --from=backend-builder /app/dist/backend ./dist/backend
COPY --chown=node:node package.json ./
# Schema for Postgres and MySQL. Unused by the default SQLite deployment, which
# builds its tables at startup instead.
COPY --chown=node:node drizzle ./drizzle
VOLUME ["/app/data"]
EXPOSE ${PORT} 30001 30002 30003 30004 30005 30006
EXPOSE ${PORT} 30001 30002 30003 30004 30005 30006 30007 30008 30009 30010 30011 30012
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -q -O /dev/null http://localhost:30001/health || exit 1
@@ -12,6 +12,9 @@ services:
environment:
PORT: "8080"
NODE_ENV: development
GUACD_HOST: "guacd-dev"
GUACD_TUNNEL_HOST: "termix-dev"
GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole"
depends_on:
- guacd-dev
networks:
@@ -21,6 +24,8 @@ services:
image: guacamole/guacd:1.6.0
container_name: guacd-dev
restart: unless-stopped
volumes:
- termix-dev-data:/termix-data
networks:
- termix-dev-net
+5 -2
View File
@@ -9,6 +9,9 @@ services:
- termix-data:/app/data
environment:
PORT: "8080"
GUACD_HOST: "guacd"
GUACD_TUNNEL_HOST: "termix"
GUACD_RECORDING_PATH: "/termix-data/session_recordings/guacamole"
depends_on:
- guacd
networks:
@@ -18,8 +21,8 @@ services:
image: guacamole/guacd:1.6.0
container_name: guacd
restart: unless-stopped
ports:
- "4822:4822"
volumes:
- termix-data:/termix-data
networks:
- termix-net
+16 -7
View File
@@ -14,7 +14,7 @@ if [ "$(id -u)" = "0" ]; then
groupmod -o -g "$PGID" node 2>/dev/null || true
usermod -o -u "$PUID" node 2>/dev/null || true
chown -R node:node /app/data /app/uploads /tmp/nginx 2>/dev/null || true
chown -R node:node /app/data /app/uploads /app/html /tmp/nginx 2>/dev/null || true
echo "User node is now UID: $PUID, GID: $PGID"
@@ -41,7 +41,7 @@ fi
mkdir -p /tmp/nginx
envsubst '${PORT} ${SSL_PORT} ${SSL_CERT_PATH} ${SSL_KEY_PATH}' < $NGINX_CONF_SOURCE > /tmp/nginx/nginx.conf
mkdir -p /app/data /app/uploads /app/data/.opk
mkdir -p /app/data /app/uploads /app/data/.opk /app/data/acme-webroot/.well-known/acme-challenge
chmod 755 /app/data /app/uploads /app/data/.opk 2>/dev/null || true
if [ -w /app/data ]; then
@@ -135,6 +135,19 @@ fi
echo "Starting nginx..."
nginx -c /tmp/nginx/nginx.conf
# Inject runtime BASE_PATH into frontend if configured
if [ -n "$BASE_PATH" ]; then
echo "Injecting BASE_PATH: $BASE_PATH"
# Strip trailing slash for use as a path prefix
CLEAN_BASE_PATH="${BASE_PATH%/}"
find /app/html -name "index.html" -exec sed -i "s|window.__TERMIX_BASE_PATH__ = \"\"|window.__TERMIX_BASE_PATH__ = \"$CLEAN_BASE_PATH\"|g" {} \;
# Patch sw.js static asset paths with the base path prefix
find /app/html -name "sw.js" -exec sed -i "s|__TERMIX_SW_BASE_PATH__|$CLEAN_BASE_PATH|g" {} \;
else
# No base path - replace placeholder with empty string so paths stay absolute from root
find /app/html -name "sw.js" -exec sed -i "s|__TERMIX_SW_BASE_PATH__||g" {} \;
fi
echo "Starting backend services..."
cd /app
export NODE_ENV=production
@@ -150,8 +163,4 @@ else
echo "Warning: package.json not found"
fi
node dist/backend/backend/starter.js
echo "All services started"
tail -f /dev/null
exec node dist/backend/backend/starter.js
+283 -48
View File
@@ -11,6 +11,8 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
access_log /tmp/nginx/access.log;
client_body_temp_path /tmp/nginx/client_body;
@@ -24,7 +26,11 @@ http {
client_header_timeout 300s;
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
@@ -58,16 +64,25 @@ http {
listen ${SSL_PORT} ssl;
server_name _;
absolute_redirect off;
ssl_certificate ${SSL_CERT_PATH};
ssl_certificate_key ${SSL_KEY_PATH};
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
location ^~ /.well-known/acme-challenge/ {
root /app/data/acme-webroot;
default_type "text/plain";
try_files $uri =404;
}
location = /sw.js {
root /app/html;
expires off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
@@ -75,31 +90,64 @@ http {
location = /manifest.json {
root /app/html;
expires off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ^~ /assets/ {
root /app/html;
expires 1y;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location ^~ /fonts/ {
root /app/html;
expires 1y;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location ^~ /icons/ {
root /app/html;
expires 30d;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=2592000" always;
try_files $uri =404;
}
location ~* ^/[^/]+\.(js|css|png|jpe?g|gif|ico|svg|webp|woff2?|ttf|eot)$ {
root /app/html;
expires 30d;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=2592000" always;
try_files $uri =404;
}
location ~* \.map$ {
access_log off;
log_not_found off;
return 404;
}
location / {
root /app/html;
index index.html index.htm;
expires off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri $uri/ /index.html;
}
location ~* \.map$ {
return 404;
access_log off;
log_not_found off;
}
location ~ ^/users/sessions(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
@@ -126,7 +174,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/releases(/.*)?$ {
@@ -135,7 +183,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alerts(/.*)?$ {
@@ -144,7 +192,34 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alert-rules(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/notification-channels(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alert-firings(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/rbac(/.*)?$ {
@@ -153,7 +228,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/credentials(/.*)?$ {
@@ -162,7 +237,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -170,6 +245,33 @@ http {
}
location ~ ^/snippets(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/vault(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/sync(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/termix-id(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
@@ -178,13 +280,34 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/proxmox(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
location ~ ^/c2s-tunnel-presets(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/audit-logs(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/terminal(/.*)?$ {
@@ -193,7 +316,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/open-tabs(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/user-preferences(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/database(/.*)?$ {
@@ -205,7 +346,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -224,7 +365,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -240,7 +381,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/quick-connect {
@@ -252,7 +393,7 @@ http {
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/host/opkssh-chooser(/.*)?$ {
@@ -267,7 +408,9 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location ~ ^/host/opkssh-callback(/.*)?$ {
@@ -282,7 +425,9 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location /host/ {
@@ -291,7 +436,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /session_logs/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /tailscale/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/websocket/ {
@@ -330,9 +493,9 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
@@ -350,7 +513,16 @@ http {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/session-sharing(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/tunnel/ {
@@ -359,7 +531,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/tunnel/ {
@@ -370,7 +542,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
@@ -383,7 +555,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/pinned {
@@ -392,7 +564,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/shortcuts {
@@ -401,7 +573,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/sudo-password {
@@ -410,13 +582,15 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/file_manager/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -424,7 +598,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -438,6 +612,8 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -445,7 +621,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -461,7 +637,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /health {
@@ -470,7 +646,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/status(/.*)?$ {
@@ -479,7 +655,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/metrics(/.*)?$ {
@@ -488,7 +664,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
@@ -501,7 +677,20 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/host-metrics(/.*)?$ {
proxy_pass http://127.0.0.1:30005;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}
location ~ ^/global-settings(/.*)?$ {
@@ -510,7 +699,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/uptime(/.*)?$ {
@@ -519,7 +708,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/activity(/.*)?$ {
@@ -528,16 +717,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/dashboard/preferences(/.*)?$ {
location ~ ^/service-links(/.*)?$ {
proxy_pass http://127.0.0.1:30006;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/homepage(/.*)?$ {
proxy_pass http://127.0.0.1:30012;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ^~ /docker/console/ {
@@ -551,9 +749,9 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
@@ -565,22 +763,59 @@ http {
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
# --- tmux-monitor begin ---
location ~ ^/tmux_monitor(/.*)?$ {
proxy_pass http://127.0.0.1:30010;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# --- tmux-monitor end ---
location ~ ^/docker(/.*)?$ {
proxy_pass http://127.0.0.1:30007;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location ^~ /serial/websocket/ {
proxy_pass http://127.0.0.1:30011/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /app/html;
internal;
}
}
}
+273 -49
View File
@@ -11,6 +11,8 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
access_log /tmp/nginx/access.log;
client_body_temp_path /tmp/nginx/client_body;
@@ -24,7 +26,11 @@ http {
client_header_timeout 300s;
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
@@ -49,14 +55,22 @@ http {
server {
listen ${PORT};
server_name localhost;
server_name _;
absolute_redirect off;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
location ^~ /.well-known/acme-challenge/ {
root /app/data/acme-webroot;
default_type "text/plain";
try_files $uri =404;
}
location = /sw.js {
root /app/html;
expires off;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
@@ -64,31 +78,58 @@ http {
location = /manifest.json {
root /app/html;
expires off;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri =404;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
location ^~ /assets/ {
root /app/html;
expires 1y;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location ^~ /fonts/ {
root /app/html;
expires 1y;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
location ^~ /icons/ {
root /app/html;
expires 30d;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=2592000" always;
try_files $uri =404;
}
location ~* ^/[^/]+\.(js|css|png|jpe?g|gif|ico|svg|webp|woff2?|ttf|eot)$ {
root /app/html;
expires 30d;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "public, max-age=2592000" always;
try_files $uri =404;
}
location ~* \.map$ {
access_log off;
log_not_found off;
return 404;
}
location / {
root /app/html;
index index.html index.htm;
expires off;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
try_files $uri $uri/ /index.html;
}
location ~* \.map$ {
return 404;
access_log off;
log_not_found off;
}
location ~ ^/users/sessions(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
@@ -115,7 +156,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/releases(/.*)?$ {
@@ -124,7 +165,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alerts(/.*)?$ {
@@ -133,7 +174,34 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alert-rules(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/notification-channels(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/alert-firings(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/rbac(/.*)?$ {
@@ -142,7 +210,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/credentials(/.*)?$ {
@@ -151,7 +219,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -159,6 +227,33 @@ http {
}
location ~ ^/snippets(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/vault(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/sync(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/termix-id(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
@@ -167,13 +262,34 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/proxmox(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
location ~ ^/c2s-tunnel-presets(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/audit-logs(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/terminal(/.*)?$ {
@@ -182,7 +298,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/open-tabs(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/user-preferences(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/database(/.*)?$ {
@@ -194,7 +328,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -213,7 +347,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -229,7 +363,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/quick-connect {
@@ -241,7 +375,7 @@ http {
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/host/opkssh-chooser(/.*)?$ {
@@ -256,7 +390,8 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location ~ ^/host/opkssh-callback(/.*)?$ {
@@ -271,7 +406,8 @@ http {
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
}
location /host/ {
@@ -280,7 +416,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /session_logs/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /tailscale/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/websocket/ {
@@ -319,9 +473,9 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
@@ -339,7 +493,16 @@ http {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/session-sharing(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/tunnel/ {
@@ -348,7 +511,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/tunnel/ {
@@ -359,7 +522,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
@@ -372,7 +535,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/pinned {
@@ -381,7 +544,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/shortcuts {
@@ -390,7 +553,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /host/file_manager/sudo-password {
@@ -399,13 +562,14 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /ssh/file_manager/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -413,7 +577,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -427,6 +591,7 @@ http {
client_max_body_size 5G;
client_body_timeout 300s;
add_header X-Content-Type-Options nosniff always;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
@@ -434,7 +599,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -450,7 +615,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location /health {
@@ -459,7 +624,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/status(/.*)?$ {
@@ -468,7 +633,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/metrics(/.*)?$ {
@@ -477,7 +642,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
@@ -490,7 +655,20 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/host-metrics(/.*)?$ {
proxy_pass http://127.0.0.1:30005;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}
location ~ ^/global-settings(/.*)?$ {
@@ -499,7 +677,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/uptime(/.*)?$ {
@@ -508,7 +686,7 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/activity(/.*)?$ {
@@ -517,16 +695,25 @@ http {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/dashboard/preferences(/.*)?$ {
location ~ ^/service-links(/.*)?$ {
proxy_pass http://127.0.0.1:30006;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ~ ^/homepage(/.*)?$ {
proxy_pass http://127.0.0.1:30012;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
}
location ^~ /docker/console/ {
@@ -540,9 +727,9 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
@@ -554,22 +741,59 @@ http {
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
# --- tmux-monitor begin ---
location ~ ^/tmux_monitor(/.*)?$ {
proxy_pass http://127.0.0.1:30010;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# --- tmux-monitor end ---
location ~ ^/docker(/.*)?$ {
proxy_pass http://127.0.0.1:30007;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location ^~ /serial/websocket/ {
proxy_pass http://127.0.0.1:30011/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /app/html;
internal;
}
}
}
+240
View File
@@ -0,0 +1,240 @@
# Database backends
Termix runs on SQLite by default. Postgres and MySQL are supported for
self-hosted deployments; this document records how the three differ, because the
differences are not only about SQL.
## This is multi-backend, not a migration
SQLite is not going away. The desktop app embeds its own backend and cannot ship
a database server, so it will always run on SQLite. Postgres and MySQL exist for
self-hosted deployments that need more than one process to reach the data —
multiple replicas, an external backup story, or an existing database estate.
Anything that assumes a single engine is wrong.
## Where the schema comes from
`src/backend/database/db/schema.ts` is the single source of truth, written
against `drizzle-orm/sqlite-core`.
`schema.pg.ts` and `schema.mysql.ts` are **generated** from it:
```bash
npm run schema:generate # rewrite the generated modules
npm run schema:check # fail if they are out of date (runs as part of lint)
```
Never edit the generated files. `npm run lint` fails if they drift from the
source, so a schema change that forgets to regenerate cannot reach main.
The transforms are mechanical:
| sqlite | postgres | mysql |
| ------------------------------------------------ | ----------------- | ----------------------- |
| `integer(…, { mode: "boolean" })` | `boolean` | `boolean` |
| `integer(…).primaryKey({ autoIncrement: true })` | `serial` | `int().autoincrement()` |
| `integer` | `integer` | `int` |
| `real` | `doublePrecision` | `double` |
| `text` used as a key | `varchar(255)` | `varchar(255)` |
A column becomes `varchar` if it is a primary key, is unique, or sits on either
end of a foreign key — MySQL cannot index an unbounded `TEXT`, and both sides of
a foreign key must agree.
## Durability
On SQLite the database is loaded into memory and serialised back to an encrypted
file, so every write needs an explicit flush. That is what the `onWrite` hook
each repository receives is for.
On Postgres and MySQL a committed write is already durable. No hook is installed
at all — see `needsExplicitPersist` in `db/dialect.ts`.
## Encryption: what changes, and what does not
This is the part most likely to be misread, so it is spelled out.
### Unchanged on every backend
**Field-level encryption still applies.** Credentials and other sensitive values
are encrypted in the application before they reach the database, under a
per-user data key:
- `ssh_data` — passwords, private keys, key passphrases, sudo/RDP/VNC/Telnet
secrets
- `ssh_credentials` — passwords, private and public keys
- `users` — TOTP secret and backup codes
- `vault_tokens`, `opkssh_tokens`, `termix_identity_ca` — certificates and keys
- `shared_host_secrets` — re-encrypted per recipient
Installation-level secrets — the OIDC client secret and LDAP bind password —
are encrypted under the system key, since they have no owning user and must be
readable during login.
This is the protection that matters most, and it is identical on all three
engines.
### Different on Postgres and MySQL
**Whole-file encryption does not exist.** On SQLite the database file itself is
encrypted at rest. There is no equivalent for a client-server engine: the data
lives in the server's storage, not in a file Termix owns.
Concretely, on Postgres/MySQL the following are readable by anyone with database
access, where on SQLite they were covered by the file encryption:
- host names, addresses, ports and usernames
- folder and snippet names, and **snippet contents**
- audit log entries
- session recording metadata and paths
- user names, roles and API key hashes
None of these are credentials — those stay encrypted — but together they
describe your estate.
**If you run Postgres or MySQL, encryption at rest is your responsibility**:
transparent data encryption, an encrypted volume, or an encrypted filesystem.
Termix does not provide it and cannot.
### Threat model, side by side
| | SQLite | Postgres / MySQL |
| ----------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------ |
| Stolen database file / volume | credentials encrypted, everything else encrypted | credentials encrypted, **rest depends on your storage encryption** |
| Database access without app access | credentials unreadable | credentials unreadable |
| Application compromise while a user is unlocked | that user's secrets readable | same |
| Backups | inherit file encryption | **plain unless you encrypt them** |
The second row is the point of field-level encryption, and it holds everywhere.
The first and last rows are where the backends genuinely differ.
## Running on Postgres or MySQL
Two variables. Unset, nothing changes and SQLite is used exactly as before.
```
DATABASE_DIALECT=postgres
DATABASE_URL=postgres://user:password@host:5432/termix
```
```
DATABASE_DIALECT=mysql
DATABASE_URL=mysql://user:password@host:3306/termix
```
`mariadb://` is accepted for MySQL. The scheme is checked against the dialect
before a connection is attempted, so a mismatch fails with a readable message
rather than a driver error deep in a stack.
Point it at an **empty** database. Migrations are applied at startup, from
`drizzle/postgres` or `drizzle/mysql`, and drizzle records what it has applied —
so several instances against one database are safe, and so is restarting.
There is no migration path from an existing SQLite database. Exporting one and
importing it into Postgres is not something this branch does.
### Docker
`drizzle/` ships in the image. A compose service needs only the two variables:
Added to the compose file in the README, that is one service and two variables:
```yaml
services:
termix:
image: ghcr.io/lukegus/termix:latest
environment:
PORT: "8080"
DATABASE_DIALECT: postgres
DATABASE_URL: postgres://termix:termix@db:5432/termix
depends_on:
- db
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: termix
POSTGRES_PASSWORD: termix
POSTGRES_DB: termix
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
```
`DATA_DIR` is still used for uploads and recordings on every backend. Only the
database itself moves.
## What is verified, and how
`npm run verify:dialect -- <url>` applies the migrations to an empty database and
drives the real repository classes against it, asserting values rather than the
absence of exceptions.
The repository test suite also runs against each engine:
```
TEST_DIALECT=postgres TEST_DATABASE_URL=<url> npx vitest run \
src/backend/tests/database/repositories --no-file-parallelism
```
CI runs both, against PostgreSQL 16 and MySQL 8 service containers. Eighteen
tests assert on bytes stored by the SQLite driver and skip on other engines;
they still run in the SQLite pass.
Tested against PostgreSQL 16 and MySQL 8. **MariaDB is not a substitute for
MySQL when testing** — it accepts DDL that MySQL 8 rejects, which has hidden a
real defect here more than once.
### What neither of them covers
Both harnesses build a `DatabaseContext` of their own, so neither runs
`createCurrentRepositoryContext()` — the one the application actually uses.
That gap hid a hardcoded `dialect: "sqlite"` in it: every engine reported
itself as SQLite at runtime while all three test passes stayed green, which on
MySQL meant `upsert` reached for `onConflictDoUpdate` and died with a
TypeError on the first write.
Anything the factory decides from the dialect needs its own test against the
factory. Asserting it through a hand-built context proves nothing about what
runs in production.
## Known limits
- The desktop app always uses SQLite. It embeds its own backend and cannot ship
a database server.
- Repositories import the SQLite table definitions on every engine. That is
correct — the query builder needs identifiers and value encoders, and those
agree — but it means `PortableDatabase` is a named approximation rather than a
guarantee. See `repositories/database-context.ts`.
- `getCurrentSettingValue` is a synchronous read. On Postgres and MySQL it comes
from a cache primed at startup and kept current by `SettingsRepository`,
because those drivers have no synchronous query.
That cache is per-process, so on a **multi-replica** deployment a setting
changed on one instance does not reach the others through the write path. Each
replica re-reads the settings table every 30 seconds
(`SETTINGS_CACHE_REFRESH_SECONDS`, 0 to disable), which does not make settings
immediately consistent — it bounds how long they can disagree. Changing a
setting takes effect on the replica that made the change at once, and on the
others within the interval.
- **Importing a backup is SQLite-only.** The restore writes tables in an order
that is not dependency-safe and relies on `PRAGMA foreign_keys = OFF`, which
has no equivalent here: Postgres needs superuser to disable triggers, and
MySQL's session-scoped switch is not guaranteed across a pool. It refuses with
a message rather than failing partway through and leaving a half-restored
database. Restore into Postgres or MySQL with their own tooling.
- **`LIKE` is case-insensitive on SQLite and case-sensitive on Postgres.** The
four places that use it match folder path prefixes and settings keys, so the
practical effect is that renaming a folder `prod` on SQLite also catches
`PROD / api` and on Postgres does not. Postgres is arguably the more correct
of the two; nothing was changed to make them agree, because that would alter
SQLite behaviour for existing deployments.
- The SQLite-era data migrations — legacy shared-credential cleanup, the
shared-host-secrets rebuild, per-user field-encryption backfill — do not run on
the other engines. A database created by the drizzle migrations never had the
shapes they repair.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>إدارة SSH ذاتية الاستضافة والوصول إلى سطح المكتب البعيد</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
العربية ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/) للمساعدة في تغطية تكاليف الخادم ووقت التطوير.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**مشاركة الجلسة:**
شارك جلسة طرفية أو RDP أو VNC أو Telnet مباشرة مع الآخرين في الوقت الفعلي. شارك عبر رابط (الانضمام بشكل مجهول، بدون الحاجة لحساب) أو مع مستخدم Termix محدد، واختر الوصول للقراءة فقط أو للقراءة والكتابة. يمكن أن تنتهي صلاحية المشاركات تلقائيًا أو يتم إلغاؤها في أي وقت، ويمكن تبديل مشاركة الجلسة عالميًا أو لكل مضيف على حدة.
</td>
<td width="50%" valign="top">
**تطبيق سطح مكتب مستقل + مزامنة ثنائية الاتجاه:**
يعمل تطبيق سطح المكتب Electron بشكل مستقل تمامًا مع خلفية وقاعدة بيانات محلية خاصة به، دون الحاجة لخادم. يمكن اختياريًا توصيله بخادم Termix عن بُعد للمزامنة التلقائية ثنائية الاتجاه للمضيفين وبيانات الاعتماد والمقتطفات والمزيد، واختيار ما إذا كانت اتصالات SSH تبدأ محليًا أو عبر الخادم البعيد.
</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 الغنية** - دعم مضيفات القفز، 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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>نسخة محمولة · مثبت MSI · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>نسخة محمولة · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## التثبيت
قم بزيارة [وثائق](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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## الدعم
إذا كنت بحاجة إلى مساعدة أو ترغب في طلب ميزة لـ Termix، قم بزيارة صفحة [المشكلات](https://github.com/Termix-SSH/Support/issues)، وسجل الدخول، واضغط على `New Issue`. يرجى أن تكون مفصلاً قدر الإمكان في مشكلتك، ويُفضَّل كتابتها باللغة الإنجليزية. يمكنك أيضاً الانضمام إلى خادم [Discord](https://discord.gg/jVQGdvHDrf) وزيارة قناة الدعم، ومع ذلك قد تكون أوقات الاستجابة أطول.
<br />
## لقطات الشاشة
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>شاهد نظرة عامة على التحديثات على YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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` لمزيد من المعلومات.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>自托管 SSH 管理与远程桌面访问平台</p>
<p>
<a href="../README.md">English</a> ·
中文 ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/)以帮助支付服务器费用和开发时间。
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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 隧道功能、远程文件管理以及许多其他工具。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 的服务器上查看 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 或 webhook 接收触发通知。在历史日志中查看触发和已解决的告警。
</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>
<tr>
<td width="50%" valign="top">
**会话共享:**
与他人实时共享终端、RDP、VNC 或 Telnet 会话。通过链接分享(匿名加入,无需帐户)或与特定的 Termix 用户分享,并选择只读或读写权限。共享可以自动过期或随时撤销,会话共享可以全局或按主机切换。
</td>
<td width="50%" valign="top">
**桌面独立运行 + 双向同步:**
Electron 桌面应用可完全独立运行,拥有自己的本地后端和数据库,无需服务器。也可以选择连接到远程 Termix 服务器,实现主机、凭据、代码片段等的自动双向同步,并选择 SSH 连接是在本地启动还是通过远程服务器启动。
</td>
</tr>
</table>
<br />
<details>
<summary><b>更多功能</b></summary>
<br />
- **仪表板** - 在仪表板上一目了然地查看服务器信息
- **API 密钥** - 创建带有到期日期的用户范围 API 密钥,用于自动化/CI
- **数据导出/导入** - 导出和导入 SSH 主机、凭据和文件管理器数据
- **自动 SSL 设置** - 内置 SSL 证书生成和管理,支持 HTTPS 重定向
- **现代 UI** - 使用 React、Tailwind CSS 和 Shadcn 构建的整洁的桌面/移动友好界面。有多种 UI 主题可选,包括浅色、深色、Dracula 等。使用 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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>便携版 · MSI 安装程序 · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>便携版 · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## 安装
访问 [Termix 文档](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)。
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## 支持
如果您需要 Termix 的帮助或想要请求功能,请访问 [Issues](https://github.com/Termix-SSH/Support/issues) 页面,登录并点击 `New Issue`。请尽可能详细地描述您的问题,建议使用英语。您也可以加入 [Discord](https://discord.gg/jVQGdvHDrf) 服务器并访问支持频道,但响应时间可能较长。
<br />
## 展示
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>在 YouTube 上观看更新概览</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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`
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Selbst gehostete SSH-Verwaltung und Remote-Desktop-Zugriff</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
Deutsch ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Sitzungsfreigabe:**
Teilen Sie eine Live-Terminal-, RDP-, VNC- oder Telnet-Sitzung in Echtzeit mit anderen. Freigabe uber einen Link (anonymer Beitritt, kein Konto erforderlich) oder mit einem bestimmten Termix-Benutzer, mit Wahl zwischen Nur-Lese- oder Lese-/Schreibzugriff. Freigaben konnen automatisch ablaufen oder jederzeit widerrufen werden, und die Sitzungsfreigabe kann global oder pro Host umgeschaltet werden.
</td>
<td width="50%" valign="top">
**Eigenstandiger Desktop + bidirektionale Synchronisierung:**
Die Electron-Desktop-App lauft vollstandig eigenstandig mit eigenem lokalem Backend und eigener Datenbank, kein Server erforderlich. Optional mit einem entfernten Termix-Server verbinden fur automatische bidirektionale Synchronisierung von Hosts, Zugangsdaten, Snippets und mehr, mit der Wahl, ob SSH-Verbindungen lokal oder uber den entfernten Server gestartet werden.
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portabel · MSI-Installationsprogramm · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portabel · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Installation
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Support
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.
<br />
## Screenshots
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Update-Ubersichten auf YouTube ansehen</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Gestión SSH autoalojada y acceso a escritorio remoto</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
Español ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Uso compartido de sesion:**
Comparte una sesion en vivo de terminal, RDP, VNC o Telnet con otras personas en tiempo real. Comparte mediante un enlace (se une de forma anonima, sin necesidad de cuenta) o con un usuario especifico de Termix, y elige acceso de solo lectura o de lectura y escritura. Las comparticiones pueden expirar automaticamente o revocarse en cualquier momento, y el uso compartido de sesiones se puede activar globalmente o por host.
</td>
<td width="50%" valign="top">
**Aplicacion de escritorio independiente + sincronizacion bidireccional:**
La aplicacion de escritorio Electron funciona de forma totalmente independiente con su propio backend y base de datos locales, sin necesidad de servidor. Opcionalmente, conectala a un servidor Termix remoto para sincronizacion bidireccional automatica de hosts, credenciales, fragmentos y mas, y elige si las conexiones SSH se inician localmente o a traves del servidor remoto.
</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.
</details>
<br />
## Soporte de Plataformas
<table align="center">
<tr>
<th align="center">Plataforma</th>
<th align="center">Distribucion</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Cualquier navegador moderno (Chrome, Safari, Firefox) · Soporte PWA</td>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable · Instalador MSI · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portable · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Instalacion
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Soporte
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.
<br />
## Capturas de Pantalla
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Ver resúmenes de actualizaciones en YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Gestion SSH auto-hebergee et acces bureau a distance</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
Français ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Partage de session:**
Partagez une session de terminal, RDP, VNC ou Telnet en direct avec d'autres personnes en temps reel. Partagez via un lien (rejoint anonymement, sans compte necessaire) ou avec un utilisateur Termix specifique, et choisissez un acces en lecture seule ou en lecture-ecriture. Les partages peuvent expirer automatiquement ou etre revoques a tout moment, et le partage de session peut etre active globalement ou par hote.
</td>
<td width="50%" valign="top">
**Application de bureau autonome + synchronisation bidirectionnelle:**
L'application de bureau Electron fonctionne de maniere totalement autonome avec son propre backend et sa propre base de donnees locale, sans serveur requis. Connectez-la eventuellement a un serveur Termix distant pour une synchronisation bidirectionnelle automatique des hotes, des identifiants, des extraits de code et plus encore, et choisissez si les connexions SSH sont demarrees localement ou via le serveur distant.
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable · MSI Installateur · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portable · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Installation
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Support
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.
<br />
## Captures d'ecran
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Regarder les aperçus des mises a jour sur YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>स्व-होस्टेड SSH प्रबंधन और रिमोट डेस्कटॉप एक्सेस</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
हिन्दी ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/)।
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**सेशन शेयरिंग:**
लाइव टर्मिनल, RDP, VNC, या Telnet सेशन को दूसरों के साथ रीयल टाइम में शेयर करें। लिंक के जरिए शेयर करें (गुमनाम रूप से जुड़ें, अकाउंट की जरूरत नहीं) या किसी खास Termix यूजर के साथ, और रीड-ओनली या रीड-राइट एक्सेस चुनें। शेयर अपने आप एक्सपायर हो सकते हैं या कभी भी रद्द किए जा सकते हैं, और सेशन शेयरिंग को ग्लोबली या प्रति होस्ट टॉगल किया जा सकता है।
</td>
<td width="50%" valign="top">
**डेस्कटॉप स्टैंडअलोन + 2-वे सिंक:**
Electron डेस्कटॉप ऐप अपने खुद के लोकल बैकएंड और डेटाबेस के साथ पूरी तरह से स्टैंडअलोन चलता है, किसी सर्वर की जरूरत नहीं। चाहें तो इसे किसी रिमोट Termix सर्वर से कनेक्ट करें ताकि होस्ट्स, क्रेडेंशियल्स, स्निपेट्स और अन्य चीज़ों का ऑटोमैटिक 2-वे सिंक हो सके, और चुनें कि SSH कनेक्शन लोकली शुरू हों या रिमोट सर्वर के जरिए।
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>पोर्टेबल · MSI इंस्टॉलर · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>पोर्टेबल · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## इंस्टॉलेशन
सभी प्लेटफ़ॉर्म पर पूर्ण इंस्टॉलेशन निर्देशों के लिए 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) पर ईमेल करें।
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## सहायता
यदि आपको सहायता चाहिए या Termix के लिए किसी विशेषता का अनुरोध करना चाहते हैं, तो [इश्यूज़](https://github.com/Termix-SSH/Support/issues) पेज पर जाएँ, लॉग इन करें, और `New Issue` दबाएँ। कृपया अपने इश्यू में यथासंभव विस्तृत विवरण दें, अधिमानतः अंग्रेज़ी में लिखें। आप [Discord](https://discord.gg/jVQGdvHDrf) सर्वर में भी शामिल हो सकते हैं और सहायता चैनल पर जा सकते हैं, हालाँकि, प्रतिक्रिया समय अधिक हो सकता है।
<br />
## स्क्रीनशॉट
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>YouTube पर अपडेट की समीक्षाएँ देखें</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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` देखें।
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Gestione SSH self-hosted e accesso al desktop remoto</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
Italiano
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Condivisione Sessione:**
Condividi una sessione di terminale, RDP, VNC o Telnet dal vivo con altri in tempo reale. Condividi tramite un link (accesso anonimo, nessun account necessario) o con un utente Termix specifico, e scegli l'accesso in sola lettura o lettura/scrittura. Le condivisioni possono scadere automaticamente o essere revocate in qualsiasi momento, e la condivisione della sessione puo essere attivata globalmente o per singolo host.
</td>
<td width="50%" valign="top">
**App Desktop Standalone + Sincronizzazione Bidirezionale:**
L'app desktop Electron funziona in modo completamente autonomo con il proprio backend e database locali, senza bisogno di un server. Facoltativamente, collegala a un server Termix remoto per la sincronizzazione bidirezionale automatica di host, credenziali, snippet e altro, scegliendo se le connessioni SSH vengono avviate localmente o tramite il server remoto.
</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
</details>
<br />
## Supporto Piattaforme
<table align="center">
<tr>
<th align="center">Piattaforma</th>
<th align="center">Distribuzione</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Qualsiasi browser moderno (Chrome, Safari, Firefox) · Supporto PWA</td>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable · Installer MSI · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portable · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Installazione
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Supporto
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.
<br />
## Screenshot
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Guarda le panoramiche degli aggiornamenti su YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>セルフホスト型 SSH 管理とリモートデスクトップアクセス</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
日本語 ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/)をご検討ください。
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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トンネリング機能、リモートファイル管理、およびその他多くのツールを提供します。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(アクセス制御付き)、2FATOTP)、パスキー(WebAuthn)対応による安全なユーザー管理。すべてのプラットフォームでアクティブなユーザーセッションを表示し、権限を取り消し可能。OIDC/ローカルアカウントの連携が可能です。すべてのユーザー操作の監査ログを表示できます。
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Tailscaleインテグレーション:**
Tailnetのデバイスをリストしてホストとしてすばやく追加し、Tailscale SSHを認証方法として使用して接続します。これにより、Tailnetの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またはwebhookで通知を受け取れます。発動中および解決済みのアラートを履歴ログで確認できます。
</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>
<tr>
<td width="50%" valign="top">
**セッション共有:**
ターミナル、RDP、VNC、Telnetのライブセッションを他のユーザーとリアルタイムで共有できます。リンクで共有(匿名参加、アカウント不要)するか、特定のTermixユーザーと共有し、読み取り専用または読み取り/書き込みアクセスを選択できます。共有は自動的に期限切れになるか、いつでも取り消すことができ、セッション共有はグローバルまたはホストごとに切り替えられます。
</td>
<td width="50%" valign="top">
**デスクトップスタンドアロン + 双方向同期:**
Electronデスクトップアプリは、独自のローカルバックエンドとデータベースを使用して完全にスタンドアロンで動作し、サーバーは不要です。オプションでリモートのTermixサーバーに接続し、ホスト、認証情報、スニペットなどの自動双方向同期を行い、SSH接続をローカルで開始するかリモートサーバー経由で開始するかを選択できます。
</td>
</tr>
</table>
<br />
<details>
<summary><b>その他の機能</b></summary>
<br />
- **ダッシュボード** - ダッシュボードでサーバー情報を一目で確認できます
- **APIキー** - 自動化/CI用に有効期限付きのユーザースコープAPIキーを作成できます
- **データのエクスポート/インポート** - SSHホスト、認証情報、ファイルマネージャーデータのエクスポートとインポートが可能です
- **自動SSL設定** - HTTPSリダイレクト付きの組み込みSSL証明書生成・管理が可能です
- **モダンUI** - React、Tailwind CSS、Shadcnで構築された、デスクトップ/モバイル対応のクリーンなインターフェース。ライト、ダーク、Draculaなど、多くの異なるUIテーマから選択可能。URLルートで任意の接続をフルスクリーンで開くことができます。
- **コマンド履歴** - 過去に実行したSSHコマンドの自動補完と表示が可能です
- **クイック接続** - 接続データを保存せずにサーバーに接続できます
- **コマンドパレット** - 左Shiftキーを2回押すことで、キーボードからSSH接続に素早くアクセスできます
- **Proxmox統合** - Proxmoxインスタンスからホストを自動的にTermixに追加できます
- **SSH機能充実** - ジャンプホスト、Warpgate、TOTPベースの接続、SOCKS5、ホストキー検証、パスワード自動入力、[OPKSSH](https://github.com/openpubkey/opkssh)、tmux、ポート敲き(port knocking)、ターミナルログ記録、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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>ポータブル版 · MSIインストーラー · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>ポータブル版 · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## インストール
すべてのプラットフォームへの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、Agentサポートなどの機能を構築するために必要な調査と学習の時間を確保することにも役立ちます。以下で進捗を確認し、寄付できます。
[寄付する](https://donate.termix.site/)
<br />
## スポンサー
開発を支援するための有料掲載にご興味がありますか?[mail@termix.site](mailto:mail@termix.site)までメールをお送りください。
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## サポート
Termixに関するヘルプや機能リクエストが必要な場合は、[Issues](https://github.com/Termix-SSH/Support/issues)ページにアクセスし、ログインして`New Issue`を押してください。Issueはできるだけ詳細に記述し、英語での記述が望ましいです。また、[Discord](https://discord.gg/jVQGdvHDrf)サーバーに参加してサポートチャンネルを利用することもできますが、応答時間が長くなる場合があります。
<br />
## スクリーンショット
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>YouTubeでアップデートの概要を視聴する</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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`をご覧ください。
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>셀프 호스팅 SSH 관리 및 원격 데스크톱 액세스</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
한국어 ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/)을 고려해 주세요.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**세션 공유:**
터미널, RDP, VNC, Telnet 세션을 다른 사람과 실시간으로 공유하세요. 링크를 통해 공유(계정 없이 익명으로 참여)하거나 특정 Termix 사용자와 공유할 수 있으며, 읽기 전용 또는 읽기/쓰기 권한을 선택할 수 있습니다. 공유는 자동으로 만료되거나 언제든지 취소될 수 있으며, 세션 공유는 전역 또는 호스트별로 전환할 수 있습니다.
</td>
<td width="50%" valign="top">
**데스크톱 독립 실행 + 양방향 동기화:**
Electron 데스크톱 앱은 자체 로컬 백엔드와 데이터베이스를 사용하여 서버 없이 완전히 독립적으로 실행됩니다. 선택적으로 원격 Termix 서버에 연결하여 호스트, 자격 증명, 스니펫 등을 자동으로 양방향 동기화하고, SSH 연결을 로컬에서 시작할지 원격 서버를 통해 시작할지 선택할 수 있습니다.
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>포터블 · MSI 설치 프로그램 · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>포터블 · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## 설치
모든 플랫폼에 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)로 이메일을 보내주세요.
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## 지원
Termix에 대한 도움이 필요하거나 기능을 요청하려면 [Issues](https://github.com/Termix-SSH/Support/issues) 페이지를 방문하여 로그인하고 `New Issue`를 누르세요. 이슈는 가능한 한 상세하게 작성하고, 영어로 작성하는 것이 좋습니다. [Discord](https://discord.gg/jVQGdvHDrf) 서버에 참여하여 지원 채널을 이용할 수도 있지만, 응답 시간이 더 길 수 있습니다.
<br />
## 스크린샷
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>YouTube에서 업데이트 개요 시청하기</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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`를 참조하세요.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Gerenciamento SSH auto-hospedado e acesso a area de trabalho remota</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
Português ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Compartilhamento de sessao:**
Compartilhe uma sessao de terminal, RDP, VNC ou Telnet ao vivo com outras pessoas em tempo real. Compartilhe por meio de um link (entrada anonima, sem necessidade de conta) ou com um usuario especifico do Termix, e escolha acesso somente leitura ou leitura/gravacao. Os compartilhamentos podem expirar automaticamente ou ser revogados a qualquer momento, e o compartilhamento de sessao pode ser ativado globalmente ou por host.
</td>
<td width="50%" valign="top">
**Aplicativo de desktop autonomo + sincronizacao bidirecional:**
O aplicativo de desktop Electron funciona de forma totalmente autonoma com seu proprio backend e banco de dados locais, sem necessidade de servidor. Opcionalmente, conecte-o a um servidor Termix remoto para sincronizacao bidirecional automatica de hosts, credenciais, snippets e muito mais, e escolha se as conexoes SSH sao iniciadas localmente ou por meio do servidor remoto.
</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.
</details>
<br />
## Suporte a Plataformas
<table align="center">
<tr>
<th align="center">Plataforma</th>
<th align="center">Distribuicao</th>
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Qualquer navegador moderno (Chrome, Safari, Firefox) · Suporte PWA</td>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portatil · Instalador MSI · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portatil · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Instalacao
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Suporte
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.
<br />
## Capturas de Tela
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Assista resumos de atualizacoes no YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Самостоятельно размещаемое управление SSH и доступ к удалённому рабочему столу</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
Русский ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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/) для покрытия расходов на серверы и время разработки.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Общий доступ к сеансу:**
Делитесь сеансом терминала, RDP, VNC или Telnet с другими в режиме реального времени. Делитесь по ссылке (анонимное присоединение, учетная запись не требуется) или с конкретным пользователем Termix, выбирая доступ только для чтения или для чтения и записи. Общий доступ может автоматически истекать или быть отозван в любое время, а общий доступ к сеансам можно включать глобально или для отдельного хоста.
</td>
<td width="50%" valign="top">
**Автономное настольное приложение + двусторонняя синхронизация:**
Настольное приложение на Electron полностью автономно, с собственным локальным бэкендом и базой данных, сервер не требуется. При желании подключите его к удаленному серверу Termix для автоматической двусторонней синхронизации хостов, учетных данных, сниппетов и прочего, и выберите, запускаются ли SSH-соединения локально или через удаленный сервер.
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Портативная версия · Установщик MSI · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Портативная версия · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Установка
Посетите [документацию](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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Поддержка
Если вам нужна помощь или вы хотите запросить новую функцию для Termix, посетите страницу [Проблемы](https://github.com/Termix-SSH/Support/issues), войдите в систему и нажмите `New Issue`. Пожалуйста, опишите вашу проблему как можно подробнее, предпочтительно на английском языке. Вы также можете присоединиться к серверу [Discord](https://discord.gg/jVQGdvHDrf) и обратиться в канал поддержки, однако время ответа может быть дольше.
<br />
## Скриншоты
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Смотрите обзоры обновлений на YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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`.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Kendi sunucunuzda barindirilan SSH yonetimi ve uzak masaustu erisimi</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
Türkçe ·
<a href="README-VI.md">Tiếng Việt</a> ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Kalici Sekmeler:**
Kullanici profilinde etkinlestirilmisse SSH oturumlari ve sekmeler cihazlar/yenilemeler arasinda acik kalir.
</td>
<td width="50%" valign="top">
**Diller:**
Yaklasik 30 dil icin yerlesik destek ([Crowdin](https://docs.termix.site/translations) tarafindan yonetilir).
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Oturum Paylasimi:**
Canli bir terminal, RDP, VNC veya Telnet oturumunu baskalariyla gercek zamanli olarak paylasin. Bir baglanti uzerinden (anonim olarak katilir, hesap gerekmez) veya belirli bir Termix kullanicisiyla paylasin ve salt okunur veya okuma/yazma erisimi secin. Paylasimlar otomatik olarak sona erebilir veya istediginiz zaman iptal edilebilir; oturum paylasimi genel olarak veya sunucu bazinda acilip kapatilabilir.
</td>
<td width="50%" valign="top">
**Bagimsiz Masaustu + Cift Yonlu Senkronizasyon:**
Electron masaustu uygulamasi, kendi yerel arka ucu ve veritabaniyla tamamen bagimsiz calisir, sunucu gerekmez. Istege bagli olarak sunucular, kimlik bilgileri, kod parcaciklari ve daha fazlasinin otomatik cift yonlu senkronizasyonu icin uzak bir Termix sunucusuna baglayin ve SSH baglantilarinin yerel olarak mi yoksa uzak sunucu uzerinden mi baslatilacagini secin.
</td>
</tr>
</table>
<br />
<details>
<summary><b>Daha fazla ozellik</b></summary>
<br />
- **Kontrol Paneli** - Kontrol panelinizde sunucu bilgilerini bir bakista goruntuleyin
- **API Anahtarlari** - Otomasyon/CI icin kullanilmak uzere son kullanma tarihleriyle kullanici kapsamli API anahtarlari olusturun
- **Veri Disa/Ice Aktarma** - SSH ana bilgisayarlarini, kimlik bilgilerini ve dosya yoneticisi verilerini disa ve ice aktarin
- **Otomatik SSL Kurulumu** - HTTPS yonlendirmeleriyle yerlesik SSL sertifika olusturma ve yonetimi
- **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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Tasınabilir · MSI Yukleyici · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Tasınabilir · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Kurulum
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.
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Destek
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.
<br />
## Ekran Goruntuleri
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>YouTube'da guncelleme ozetlerini izleyin</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<sub>Bazi videolar ve gorseller guncel olmayabilir veya ozellikleri tam olarak yansitmayabilir.</sub>
</div>
<br />
## Planlanan Ozellikler
Tum planlanan ozellikler icin [Projeler](https://github.com/orgs/Termix-SSH/projects/5) sayfasina bakin. Katkida bulunmak istiyorsaniz, [Katkida Bulunma](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md) sayfasina bakin.
<br />
## Lisans
Apache Lisansi Surumu 2.0 altinda dagitilmaktadir. Daha fazla bilgi icin `LICENSE` dosyasina bakin.
+430
View File
@@ -0,0 +1,430 @@
<div align="center">
<img src="../public/icon.svg" width="120" height="120" alt="Termix Logo" />
<h1>Termix</h1>
<p>Quan ly SSH tu luu tru va truy cap may tinh tu xa</p>
<p>
<a href="../README.md">English</a> ·
<a href="README-CN.md">中文</a> ·
<a href="README-JA.md">日本語</a> ·
<a href="README-KO.md">한국어</a> ·
<a href="README-FR.md">Français</a> ·
<a href="README-DE.md">Deutsch</a> ·
<a href="README-ES.md">Español</a> ·
<a href="README-PT.md">Português</a> ·
<a href="README-RU.md">Русский</a> ·
<a href="README-AR.md">العربية</a> ·
<a href="README-HI.md">हिन्दी</a> ·
<a href="README-TR.md">Türkçe</a> ·
Tiếng Việt ·
<a href="README-IT.md">Italiano</a>
</p>
<p>
<img src="https://img.shields.io/github/stars/Termix-SSH/Termix?style=flat&label=Stars&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/forks/Termix-SSH/Termix?style=flat&label=Forks&color=F39044&labelColor=1a1a1a" />
<img src="https://img.shields.io/github/v/release/Termix-SSH/Termix?style=flat&label=Release&color=F39044&labelColor=1a1a1a&v=1" />
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720?color=F39044&labelColor=1a1a1a" /></a>
<a href="https://donate.termix.site/"><img alt="Donate" src="https://img.shields.io/badge/Donate-Support%20Termix-F39044?style=flat&labelColor=1a1a1a" /></a>
</p>
<p>
<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.
<br />
<img src="../repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<p>
<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>
<tr>
<td width="50%" valign="top">
**Chia Se Phien:**
Chia se mot phien terminal, RDP, VNC, hoac Telnet truc tiep voi nguoi khac theo thoi gian thuc. Chia se qua lien ket (tham gia an danh, khong can tai khoan) hoac voi mot nguoi dung Termix cu the, va chon quyen truy cap chi doc hoac doc/ghi. Cac lien ket chia se co the tu dong het han hoac bi thu hoi bat cu luc nao, va tinh nang chia se phien co the duoc bat/tat toan cuc hoac theo tung host.
</td>
<td width="50%" valign="top">
**Ung Dung Desktop Doc Lap + Dong Bo 2 Chieu:**
Ung dung desktop Electron chay hoan toan doc lap voi backend va co so du lieu cuc bo rieng, khong can may chu. Tuy chon ket noi voi may chu Termix tu xa de tu dong dong bo 2 chieu cac host, thong tin dang nhap, doan ma va nhieu hon nua, va chon xem cac ket noi SSH duoc khoi tao cuc bo hay thong qua may chu tu xa.
</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>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable · MSI Installer · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/ia32</sub></td>
<td>Portable · AUR · AppImage · Deb · Flatpak</td>
</tr>
<tr>
<td align="center"><b>macOS</b> <sub>x64/ia32, v12.0+</sub></td>
<td>Apple App Store · DMG · Homebrew</td>
</tr>
<tr>
<td align="center"><b>iOS/iPadOS</b> <sub>v15.1+</sub></td>
<td>Apple App Store · IPA</td>
</tr>
<tr>
<td align="center"><b>Android</b> <sub>v7.0+</sub></td>
<td>Google Play Store · APK</td>
</tr>
</table>
<br />
## Cai Dat
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).
<div align="center">
<br />
<a href="https://www.digitalocean.com/">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" height="40" alt="DigitalOcean" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://crowdin.com/">
<img src="https://support.crowdin.com/assets/logos/core-logo/svg/crowdin-core-logo-cDark.svg" height="40" alt="Crowdin" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.blacksmith.sh/">
<img src="https://cdn.prod.website-files.com/681bfb0c9a4601bc6e288ec4/683ca9e2c5186757092611b8_e8cb22127df4da0811c4120a523722d2_logo-backsmith-wordmark-light.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://sirv.sirv.com/website/screenshots/cloudflare/cloudflare-logo.png?w=300" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://tailscale.com/">
<img src="https://drive.google.com/uc?export=view&id=1lIxkJuX6M23bW-2FElhT0rQieTrzaVSL" height="40" alt="Tailscale" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/8b/Akamai_logo.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Amazon_Web_Services_Logo.svg/960px-Amazon_Web_Services_Logo.svg.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://rackgenius.com/rackgenius-logo.png" height="40" alt="Rack Genius" />
</a>
</div>
<br />
## Ho Tro
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.
<br />
## Anh Chup Man Hinh
<div align="center">
<br />
[![YouTube](../repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Xem tong quan cap nhat tren YouTube</sub>
<br />
<br />
<table>
<tr>
<td><img src="../repo-images/Image 1.png" alt="Termix Screenshot 1" width="400" /></td>
<td><img src="../repo-images/Image 2.png" alt="Termix Screenshot 2" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 3.png" alt="Termix Screenshot 3" width="400" /></td>
<td><img src="../repo-images/Image 4.png" alt="Termix Screenshot 4" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 5.png" alt="Termix Screenshot 5" width="400" /></td>
<td><img src="../repo-images/Image 6.png" alt="Termix Screenshot 6" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 7.png" alt="Termix Screenshot 7" width="400" /></td>
<td><img src="../repo-images/Image 8.png" alt="Termix Screenshot 8" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 9.png" alt="Termix Screenshot 9" width="400" /></td>
<td><img src="../repo-images/Image 10.png" alt="Termix Screenshot 10" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 11.png" alt="Termix Screenshot 11" width="400" /></td>
<td><img src="../repo-images/Image 12.png" alt="Termix Screenshot 12" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 13.png" alt="Termix Screenshot 13" width="400" /></td>
<td><img src="../repo-images/Image 14.png" alt="Termix Screenshot 14" width="400" /></td>
</tr>
<tr>
<td><img src="../repo-images/Image 15.png" alt="Termix Screenshot 15" width="400" /></td>
<td><img src="../repo-images/Image 16.png" alt="Termix Screenshot 16" width="400" /></td>
</tr>
</table>
<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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "mysql",
schema: "./src/backend/database/db/schema.mysql.ts",
out: "./drizzle/mysql",
});
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./src/backend/database/db/schema.pg.ts",
out: "./drizzle/postgres",
});
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/backend/database/db/schema.ts",
out: "./drizzle/sqlite",
});
+890
View File
@@ -0,0 +1,890 @@
CREATE TABLE `alert_firings` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`rule_id` int NOT NULL,
`host_id` int NOT NULL,
`host_name` text NOT NULL,
`fired_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`resolved_at` text,
`value` double,
`message` text NOT NULL,
`severity` text NOT NULL DEFAULT ('warning'),
`acknowledged` boolean NOT NULL DEFAULT false,
CONSTRAINT `alert_firings_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `alert_rule_channels` (
`id` int AUTO_INCREMENT NOT NULL,
`rule_id` int NOT NULL,
`channel_id` int NOT NULL,
CONSTRAINT `alert_rule_channels_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `alert_rules` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int,
`name` varchar(255) NOT NULL,
`enabled` boolean NOT NULL DEFAULT true,
`trigger_type` text NOT NULL,
`threshold_value` double,
`threshold_duration_seconds` int,
`cooldown_minutes` int NOT NULL DEFAULT 15,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `alert_rules_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `api_keys` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`token_hash` text NOT NULL,
`token_prefix` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text,
`last_used_at` text,
`is_active` boolean NOT NULL DEFAULT true,
CONSTRAINT `api_keys_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `audit_logs` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255),
`username` text NOT NULL,
`action` text NOT NULL,
`resource_type` text NOT NULL,
`resource_id` text,
`resource_name` text,
`details` text,
`ip_address` text,
`user_agent` text,
`success` boolean NOT NULL,
`error_message` text,
`timestamp` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `audit_logs_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `c2s_tunnel_presets` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`config` text NOT NULL,
`platform` text,
`computer_name` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `c2s_tunnel_presets_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `command_history` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`command` text NOT NULL,
`executed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `command_history_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `dashboard_service_links` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`label` text NOT NULL,
`url` text NOT NULL,
`order` int NOT NULL DEFAULT 0,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `dashboard_service_links_id` PRIMARY KEY(`id`),
CONSTRAINT `dashboard_service_links_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `dismissed_alerts` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`alert_id` text NOT NULL,
`dismissed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `dismissed_alerts_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `file_manager_pinned` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`name` varchar(255) NOT NULL,
`path` text NOT NULL,
`pinned_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `file_manager_pinned_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `file_manager_recent` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`name` varchar(255) NOT NULL,
`path` text NOT NULL,
`last_opened` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `file_manager_recent_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `file_manager_shortcuts` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`name` varchar(255) NOT NULL,
`path` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `file_manager_shortcuts_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `homepage_items` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`type_id` text NOT NULL,
`title` text,
`config` text NOT NULL DEFAULT ('{}'),
`folder_id` int,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `homepage_items_id` PRIMARY KEY(`id`),
CONSTRAINT `homepage_items_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `homepage_layouts` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`layout` text NOT NULL DEFAULT ('{}'),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `homepage_layouts_id` PRIMARY KEY(`id`),
CONSTRAINT `homepage_layouts_user_id_unique` UNIQUE(`user_id`)
);
--> statement-breakpoint
CREATE TABLE `host_access` (
`id` int AUTO_INCREMENT NOT NULL,
`host_id` int NOT NULL,
`user_id` varchar(255),
`role_id` int,
`granted_by` varchar(255) NOT NULL,
`permission_level` text NOT NULL DEFAULT ('connect'),
`expires_at` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`last_accessed_at` text,
`access_count` int NOT NULL DEFAULT 0,
CONSTRAINT `host_access_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `host_health_checks` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`checks` text NOT NULL,
`interval_seconds` int NOT NULL DEFAULT 300,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `host_health_checks_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_host_health_checks_user_host` UNIQUE(`user_id`,`host_id`)
);
--> statement-breakpoint
CREATE TABLE `host_health_history` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`check_id` text NOT NULL,
`ts` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`ok` boolean NOT NULL,
`latency_ms` int,
`detail` text,
CONSTRAINT `host_health_history_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `host_metrics_history` (
`id` int AUTO_INCREMENT NOT NULL,
`host_id` int NOT NULL,
`ts` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`cpu_percent` double,
`mem_percent` double,
`disk_percent` double,
`net_rx_bytes` int,
`net_tx_bytes` int,
CONSTRAINT `host_metrics_history_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `host_metrics_preferences` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`layout` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `host_metrics_preferences_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_host_metrics_prefs_user_host` UNIQUE(`user_id`,`host_id`)
);
--> statement-breakpoint
CREATE TABLE `ssh_data` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`connection_type` text NOT NULL DEFAULT ('ssh'),
`name` varchar(255),
`ip` text NOT NULL,
`port` int NOT NULL,
`username` text NOT NULL,
`folder` text,
`tags` text,
`pin` boolean NOT NULL DEFAULT false,
`auth_type` text NOT NULL,
`use_warpgate` boolean NOT NULL DEFAULT false,
`share_ssh_auth` boolean NOT NULL DEFAULT false,
`force_keyboard_interactive` text,
`password` text,
`key` text,
`key_password` text,
`key_type` text,
`sudo_password` text,
`autostart_password` text,
`autostart_key` text,
`autostart_key_password` text,
`credential_id` int,
`override_credential_username` boolean,
`vault_profile_id` int,
`enable_terminal` boolean NOT NULL DEFAULT true,
`enable_session_logging` boolean NOT NULL DEFAULT true,
`allow_session_sharing` boolean NOT NULL DEFAULT true,
`enable_command_history` boolean NOT NULL DEFAULT true,
`enable_tunnel` boolean NOT NULL DEFAULT true,
`tunnel_connections` text,
`jump_hosts` text,
`enable_file_manager` boolean NOT NULL DEFAULT true,
`scp_legacy` boolean NOT NULL DEFAULT false,
`enable_docker` boolean NOT NULL DEFAULT false,
`enable_tmux_monitor` boolean NOT NULL DEFAULT false,
`show_terminal_in_sidebar` boolean NOT NULL DEFAULT true,
`show_file_manager_in_sidebar` boolean NOT NULL DEFAULT false,
`show_tunnel_in_sidebar` boolean NOT NULL DEFAULT false,
`show_docker_in_sidebar` boolean NOT NULL DEFAULT false,
`show_server_stats_in_sidebar` boolean NOT NULL DEFAULT false,
`default_path` text,
`stats_config` text,
`docker_config` text,
`enable_proxmox` boolean NOT NULL DEFAULT false,
`proxmox_config` text,
`terminal_config` text,
`quick_actions` text,
`notes` text,
`enable_ssh` boolean NOT NULL DEFAULT true,
`enable_rdp` boolean NOT NULL DEFAULT false,
`enable_vnc` boolean NOT NULL DEFAULT false,
`enable_telnet` boolean NOT NULL DEFAULT false,
`ssh_port` int DEFAULT 22,
`rdp_port` int DEFAULT 3389,
`vnc_port` int DEFAULT 5900,
`telnet_port` int DEFAULT 23,
`rdp_credential_id` int,
`rdp_user` text,
`rdp_password` text,
`rdp_domain` text,
`rdp_security` text,
`rdp_ignore_cert` boolean DEFAULT false,
`vnc_credential_id` int,
`vnc_password` text,
`vnc_user` text,
`telnet_user` text,
`telnet_password` text,
`telnet_credential_id` int,
`rdp_auth_type` text,
`vnc_auth_type` text,
`telnet_auth_type` text,
`domain` text,
`security` text,
`ignore_cert` boolean DEFAULT false,
`guacamole_config` text,
`use_socks5` boolean,
`socks5_host` text,
`socks5_port` int,
`socks5_username` text,
`socks5_password` text,
`socks5_proxy_chain` text,
`connection_origin` text,
`mac_address` text,
`wol_broadcast_address` text,
`port_knock_sequence` text,
`host_key_fingerprint` text,
`host_key_type` text,
`host_key_algorithm` text DEFAULT ('sha256'),
`host_key_first_seen` text,
`host_key_last_verified` text,
`host_key_changed_count` int DEFAULT 0,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ssh_data_id` PRIMARY KEY(`id`),
CONSTRAINT `ssh_data_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `network_topology` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`topology` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `network_topology_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `notification_channels` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`type` text NOT NULL,
`config` text NOT NULL,
`enabled` boolean NOT NULL DEFAULT true,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `notification_channels_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `opkssh_tokens` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`ssh_cert` text NOT NULL,
`private_key` text NOT NULL,
`email` text,
`sub` text,
`issuer` text,
`audience` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text NOT NULL,
`last_used` text,
CONSTRAINT `opkssh_tokens_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_opkssh_tokens_user_host` UNIQUE(`user_id`,`host_id`)
);
--> statement-breakpoint
CREATE TABLE `recent_activity` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`type` text NOT NULL,
`host_id` int NOT NULL,
`host_name` text,
`timestamp` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `recent_activity_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `roles` (
`id` int AUTO_INCREMENT NOT NULL,
`name` varchar(255) NOT NULL,
`display_name` text NOT NULL,
`description` text,
`is_system` boolean NOT NULL DEFAULT false,
`permissions` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `roles_id` PRIMARY KEY(`id`),
CONSTRAINT `roles_name_unique` UNIQUE(`name`)
);
--> statement-breakpoint
CREATE TABLE `session_recordings` (
`id` int AUTO_INCREMENT NOT NULL,
`host_id` int NOT NULL,
`user_id` varchar(255),
`username` text,
`access_id` int,
`started_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`ended_at` text,
`duration` int,
`commands` text,
`dangerous_actions` text,
`recording_path` text,
`protocol` varchar(255) NOT NULL DEFAULT 'ssh',
`format` text NOT NULL DEFAULT ('text'),
`terminated_by_owner` boolean DEFAULT false,
`termination_reason` text,
CONSTRAINT `session_recordings_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `session_share_participants` (
`id` int AUTO_INCREMENT NOT NULL,
`share_id` varchar(255) NOT NULL,
`user_id` varchar(255),
`guest_label` text,
`joined_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`left_at` text,
CONSTRAINT `session_share_participants_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `session_shares` (
`id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`owner_user_id` varchar(255) NOT NULL,
`protocol` varchar(255) NOT NULL,
`session_id` text NOT NULL,
`tab_instance_id` text,
`share_type` text NOT NULL,
`target_user_id` varchar(255),
`link_token` varchar(255),
`permission_level` text NOT NULL DEFAULT ('read-only'),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text NOT NULL,
`revoked_at` text,
`last_joined_at` text,
`join_count` int NOT NULL DEFAULT 0,
CONSTRAINT `session_shares_id` PRIMARY KEY(`id`),
CONSTRAINT `session_shares_link_token_unique` UNIQUE(`link_token`)
);
--> statement-breakpoint
CREATE TABLE `sessions` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`jwt_token` text NOT NULL,
`device_type` text NOT NULL,
`device_info` text NOT NULL,
`oidc_sub` text,
`oidc_sid` text,
`sso_provider_id` int,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text NOT NULL,
`last_active_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `sessions_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `settings` (
`key` varchar(255) NOT NULL,
`value` text NOT NULL,
CONSTRAINT `settings_key` PRIMARY KEY(`key`)
);
--> statement-breakpoint
CREATE TABLE `shared_host_auth_overrides` (
`id` int AUTO_INCREMENT NOT NULL,
`host_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`protocol` varchar(255) NOT NULL DEFAULT 'ssh',
`credential_id` int NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `shared_host_auth_overrides_id` PRIMARY KEY(`id`),
CONSTRAINT `shared_host_auth_overrides_host_user_protocol_unique` UNIQUE(`host_id`,`user_id`,`protocol`)
);
--> statement-breakpoint
CREATE TABLE `shared_host_secrets` (
`id` int AUTO_INCREMENT NOT NULL,
`host_access_id` int NOT NULL,
`target_user_id` varchar(255) NOT NULL,
`protocol` varchar(255) NOT NULL DEFAULT 'ssh',
`source_type` text NOT NULL DEFAULT ('credential'),
`original_credential_id` int,
`encrypted_username` text,
`encrypted_auth_type` text,
`encrypted_password` text,
`encrypted_key` text,
`encrypted_key_password` text,
`encrypted_key_type` text,
`encrypted_domain` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `shared_host_secrets_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_shared_host_secrets_scope` UNIQUE(`host_access_id`,`target_user_id`,`protocol`)
);
--> statement-breakpoint
CREATE TABLE `snippet_access` (
`id` int AUTO_INCREMENT NOT NULL,
`snippet_id` int NOT NULL,
`user_id` varchar(255),
`role_id` int,
`granted_by` varchar(255) NOT NULL,
`permission_level` text NOT NULL DEFAULT ('view'),
`expires_at` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `snippet_access_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `snippet_folders` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`color` text,
`icon` text,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `snippet_folders_id` PRIMARY KEY(`id`),
CONSTRAINT `snippet_folders_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `snippets` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`content` text NOT NULL,
`description` text,
`folder` text,
`order` int NOT NULL DEFAULT 0,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`host_filter` text,
CONSTRAINT `snippets_id` PRIMARY KEY(`id`),
CONSTRAINT `snippets_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `ssh_credential_usage` (
`id` int AUTO_INCREMENT NOT NULL,
`credential_id` int NOT NULL,
`host_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`used_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ssh_credential_usage_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `ssh_credentials` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`folder` text,
`tags` text,
`auth_type` text NOT NULL,
`username` text,
`password` text,
`key` text,
`private_key` text,
`public_key` text,
`key_password` text,
`key_type` text,
`detected_key_type` text,
`cert_public_key` text,
`usage_count` int NOT NULL DEFAULT 0,
`last_used` text,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ssh_credentials_id` PRIMARY KEY(`id`),
CONSTRAINT `ssh_credentials_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `ssh_folders` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`color` text,
`icon` text,
`credential_id` int,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ssh_folders_id` PRIMARY KEY(`id`),
CONSTRAINT `ssh_folders_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `sso_providers` (
`id` int AUTO_INCREMENT NOT NULL,
`name` varchar(255) NOT NULL,
`type` text NOT NULL,
`enabled` boolean NOT NULL DEFAULT true,
`display_order` int NOT NULL DEFAULT 0,
`config` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `sso_providers_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `sync_tombstones` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`entity_type` text NOT NULL,
`sync_id` varchar(255) NOT NULL,
`deleted_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `sync_tombstones_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `termix_identities` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`handle` varchar(255) NOT NULL,
`description` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `termix_identities_id` PRIMARY KEY(`id`),
CONSTRAINT `termix_identities_user_id_unique` UNIQUE(`user_id`),
CONSTRAINT `termix_identities_handle_unique` UNIQUE(`handle`)
);
--> statement-breakpoint
CREATE TABLE `termix_identity_ca` (
`id` int AUTO_INCREMENT NOT NULL,
`identity_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`public_key` text NOT NULL,
`private_key` text NOT NULL,
`validity_days` int NOT NULL DEFAULT 90,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `termix_identity_ca_id` PRIMARY KEY(`id`),
CONSTRAINT `termix_identity_ca_identity_id_unique` UNIQUE(`identity_id`)
);
--> statement-breakpoint
CREATE TABLE `termix_identity_keys` (
`id` int AUTO_INCREMENT NOT NULL,
`identity_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`public_key` text NOT NULL,
`key_type` text NOT NULL,
`algorithm` text NOT NULL,
`label` text,
`comment` text,
`source` text NOT NULL DEFAULT ('manual'),
`credential_id` int,
`enabled` boolean NOT NULL DEFAULT true,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `termix_identity_keys_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `tmux_session_tags` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`host_id` int NOT NULL,
`session_name` text NOT NULL,
`tag` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `tmux_session_tags_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `transfer_recent` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`source_host_id` int NOT NULL,
`dest_host_id` int NOT NULL,
`dest_path` text NOT NULL,
`dest_path_label` text NOT NULL,
`last_used` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `transfer_recent_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `trusted_devices` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`device_fingerprint` text NOT NULL,
`device_type` text NOT NULL,
`device_info` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text NOT NULL,
`last_used_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `trusted_devices_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `user_open_tabs` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`tab_type` text NOT NULL,
`host_id` int,
`label` text NOT NULL,
`tab_order` int NOT NULL DEFAULT 0,
`backend_session_id` text,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `user_open_tabs_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `user_preferences` (
`user_id` varchar(255) NOT NULL,
`reopen_tabs_on_login` boolean NOT NULL DEFAULT false,
`theme` text,
`font_size` text,
`accent_color` text,
`language` text,
`storage_mode` text,
`command_autocomplete` boolean,
`command_palette_enabled` boolean,
`show_host_tags` boolean,
`host_tray_on_click` boolean,
`pin_app_rail` boolean,
`expand_app_rail_on_hover` boolean,
`folders_collapsed` boolean,
`confirm_snippet_execution` boolean,
`disable_update_check` boolean,
`confirm_tab_close` boolean,
`hidden_rail_tabs` text,
`compact_host_view` boolean,
`status_color_scheme` text,
`custom_themes` text,
`custom_keybindings` text,
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `user_preferences_user_id` PRIMARY KEY(`user_id`)
);
--> statement-breakpoint
CREATE TABLE `user_roles` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`role_id` int NOT NULL,
`granted_by` varchar(255),
`granted_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `user_roles_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_user_roles_user_role` UNIQUE(`user_id`,`role_id`)
);
--> statement-breakpoint
CREATE TABLE `users` (
`id` varchar(255) NOT NULL,
`username` text NOT NULL,
`password_hash` text NOT NULL,
`is_admin` boolean NOT NULL DEFAULT false,
`is_oidc` boolean NOT NULL DEFAULT false,
`oidc_identifier` text,
`sso_provider_id` int,
`client_id` text,
`client_secret` text,
`issuer_url` text,
`authorization_url` text,
`token_url` text,
`identifier_path` text,
`name_path` text,
`scopes` text DEFAULT ('openid email profile'),
`totp_secret` text,
`totp_enabled` boolean NOT NULL DEFAULT false,
`totp_backup_codes` text,
`registered_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`donation_modal_dismissed` boolean NOT NULL DEFAULT false,
CONSTRAINT `users_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `vault_profiles` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`folder` text,
`tags` text,
`vault_addr` text NOT NULL,
`vault_namespace` text,
`oidc_mount` text,
`oidc_role` text,
`ssh_mount` text,
`ssh_role` text NOT NULL,
`valid_principals` text,
`key_type` text,
`shared` boolean NOT NULL DEFAULT false,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `vault_profiles_id` PRIMARY KEY(`id`),
CONSTRAINT `vault_profiles_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
CREATE TABLE `vault_tokens` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`profile_id` int NOT NULL,
`ssh_cert` text NOT NULL,
`private_key` text NOT NULL,
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`expires_at` text NOT NULL,
`last_used` text,
CONSTRAINT `vault_tokens_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_vault_tokens_user_profile` UNIQUE(`user_id`,`profile_id`)
);
--> statement-breakpoint
CREATE TABLE `webauthn_credentials` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`credential_id` text NOT NULL,
`public_key` text NOT NULL,
`counter` int NOT NULL DEFAULT 0,
`device_type` text,
`backed_up` boolean NOT NULL DEFAULT false,
`transports` text,
`user_verification` text NOT NULL DEFAULT ('preferred'),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`last_used_at` text,
CONSTRAINT `webauthn_credentials_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `alert_firings` ADD CONSTRAINT `alert_firings_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `alert_firings` ADD CONSTRAINT `alert_firings_rule_id_alert_rules_id_fk` FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `alert_rule_channels` ADD CONSTRAINT `alert_rule_channels_rule_id_alert_rules_id_fk` FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `alert_rule_channels` ADD CONSTRAINT `alert_rule_channels_channel_id_notification_channels_id_fk` FOREIGN KEY (`channel_id`) REFERENCES `notification_channels`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `alert_rules` ADD CONSTRAINT `alert_rules_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `alert_rules` ADD CONSTRAINT `alert_rules_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `api_keys` ADD CONSTRAINT `api_keys_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `audit_logs` ADD CONSTRAINT `audit_logs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `c2s_tunnel_presets` ADD CONSTRAINT `c2s_tunnel_presets_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `command_history` ADD CONSTRAINT `command_history_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `command_history` ADD CONSTRAINT `command_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `dashboard_service_links` ADD CONSTRAINT `dashboard_service_links_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `dismissed_alerts` ADD CONSTRAINT `dismissed_alerts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_pinned` ADD CONSTRAINT `file_manager_pinned_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_pinned` ADD CONSTRAINT `file_manager_pinned_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_recent` ADD CONSTRAINT `file_manager_recent_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_recent` ADD CONSTRAINT `file_manager_recent_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_shortcuts` ADD CONSTRAINT `file_manager_shortcuts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `file_manager_shortcuts` ADD CONSTRAINT `file_manager_shortcuts_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `homepage_items` ADD CONSTRAINT `homepage_items_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `homepage_layouts` ADD CONSTRAINT `homepage_layouts_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_access` ADD CONSTRAINT `host_access_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_access` ADD CONSTRAINT `host_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_access` ADD CONSTRAINT `host_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_access` ADD CONSTRAINT `host_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_health_checks` ADD CONSTRAINT `host_health_checks_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_health_checks` ADD CONSTRAINT `host_health_checks_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_health_history` ADD CONSTRAINT `host_health_history_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_health_history` ADD CONSTRAINT `host_health_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_metrics_history` ADD CONSTRAINT `host_metrics_history_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_metrics_preferences` ADD CONSTRAINT `host_metrics_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `host_metrics_preferences` ADD CONSTRAINT `host_metrics_preferences_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_vault_profile_id_vault_profiles_id_fk` FOREIGN KEY (`vault_profile_id`) REFERENCES `vault_profiles`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_rdp_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`rdp_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_vnc_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`vnc_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_telnet_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`telnet_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `network_topology` ADD CONSTRAINT `network_topology_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `notification_channels` ADD CONSTRAINT `notification_channels_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `opkssh_tokens` ADD CONSTRAINT `opkssh_tokens_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `opkssh_tokens` ADD CONSTRAINT `opkssh_tokens_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `recent_activity` ADD CONSTRAINT `recent_activity_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `recent_activity` ADD CONSTRAINT `recent_activity_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_recordings` ADD CONSTRAINT `session_recordings_access_id_host_access_id_fk` FOREIGN KEY (`access_id`) REFERENCES `host_access`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_share_participants` ADD CONSTRAINT `session_share_participants_share_id_session_shares_id_fk` FOREIGN KEY (`share_id`) REFERENCES `session_shares`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_share_participants` ADD CONSTRAINT `session_share_participants_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_owner_user_id_users_id_fk` FOREIGN KEY (`owner_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `session_shares` ADD CONSTRAINT `session_shares_target_user_id_users_id_fk` FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `sessions` ADD CONSTRAINT `sessions_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_auth_overrides` ADD CONSTRAINT `shared_host_auth_overrides_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_host_access_id_host_access_id_fk` FOREIGN KEY (`host_access_id`) REFERENCES `host_access`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_target_user_id_users_id_fk` FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_host_secrets` ADD CONSTRAINT `shared_host_secrets_original_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`original_credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_snippet_id_snippets_id_fk` FOREIGN KEY (`snippet_id`) REFERENCES `snippets`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippet_access` ADD CONSTRAINT `snippet_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippet_folders` ADD CONSTRAINT `snippet_folders_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `snippets` ADD CONSTRAINT `snippets_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_credential_usage` ADD CONSTRAINT `ssh_credential_usage_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_credentials` ADD CONSTRAINT `ssh_credentials_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_folders` ADD CONSTRAINT `ssh_folders_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ssh_folders` ADD CONSTRAINT `ssh_folders_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `sync_tombstones` ADD CONSTRAINT `sync_tombstones_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identities` ADD CONSTRAINT `termix_identities_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identity_ca` ADD CONSTRAINT `termix_identity_ca_identity_id_termix_identities_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identity_ca` ADD CONSTRAINT `termix_identity_ca_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_identity_id_termix_identities_id_fk` FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `termix_identity_keys` ADD CONSTRAINT `termix_identity_keys_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `tmux_session_tags` ADD CONSTRAINT `tmux_session_tags_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `tmux_session_tags` ADD CONSTRAINT `tmux_session_tags_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_source_host_id_ssh_data_id_fk` FOREIGN KEY (`source_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `transfer_recent` ADD CONSTRAINT `transfer_recent_dest_host_id_ssh_data_id_fk` FOREIGN KEY (`dest_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `trusted_devices` ADD CONSTRAINT `trusted_devices_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_open_tabs` ADD CONSTRAINT `user_open_tabs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_open_tabs` ADD CONSTRAINT `user_open_tabs_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_preferences` ADD CONSTRAINT `user_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_roles` ADD CONSTRAINT `user_roles_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `vault_profiles` ADD CONSTRAINT `vault_profiles_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `vault_tokens` ADD CONSTRAINT `vault_tokens_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `vault_tokens` ADD CONSTRAINT `vault_tokens_profile_id_vault_profiles_id_fk` FOREIGN KEY (`profile_id`) REFERENCES `vault_profiles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `webauthn_credentials` ADD CONSTRAINT `webauthn_credentials_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "mysql",
"entries": [
{
"idx": 0,
"version": "5",
"when": 1785738871436,
"tag": "0000_clean_pretty_boy",
"breakpoints": true
}
]
}
@@ -0,0 +1,837 @@
CREATE TABLE "alert_firings" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"rule_id" integer NOT NULL,
"host_id" integer NOT NULL,
"host_name" text NOT NULL,
"fired_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"resolved_at" text,
"value" double precision,
"message" text NOT NULL,
"severity" text DEFAULT 'warning' NOT NULL,
"acknowledged" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE "alert_rule_channels" (
"id" serial PRIMARY KEY NOT NULL,
"rule_id" integer NOT NULL,
"channel_id" integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE "alert_rules" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer,
"name" varchar(255) NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"trigger_type" text NOT NULL,
"threshold_value" double precision,
"threshold_duration_seconds" integer,
"cooldown_minutes" integer DEFAULT 15 NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "api_keys" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"token_hash" text NOT NULL,
"token_prefix" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text,
"last_used_at" text,
"is_active" boolean DEFAULT true NOT NULL
);
--> statement-breakpoint
CREATE TABLE "audit_logs" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255),
"username" text NOT NULL,
"action" text NOT NULL,
"resource_type" text NOT NULL,
"resource_id" text,
"resource_name" text,
"details" text,
"ip_address" text,
"user_agent" text,
"success" boolean NOT NULL,
"error_message" text,
"timestamp" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "c2s_tunnel_presets" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"config" text NOT NULL,
"platform" text,
"computer_name" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "command_history" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"command" text NOT NULL,
"executed_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "dashboard_service_links" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"label" text NOT NULL,
"url" text NOT NULL,
"order" integer DEFAULT 0 NOT NULL,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "dashboard_service_links_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "dismissed_alerts" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"alert_id" text NOT NULL,
"dismissed_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "file_manager_pinned" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"name" varchar(255) NOT NULL,
"path" text NOT NULL,
"pinned_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "file_manager_recent" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"name" varchar(255) NOT NULL,
"path" text NOT NULL,
"last_opened" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "file_manager_shortcuts" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"name" varchar(255) NOT NULL,
"path" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "homepage_items" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"type_id" text NOT NULL,
"title" text,
"config" text DEFAULT '{}' NOT NULL,
"folder_id" integer,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "homepage_items_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "homepage_layouts" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"layout" text DEFAULT '{}' NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "homepage_layouts_user_id_unique" UNIQUE("user_id")
);
--> statement-breakpoint
CREATE TABLE "host_access" (
"id" serial PRIMARY KEY NOT NULL,
"host_id" integer NOT NULL,
"user_id" varchar(255),
"role_id" integer,
"granted_by" varchar(255) NOT NULL,
"permission_level" text DEFAULT 'connect' NOT NULL,
"expires_at" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"last_accessed_at" text,
"access_count" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "host_health_checks" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"checks" text NOT NULL,
"interval_seconds" integer DEFAULT 300 NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "host_health_history" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"check_id" text NOT NULL,
"ts" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"ok" boolean NOT NULL,
"latency_ms" integer,
"detail" text
);
--> statement-breakpoint
CREATE TABLE "host_metrics_history" (
"id" serial PRIMARY KEY NOT NULL,
"host_id" integer NOT NULL,
"ts" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"cpu_percent" double precision,
"mem_percent" double precision,
"disk_percent" double precision,
"net_rx_bytes" integer,
"net_tx_bytes" integer
);
--> statement-breakpoint
CREATE TABLE "host_metrics_preferences" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"layout" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "ssh_data" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"connection_type" text DEFAULT 'ssh' NOT NULL,
"name" varchar(255),
"ip" text NOT NULL,
"port" integer NOT NULL,
"username" text NOT NULL,
"folder" text,
"tags" text,
"pin" boolean DEFAULT false NOT NULL,
"auth_type" text NOT NULL,
"use_warpgate" boolean DEFAULT false NOT NULL,
"share_ssh_auth" boolean DEFAULT false NOT NULL,
"force_keyboard_interactive" text,
"password" text,
"key" text,
"key_password" text,
"key_type" text,
"sudo_password" text,
"autostart_password" text,
"autostart_key" text,
"autostart_key_password" text,
"credential_id" integer,
"override_credential_username" boolean,
"vault_profile_id" integer,
"enable_terminal" boolean DEFAULT true NOT NULL,
"enable_session_logging" boolean DEFAULT true NOT NULL,
"allow_session_sharing" boolean DEFAULT true NOT NULL,
"enable_command_history" boolean DEFAULT true NOT NULL,
"enable_tunnel" boolean DEFAULT true NOT NULL,
"tunnel_connections" text,
"jump_hosts" text,
"enable_file_manager" boolean DEFAULT true NOT NULL,
"scp_legacy" boolean DEFAULT false NOT NULL,
"enable_docker" boolean DEFAULT false NOT NULL,
"enable_tmux_monitor" boolean DEFAULT false NOT NULL,
"show_terminal_in_sidebar" boolean DEFAULT true NOT NULL,
"show_file_manager_in_sidebar" boolean DEFAULT false NOT NULL,
"show_tunnel_in_sidebar" boolean DEFAULT false NOT NULL,
"show_docker_in_sidebar" boolean DEFAULT false NOT NULL,
"show_server_stats_in_sidebar" boolean DEFAULT false NOT NULL,
"default_path" text,
"stats_config" text,
"docker_config" text,
"enable_proxmox" boolean DEFAULT false NOT NULL,
"proxmox_config" text,
"terminal_config" text,
"quick_actions" text,
"notes" text,
"enable_ssh" boolean DEFAULT true NOT NULL,
"enable_rdp" boolean DEFAULT false NOT NULL,
"enable_vnc" boolean DEFAULT false NOT NULL,
"enable_telnet" boolean DEFAULT false NOT NULL,
"ssh_port" integer DEFAULT 22,
"rdp_port" integer DEFAULT 3389,
"vnc_port" integer DEFAULT 5900,
"telnet_port" integer DEFAULT 23,
"rdp_credential_id" integer,
"rdp_user" text,
"rdp_password" text,
"rdp_domain" text,
"rdp_security" text,
"rdp_ignore_cert" boolean DEFAULT false,
"vnc_credential_id" integer,
"vnc_password" text,
"vnc_user" text,
"telnet_user" text,
"telnet_password" text,
"telnet_credential_id" integer,
"rdp_auth_type" text,
"vnc_auth_type" text,
"telnet_auth_type" text,
"domain" text,
"security" text,
"ignore_cert" boolean DEFAULT false,
"guacamole_config" text,
"use_socks5" boolean,
"socks5_host" text,
"socks5_port" integer,
"socks5_username" text,
"socks5_password" text,
"socks5_proxy_chain" text,
"connection_origin" text,
"mac_address" text,
"wol_broadcast_address" text,
"port_knock_sequence" text,
"host_key_fingerprint" text,
"host_key_type" text,
"host_key_algorithm" text DEFAULT 'sha256',
"host_key_first_seen" text,
"host_key_last_verified" text,
"host_key_changed_count" integer DEFAULT 0,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "ssh_data_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "network_topology" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"topology" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "notification_channels" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"type" text NOT NULL,
"config" text NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "opkssh_tokens" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"ssh_cert" text NOT NULL,
"private_key" text NOT NULL,
"email" text,
"sub" text,
"issuer" text,
"audience" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text NOT NULL,
"last_used" text
);
--> statement-breakpoint
CREATE TABLE "recent_activity" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"type" text NOT NULL,
"host_id" integer NOT NULL,
"host_name" text,
"timestamp" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "roles" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(255) NOT NULL,
"display_name" text NOT NULL,
"description" text,
"is_system" boolean DEFAULT false NOT NULL,
"permissions" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "roles_name_unique" UNIQUE("name")
);
--> statement-breakpoint
CREATE TABLE "session_recordings" (
"id" serial PRIMARY KEY NOT NULL,
"host_id" integer NOT NULL,
"user_id" varchar(255),
"username" text,
"access_id" integer,
"started_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"ended_at" text,
"duration" integer,
"commands" text,
"dangerous_actions" text,
"recording_path" text,
"protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
"format" text DEFAULT 'text' NOT NULL,
"terminated_by_owner" boolean DEFAULT false,
"termination_reason" text
);
--> statement-breakpoint
CREATE TABLE "session_share_participants" (
"id" serial PRIMARY KEY NOT NULL,
"share_id" varchar(255) NOT NULL,
"user_id" varchar(255),
"guest_label" text,
"joined_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"left_at" text
);
--> statement-breakpoint
CREATE TABLE "session_shares" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"host_id" integer NOT NULL,
"owner_user_id" varchar(255) NOT NULL,
"protocol" varchar(255) NOT NULL,
"session_id" text NOT NULL,
"tab_instance_id" text,
"share_type" text NOT NULL,
"target_user_id" varchar(255),
"link_token" varchar(255),
"permission_level" text DEFAULT 'read-only' NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text NOT NULL,
"revoked_at" text,
"last_joined_at" text,
"join_count" integer DEFAULT 0 NOT NULL,
CONSTRAINT "session_shares_link_token_unique" UNIQUE("link_token")
);
--> statement-breakpoint
CREATE TABLE "sessions" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"jwt_token" text NOT NULL,
"device_type" text NOT NULL,
"device_info" text NOT NULL,
"oidc_sub" text,
"oidc_sid" text,
"sso_provider_id" integer,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text NOT NULL,
"last_active_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "settings" (
"key" varchar(255) PRIMARY KEY NOT NULL,
"value" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "shared_host_auth_overrides" (
"id" serial PRIMARY KEY NOT NULL,
"host_id" integer NOT NULL,
"user_id" varchar(255) NOT NULL,
"protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
"credential_id" integer NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "shared_host_secrets" (
"id" serial PRIMARY KEY NOT NULL,
"host_access_id" integer NOT NULL,
"target_user_id" varchar(255) NOT NULL,
"protocol" varchar(255) DEFAULT 'ssh' NOT NULL,
"source_type" text DEFAULT 'credential' NOT NULL,
"original_credential_id" integer,
"encrypted_username" text,
"encrypted_auth_type" text,
"encrypted_password" text,
"encrypted_key" text,
"encrypted_key_password" text,
"encrypted_key_type" text,
"encrypted_domain" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "snippet_access" (
"id" serial PRIMARY KEY NOT NULL,
"snippet_id" integer NOT NULL,
"user_id" varchar(255),
"role_id" integer,
"granted_by" varchar(255) NOT NULL,
"permission_level" text DEFAULT 'view' NOT NULL,
"expires_at" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "snippet_folders" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"color" text,
"icon" text,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "snippet_folders_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "snippets" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"content" text NOT NULL,
"description" text,
"folder" text,
"order" integer DEFAULT 0 NOT NULL,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"host_filter" text,
CONSTRAINT "snippets_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "ssh_credential_usage" (
"id" serial PRIMARY KEY NOT NULL,
"credential_id" integer NOT NULL,
"host_id" integer NOT NULL,
"user_id" varchar(255) NOT NULL,
"used_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "ssh_credentials" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"description" text,
"folder" text,
"tags" text,
"auth_type" text NOT NULL,
"username" text,
"password" text,
"key" text,
"private_key" text,
"public_key" text,
"key_password" text,
"key_type" text,
"detected_key_type" text,
"cert_public_key" text,
"usage_count" integer DEFAULT 0 NOT NULL,
"last_used" text,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "ssh_credentials_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "ssh_folders" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"color" text,
"icon" text,
"credential_id" integer,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "ssh_folders_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "sso_providers" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(255) NOT NULL,
"type" text NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"display_order" integer DEFAULT 0 NOT NULL,
"config" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "sync_tombstones" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"entity_type" text NOT NULL,
"sync_id" varchar(255) NOT NULL,
"deleted_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "termix_identities" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"handle" varchar(255) NOT NULL,
"description" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "termix_identities_user_id_unique" UNIQUE("user_id"),
CONSTRAINT "termix_identities_handle_unique" UNIQUE("handle")
);
--> statement-breakpoint
CREATE TABLE "termix_identity_ca" (
"id" serial PRIMARY KEY NOT NULL,
"identity_id" integer NOT NULL,
"user_id" varchar(255) NOT NULL,
"public_key" text NOT NULL,
"private_key" text NOT NULL,
"validity_days" integer DEFAULT 90 NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "termix_identity_ca_identity_id_unique" UNIQUE("identity_id")
);
--> statement-breakpoint
CREATE TABLE "termix_identity_keys" (
"id" serial PRIMARY KEY NOT NULL,
"identity_id" integer NOT NULL,
"user_id" varchar(255) NOT NULL,
"public_key" text NOT NULL,
"key_type" text NOT NULL,
"algorithm" text NOT NULL,
"label" text,
"comment" text,
"source" text DEFAULT 'manual' NOT NULL,
"credential_id" integer,
"enabled" boolean DEFAULT true NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "tmux_session_tags" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"host_id" integer NOT NULL,
"session_name" text NOT NULL,
"tag" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "transfer_recent" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"source_host_id" integer NOT NULL,
"dest_host_id" integer NOT NULL,
"dest_path" text NOT NULL,
"dest_path_label" text NOT NULL,
"last_used" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "trusted_devices" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"device_fingerprint" text NOT NULL,
"device_type" text NOT NULL,
"device_info" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text NOT NULL,
"last_used_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user_open_tabs" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"tab_type" text NOT NULL,
"host_id" integer,
"label" text NOT NULL,
"tab_order" integer DEFAULT 0 NOT NULL,
"backend_session_id" text,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user_preferences" (
"user_id" varchar(255) PRIMARY KEY NOT NULL,
"reopen_tabs_on_login" boolean DEFAULT false NOT NULL,
"theme" text,
"font_size" text,
"accent_color" text,
"language" text,
"storage_mode" text,
"command_autocomplete" boolean,
"command_palette_enabled" boolean,
"show_host_tags" boolean,
"host_tray_on_click" boolean,
"pin_app_rail" boolean,
"expand_app_rail_on_hover" boolean,
"folders_collapsed" boolean,
"confirm_snippet_execution" boolean,
"disable_update_check" boolean,
"confirm_tab_close" boolean,
"hidden_rail_tabs" text,
"compact_host_view" boolean,
"status_color_scheme" text,
"custom_themes" text,
"custom_keybindings" text,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user_roles" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"role_id" integer NOT NULL,
"granted_by" varchar(255),
"granted_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"username" text NOT NULL,
"password_hash" text NOT NULL,
"is_admin" boolean DEFAULT false NOT NULL,
"is_oidc" boolean DEFAULT false NOT NULL,
"oidc_identifier" text,
"sso_provider_id" integer,
"client_id" text,
"client_secret" text,
"issuer_url" text,
"authorization_url" text,
"token_url" text,
"identifier_path" text,
"name_path" text,
"scopes" text DEFAULT 'openid email profile',
"totp_secret" text,
"totp_enabled" boolean DEFAULT false NOT NULL,
"totp_backup_codes" text,
"registered_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"donation_modal_dismissed" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE "vault_profiles" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"description" text,
"folder" text,
"tags" text,
"vault_addr" text NOT NULL,
"vault_namespace" text,
"oidc_mount" text,
"oidc_role" text,
"ssh_mount" text,
"ssh_role" text NOT NULL,
"valid_principals" text,
"key_type" text,
"shared" boolean DEFAULT false NOT NULL,
"sync_id" varchar(255),
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT "vault_profiles_sync_id_unique" UNIQUE("sync_id")
);
--> statement-breakpoint
CREATE TABLE "vault_tokens" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"profile_id" integer NOT NULL,
"ssh_cert" text NOT NULL,
"private_key" text NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"expires_at" text NOT NULL,
"last_used" text
);
--> statement-breakpoint
CREATE TABLE "webauthn_credentials" (
"id" varchar(255) PRIMARY KEY NOT NULL,
"user_id" varchar(255) NOT NULL,
"name" varchar(255) NOT NULL,
"credential_id" text NOT NULL,
"public_key" text NOT NULL,
"counter" integer DEFAULT 0 NOT NULL,
"device_type" text,
"backed_up" boolean DEFAULT false NOT NULL,
"transports" text,
"user_verification" text DEFAULT 'preferred' NOT NULL,
"created_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
"last_used_at" text
);
--> statement-breakpoint
ALTER TABLE "alert_firings" ADD CONSTRAINT "alert_firings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "alert_firings" ADD CONSTRAINT "alert_firings_rule_id_alert_rules_id_fk" FOREIGN KEY ("rule_id") REFERENCES "public"."alert_rules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "alert_rule_channels" ADD CONSTRAINT "alert_rule_channels_rule_id_alert_rules_id_fk" FOREIGN KEY ("rule_id") REFERENCES "public"."alert_rules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "alert_rule_channels" ADD CONSTRAINT "alert_rule_channels_channel_id_notification_channels_id_fk" FOREIGN KEY ("channel_id") REFERENCES "public"."notification_channels"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "alert_rules" ADD CONSTRAINT "alert_rules_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "alert_rules" ADD CONSTRAINT "alert_rules_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "c2s_tunnel_presets" ADD CONSTRAINT "c2s_tunnel_presets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "command_history" ADD CONSTRAINT "command_history_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "command_history" ADD CONSTRAINT "command_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "dashboard_service_links" ADD CONSTRAINT "dashboard_service_links_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "dismissed_alerts" ADD CONSTRAINT "dismissed_alerts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_pinned" ADD CONSTRAINT "file_manager_pinned_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_pinned" ADD CONSTRAINT "file_manager_pinned_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_recent" ADD CONSTRAINT "file_manager_recent_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_recent" ADD CONSTRAINT "file_manager_recent_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_shortcuts" ADD CONSTRAINT "file_manager_shortcuts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "file_manager_shortcuts" ADD CONSTRAINT "file_manager_shortcuts_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "homepage_items" ADD CONSTRAINT "homepage_items_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "homepage_layouts" ADD CONSTRAINT "homepage_layouts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_access" ADD CONSTRAINT "host_access_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_access" ADD CONSTRAINT "host_access_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_access" ADD CONSTRAINT "host_access_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_access" ADD CONSTRAINT "host_access_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_health_checks" ADD CONSTRAINT "host_health_checks_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_health_checks" ADD CONSTRAINT "host_health_checks_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_health_history" ADD CONSTRAINT "host_health_history_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_health_history" ADD CONSTRAINT "host_health_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_metrics_history" ADD CONSTRAINT "host_metrics_history_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_metrics_preferences" ADD CONSTRAINT "host_metrics_preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "host_metrics_preferences" ADD CONSTRAINT "host_metrics_preferences_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_vault_profile_id_vault_profiles_id_fk" FOREIGN KEY ("vault_profile_id") REFERENCES "public"."vault_profiles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_rdp_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("rdp_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_vnc_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("vnc_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_data" ADD CONSTRAINT "ssh_data_telnet_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("telnet_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "network_topology" ADD CONSTRAINT "network_topology_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notification_channels" ADD CONSTRAINT "notification_channels_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "opkssh_tokens" ADD CONSTRAINT "opkssh_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "opkssh_tokens" ADD CONSTRAINT "opkssh_tokens_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "recent_activity" ADD CONSTRAINT "recent_activity_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "recent_activity" ADD CONSTRAINT "recent_activity_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_recordings" ADD CONSTRAINT "session_recordings_access_id_host_access_id_fk" FOREIGN KEY ("access_id") REFERENCES "public"."host_access"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_share_participants" ADD CONSTRAINT "session_share_participants_share_id_session_shares_id_fk" FOREIGN KEY ("share_id") REFERENCES "public"."session_shares"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_share_participants" ADD CONSTRAINT "session_share_participants_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session_shares" ADD CONSTRAINT "session_shares_target_user_id_users_id_fk" FOREIGN KEY ("target_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_auth_overrides" ADD CONSTRAINT "shared_host_auth_overrides_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_host_access_id_host_access_id_fk" FOREIGN KEY ("host_access_id") REFERENCES "public"."host_access"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_target_user_id_users_id_fk" FOREIGN KEY ("target_user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "shared_host_secrets" ADD CONSTRAINT "shared_host_secrets_original_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("original_credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_snippet_id_snippets_id_fk" FOREIGN KEY ("snippet_id") REFERENCES "public"."snippets"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippet_access" ADD CONSTRAINT "snippet_access_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippet_folders" ADD CONSTRAINT "snippet_folders_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "snippets" ADD CONSTRAINT "snippets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_credential_usage" ADD CONSTRAINT "ssh_credential_usage_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_credentials" ADD CONSTRAINT "ssh_credentials_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_folders" ADD CONSTRAINT "ssh_folders_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "ssh_folders" ADD CONSTRAINT "ssh_folders_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "sync_tombstones" ADD CONSTRAINT "sync_tombstones_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identities" ADD CONSTRAINT "termix_identities_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identity_ca" ADD CONSTRAINT "termix_identity_ca_identity_id_termix_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "public"."termix_identities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identity_ca" ADD CONSTRAINT "termix_identity_ca_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_identity_id_termix_identities_id_fk" FOREIGN KEY ("identity_id") REFERENCES "public"."termix_identities"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "termix_identity_keys" ADD CONSTRAINT "termix_identity_keys_credential_id_ssh_credentials_id_fk" FOREIGN KEY ("credential_id") REFERENCES "public"."ssh_credentials"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "tmux_session_tags" ADD CONSTRAINT "tmux_session_tags_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "tmux_session_tags" ADD CONSTRAINT "tmux_session_tags_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_source_host_id_ssh_data_id_fk" FOREIGN KEY ("source_host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "transfer_recent" ADD CONSTRAINT "transfer_recent_dest_host_id_ssh_data_id_fk" FOREIGN KEY ("dest_host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "trusted_devices" ADD CONSTRAINT "trusted_devices_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_open_tabs" ADD CONSTRAINT "user_open_tabs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_open_tabs" ADD CONSTRAINT "user_open_tabs_host_id_ssh_data_id_fk" FOREIGN KEY ("host_id") REFERENCES "public"."ssh_data"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_granted_by_users_id_fk" FOREIGN KEY ("granted_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "vault_profiles" ADD CONSTRAINT "vault_profiles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "vault_tokens" ADD CONSTRAINT "vault_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "vault_tokens" ADD CONSTRAINT "vault_tokens_profile_id_vault_profiles_id_fk" FOREIGN KEY ("profile_id") REFERENCES "public"."vault_profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "webauthn_credentials" ADD CONSTRAINT "webauthn_credentials_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "idx_host_health_checks_user_host" ON "host_health_checks" USING btree ("user_id","host_id");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_host_metrics_prefs_user_host" ON "host_metrics_preferences" USING btree ("user_id","host_id");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_opkssh_tokens_user_host" ON "opkssh_tokens" USING btree ("user_id","host_id");--> statement-breakpoint
CREATE UNIQUE INDEX "shared_host_auth_overrides_host_user_protocol_unique" ON "shared_host_auth_overrides" USING btree ("host_id","user_id","protocol");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_shared_host_secrets_scope" ON "shared_host_secrets" USING btree ("host_access_id","target_user_id","protocol");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_user_roles_user_role" ON "user_roles" USING btree ("user_id","role_id");--> statement-breakpoint
CREATE UNIQUE INDEX "idx_vault_tokens_user_profile" ON "vault_tokens" USING btree ("user_id","profile_id");
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1785738871078,
"tag": "0000_jazzy_infant_terrible",
"breakpoints": true
}
]
}
+836
View File
@@ -0,0 +1,836 @@
CREATE TABLE `alert_firings` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`rule_id` integer NOT NULL,
`host_id` integer NOT NULL,
`host_name` text NOT NULL,
`fired_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`resolved_at` text,
`value` real,
`message` text NOT NULL,
`severity` text DEFAULT 'warning' NOT NULL,
`acknowledged` integer DEFAULT false NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `alert_rule_channels` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`rule_id` integer NOT NULL,
`channel_id` integer NOT NULL,
FOREIGN KEY (`rule_id`) REFERENCES `alert_rules`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`channel_id`) REFERENCES `notification_channels`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `alert_rules` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer,
`name` text NOT NULL,
`enabled` integer DEFAULT true NOT NULL,
`trigger_type` text NOT NULL,
`threshold_value` real,
`threshold_duration_seconds` integer,
`cooldown_minutes` integer DEFAULT 15 NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `api_keys` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`token_hash` text NOT NULL,
`token_prefix` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text,
`last_used_at` text,
`is_active` integer DEFAULT true NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `audit_logs` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text,
`username` text NOT NULL,
`action` text NOT NULL,
`resource_type` text NOT NULL,
`resource_id` text,
`resource_name` text,
`details` text,
`ip_address` text,
`user_agent` text,
`success` integer NOT NULL,
`error_message` text,
`timestamp` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE TABLE `c2s_tunnel_presets` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`config` text NOT NULL,
`platform` text,
`computer_name` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `command_history` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`command` text NOT NULL,
`executed_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `dashboard_service_links` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`label` text NOT NULL,
`url` text NOT NULL,
`order` integer DEFAULT 0 NOT NULL,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `dashboard_service_links_sync_id_unique` ON `dashboard_service_links` (`sync_id`);--> statement-breakpoint
CREATE TABLE `dismissed_alerts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`alert_id` text NOT NULL,
`dismissed_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `file_manager_pinned` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`name` text NOT NULL,
`path` text NOT NULL,
`pinned_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `file_manager_recent` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`name` text NOT NULL,
`path` text NOT NULL,
`last_opened` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `file_manager_shortcuts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`name` text NOT NULL,
`path` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `homepage_items` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`type_id` text NOT NULL,
`title` text,
`config` text DEFAULT '{}' NOT NULL,
`folder_id` integer,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `homepage_items_sync_id_unique` ON `homepage_items` (`sync_id`);--> statement-breakpoint
CREATE TABLE `homepage_layouts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`layout` text DEFAULT '{}' NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `homepage_layouts_user_id_unique` ON `homepage_layouts` (`user_id`);--> statement-breakpoint
CREATE TABLE `host_access` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`host_id` integer NOT NULL,
`user_id` text,
`role_id` integer,
`granted_by` text NOT NULL,
`permission_level` text DEFAULT 'connect' NOT NULL,
`expires_at` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`last_accessed_at` text,
`access_count` integer DEFAULT 0 NOT NULL,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `host_health_checks` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`checks` text NOT NULL,
`interval_seconds` integer DEFAULT 300 NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_host_health_checks_user_host` ON `host_health_checks` (`user_id`,`host_id`);--> statement-breakpoint
CREATE TABLE `host_health_history` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`check_id` text NOT NULL,
`ts` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`ok` integer NOT NULL,
`latency_ms` integer,
`detail` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `host_metrics_history` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`host_id` integer NOT NULL,
`ts` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`cpu_percent` real,
`mem_percent` real,
`disk_percent` real,
`net_rx_bytes` integer,
`net_tx_bytes` integer,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `host_metrics_preferences` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`layout` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_host_metrics_prefs_user_host` ON `host_metrics_preferences` (`user_id`,`host_id`);--> statement-breakpoint
CREATE TABLE `ssh_data` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`connection_type` text DEFAULT 'ssh' NOT NULL,
`name` text,
`ip` text NOT NULL,
`port` integer NOT NULL,
`username` text NOT NULL,
`folder` text,
`tags` text,
`pin` integer DEFAULT false NOT NULL,
`auth_type` text NOT NULL,
`use_warpgate` integer DEFAULT false NOT NULL,
`share_ssh_auth` integer DEFAULT false NOT NULL,
`force_keyboard_interactive` text,
`password` text,
`key` text(8192),
`key_password` text,
`key_type` text,
`sudo_password` text,
`autostart_password` text,
`autostart_key` text(8192),
`autostart_key_password` text,
`credential_id` integer,
`override_credential_username` integer,
`vault_profile_id` integer,
`enable_terminal` integer DEFAULT true NOT NULL,
`enable_session_logging` integer DEFAULT true NOT NULL,
`allow_session_sharing` integer DEFAULT true NOT NULL,
`enable_command_history` integer DEFAULT true NOT NULL,
`enable_tunnel` integer DEFAULT true NOT NULL,
`tunnel_connections` text,
`jump_hosts` text,
`enable_file_manager` integer DEFAULT true NOT NULL,
`scp_legacy` integer DEFAULT false NOT NULL,
`enable_docker` integer DEFAULT false NOT NULL,
`enable_tmux_monitor` integer DEFAULT false NOT NULL,
`show_terminal_in_sidebar` integer DEFAULT true NOT NULL,
`show_file_manager_in_sidebar` integer DEFAULT false NOT NULL,
`show_tunnel_in_sidebar` integer DEFAULT false NOT NULL,
`show_docker_in_sidebar` integer DEFAULT false NOT NULL,
`show_server_stats_in_sidebar` integer DEFAULT false NOT NULL,
`default_path` text,
`stats_config` text,
`docker_config` text,
`enable_proxmox` integer DEFAULT false NOT NULL,
`proxmox_config` text,
`terminal_config` text,
`quick_actions` text,
`notes` text,
`enable_ssh` integer DEFAULT true NOT NULL,
`enable_rdp` integer DEFAULT false NOT NULL,
`enable_vnc` integer DEFAULT false NOT NULL,
`enable_telnet` integer DEFAULT false NOT NULL,
`ssh_port` integer DEFAULT 22,
`rdp_port` integer DEFAULT 3389,
`vnc_port` integer DEFAULT 5900,
`telnet_port` integer DEFAULT 23,
`rdp_credential_id` integer,
`rdp_user` text,
`rdp_password` text,
`rdp_domain` text,
`rdp_security` text,
`rdp_ignore_cert` integer DEFAULT false,
`vnc_credential_id` integer,
`vnc_password` text,
`vnc_user` text,
`telnet_user` text,
`telnet_password` text,
`telnet_credential_id` integer,
`rdp_auth_type` text,
`vnc_auth_type` text,
`telnet_auth_type` text,
`domain` text,
`security` text,
`ignore_cert` integer DEFAULT false,
`guacamole_config` text,
`use_socks5` integer,
`socks5_host` text,
`socks5_port` integer,
`socks5_username` text,
`socks5_password` text,
`socks5_proxy_chain` text,
`connection_origin` text,
`mac_address` text,
`wol_broadcast_address` text,
`port_knock_sequence` text,
`host_key_fingerprint` text,
`host_key_type` text,
`host_key_algorithm` text DEFAULT 'sha256',
`host_key_first_seen` text,
`host_key_last_verified` text,
`host_key_changed_count` integer DEFAULT 0,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`vault_profile_id`) REFERENCES `vault_profiles`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`rdp_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`vnc_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`telnet_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE UNIQUE INDEX `ssh_data_sync_id_unique` ON `ssh_data` (`sync_id`);--> statement-breakpoint
CREATE TABLE `network_topology` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`topology` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `notification_channels` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`type` text NOT NULL,
`config` text NOT NULL,
`enabled` integer DEFAULT true NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `opkssh_tokens` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`ssh_cert` text(8192) NOT NULL,
`private_key` text(8192) NOT NULL,
`email` text,
`sub` text,
`issuer` text,
`audience` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text NOT NULL,
`last_used` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_opkssh_tokens_user_host` ON `opkssh_tokens` (`user_id`,`host_id`);--> statement-breakpoint
CREATE TABLE `recent_activity` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`type` text NOT NULL,
`host_id` integer NOT NULL,
`host_name` text,
`timestamp` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `roles` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`display_name` text NOT NULL,
`description` text,
`is_system` integer DEFAULT false NOT NULL,
`permissions` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `roles_name_unique` ON `roles` (`name`);--> statement-breakpoint
CREATE TABLE `session_recordings` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`host_id` integer NOT NULL,
`user_id` text,
`username` text,
`access_id` integer,
`started_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`ended_at` text,
`duration` integer,
`commands` text,
`dangerous_actions` text,
`recording_path` text,
`protocol` text DEFAULT 'ssh' NOT NULL,
`format` text DEFAULT 'text' NOT NULL,
`terminated_by_owner` integer DEFAULT false,
`termination_reason` text,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`access_id`) REFERENCES `host_access`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE TABLE `session_share_participants` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`share_id` text NOT NULL,
`user_id` text,
`guest_label` text,
`joined_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`left_at` text,
FOREIGN KEY (`share_id`) REFERENCES `session_shares`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `session_shares` (
`id` text PRIMARY KEY NOT NULL,
`host_id` integer NOT NULL,
`owner_user_id` text NOT NULL,
`protocol` text NOT NULL,
`session_id` text NOT NULL,
`tab_instance_id` text,
`share_type` text NOT NULL,
`target_user_id` text,
`link_token` text,
`permission_level` text DEFAULT 'read-only' NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text NOT NULL,
`revoked_at` text,
`last_joined_at` text,
`join_count` integer DEFAULT 0 NOT NULL,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`owner_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `session_shares_link_token_unique` ON `session_shares` (`link_token`);--> statement-breakpoint
CREATE TABLE `sessions` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`jwt_token` text NOT NULL,
`device_type` text NOT NULL,
`device_info` text NOT NULL,
`oidc_sub` text,
`oidc_sid` text,
`sso_provider_id` integer,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text NOT NULL,
`last_active_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `settings` (
`key` text PRIMARY KEY NOT NULL,
`value` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `shared_host_auth_overrides` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`host_id` integer NOT NULL,
`user_id` text NOT NULL,
`protocol` text DEFAULT 'ssh' NOT NULL,
`credential_id` integer NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `shared_host_auth_overrides_host_user_protocol_unique` ON `shared_host_auth_overrides` (`host_id`,`user_id`,`protocol`);--> statement-breakpoint
CREATE TABLE `shared_host_secrets` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`host_access_id` integer NOT NULL,
`target_user_id` text NOT NULL,
`protocol` text DEFAULT 'ssh' NOT NULL,
`source_type` text DEFAULT 'credential' NOT NULL,
`original_credential_id` integer,
`encrypted_username` text,
`encrypted_auth_type` text,
`encrypted_password` text,
`encrypted_key` text(16384),
`encrypted_key_password` text,
`encrypted_key_type` text,
`encrypted_domain` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`host_access_id`) REFERENCES `host_access`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`target_user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`original_credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_shared_host_secrets_scope` ON `shared_host_secrets` (`host_access_id`,`target_user_id`,`protocol`);--> statement-breakpoint
CREATE TABLE `snippet_access` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`snippet_id` integer NOT NULL,
`user_id` text,
`role_id` integer,
`granted_by` text NOT NULL,
`permission_level` text DEFAULT 'view' NOT NULL,
`expires_at` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`snippet_id`) REFERENCES `snippets`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `snippet_folders` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`color` text,
`icon` text,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `snippet_folders_sync_id_unique` ON `snippet_folders` (`sync_id`);--> statement-breakpoint
CREATE TABLE `snippets` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`content` text NOT NULL,
`description` text,
`folder` text,
`order` integer DEFAULT 0 NOT NULL,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`host_filter` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `snippets_sync_id_unique` ON `snippets` (`sync_id`);--> statement-breakpoint
CREATE TABLE `ssh_credential_usage` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`credential_id` integer NOT NULL,
`host_id` integer NOT NULL,
`user_id` text NOT NULL,
`used_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `ssh_credentials` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`description` text,
`folder` text,
`tags` text,
`auth_type` text NOT NULL,
`username` text,
`password` text,
`key` text(16384),
`private_key` text(16384),
`public_key` text(4096),
`key_password` text,
`key_type` text,
`detected_key_type` text,
`cert_public_key` text(8192),
`usage_count` integer DEFAULT 0 NOT NULL,
`last_used` text,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `ssh_credentials_sync_id_unique` ON `ssh_credentials` (`sync_id`);--> statement-breakpoint
CREATE TABLE `ssh_folders` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`color` text,
`icon` text,
`credential_id` integer,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE UNIQUE INDEX `ssh_folders_sync_id_unique` ON `ssh_folders` (`sync_id`);--> statement-breakpoint
CREATE TABLE `sso_providers` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`type` text NOT NULL,
`enabled` integer DEFAULT true NOT NULL,
`display_order` integer DEFAULT 0 NOT NULL,
`config` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL
);
--> statement-breakpoint
CREATE TABLE `sync_tombstones` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`entity_type` text NOT NULL,
`sync_id` text NOT NULL,
`deleted_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `termix_identities` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`handle` text NOT NULL,
`description` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `termix_identities_user_id_unique` ON `termix_identities` (`user_id`);--> statement-breakpoint
CREATE UNIQUE INDEX `termix_identities_handle_unique` ON `termix_identities` (`handle`);--> statement-breakpoint
CREATE TABLE `termix_identity_ca` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`identity_id` integer NOT NULL,
`user_id` text NOT NULL,
`public_key` text(4096) NOT NULL,
`private_key` text(8192) NOT NULL,
`validity_days` integer DEFAULT 90 NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `termix_identity_ca_identity_id_unique` ON `termix_identity_ca` (`identity_id`);--> statement-breakpoint
CREATE TABLE `termix_identity_keys` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`identity_id` integer NOT NULL,
`user_id` text NOT NULL,
`public_key` text(8192) NOT NULL,
`key_type` text NOT NULL,
`algorithm` text NOT NULL,
`label` text,
`comment` text,
`source` text DEFAULT 'manual' NOT NULL,
`credential_id` integer,
`enabled` integer DEFAULT true NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`identity_id`) REFERENCES `termix_identities`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE TABLE `tmux_session_tags` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`host_id` integer NOT NULL,
`session_name` text NOT NULL,
`tag` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `transfer_recent` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`source_host_id` integer NOT NULL,
`dest_host_id` integer NOT NULL,
`dest_path` text NOT NULL,
`dest_path_label` text NOT NULL,
`last_used` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`source_host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`dest_host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `trusted_devices` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`device_fingerprint` text NOT NULL,
`device_type` text NOT NULL,
`device_info` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text NOT NULL,
`last_used_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `user_open_tabs` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`tab_type` text NOT NULL,
`host_id` integer,
`label` text NOT NULL,
`tab_order` integer DEFAULT 0 NOT NULL,
`backend_session_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `user_preferences` (
`user_id` text PRIMARY KEY NOT NULL,
`reopen_tabs_on_login` integer DEFAULT false NOT NULL,
`theme` text,
`font_size` text,
`accent_color` text,
`language` text,
`storage_mode` text,
`command_autocomplete` integer,
`command_palette_enabled` integer,
`show_host_tags` integer,
`host_tray_on_click` integer,
`pin_app_rail` integer,
`expand_app_rail_on_hover` integer,
`folders_collapsed` integer,
`confirm_snippet_execution` integer,
`disable_update_check` integer,
`confirm_tab_close` integer,
`hidden_rail_tabs` text,
`compact_host_view` integer,
`status_color_scheme` text,
`custom_themes` text,
`custom_keybindings` text,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `user_roles` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`role_id` integer NOT NULL,
`granted_by` text,
`granted_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_user_roles_user_role` ON `user_roles` (`user_id`,`role_id`);--> statement-breakpoint
CREATE TABLE `users` (
`id` text PRIMARY KEY NOT NULL,
`username` text NOT NULL,
`password_hash` text NOT NULL,
`is_admin` integer DEFAULT false NOT NULL,
`is_oidc` integer DEFAULT false NOT NULL,
`oidc_identifier` text,
`sso_provider_id` integer,
`client_id` text,
`client_secret` text,
`issuer_url` text,
`authorization_url` text,
`token_url` text,
`identifier_path` text,
`name_path` text,
`scopes` text DEFAULT 'openid email profile',
`totp_secret` text,
`totp_enabled` integer DEFAULT false NOT NULL,
`totp_backup_codes` text,
`registered_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`donation_modal_dismissed` integer DEFAULT false NOT NULL
);
--> statement-breakpoint
CREATE TABLE `vault_profiles` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`description` text,
`folder` text,
`tags` text,
`vault_addr` text NOT NULL,
`vault_namespace` text,
`oidc_mount` text,
`oidc_role` text,
`ssh_mount` text,
`ssh_role` text NOT NULL,
`valid_principals` text,
`key_type` text,
`shared` integer DEFAULT false NOT NULL,
`sync_id` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `vault_profiles_sync_id_unique` ON `vault_profiles` (`sync_id`);--> statement-breakpoint
CREATE TABLE `vault_tokens` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` text NOT NULL,
`profile_id` integer NOT NULL,
`ssh_cert` text(8192) NOT NULL,
`private_key` text(8192) NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`expires_at` text NOT NULL,
`last_used` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`profile_id`) REFERENCES `vault_profiles`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `idx_vault_tokens_user_profile` ON `vault_tokens` (`user_id`,`profile_id`);--> statement-breakpoint
CREATE TABLE `webauthn_credentials` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`name` text NOT NULL,
`credential_id` text NOT NULL,
`public_key` text NOT NULL,
`counter` integer DEFAULT 0 NOT NULL,
`device_type` text,
`backed_up` integer DEFAULT false NOT NULL,
`transports` text,
`user_verification` text DEFAULT 'preferred' NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`last_used_at` text,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1785738870735,
"tag": "0000_clever_hercules",
"breakpoints": true
}
]
}
+20 -14
View File
@@ -6,7 +6,12 @@
"output": "release"
},
"asar": true,
"asarUnpack": ["dist/backend/**/*", "node_modules/**/*"],
"asarUnpack": [
"dist/backend/**/*",
"node_modules/**/*",
"public/icons/**/*",
"public/icon.*"
],
"files": [
"dist/**/*",
"electron/**/*",
@@ -23,14 +28,12 @@
"!dist/icon-mac.png",
"!public/icon-mac.png",
"!dist/icon.ico",
"!public/icon.ico",
"!dist/icon.icns",
"!public/icon.icns",
"!dist/icons/**/*",
"!public/icons/**/*"
"!public/icon.icns"
],
"extraMetadata": {
"main": "electron/main.cjs"
"main": "electron/main.cjs",
"type": "commonjs"
},
"buildDependenciesFromSource": false,
"nodeGypRebuild": false,
@@ -55,6 +58,8 @@
"artifactName": "termix_windows_${arch}_nsis.${ext}",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"installerIcon": "public/icon.ico",
"uninstallerIcon": "public/icon.ico",
"shortcutName": "Termix",
"uninstallDisplayName": "Termix"
},
@@ -84,8 +89,8 @@
"desktop": {
"entry": {
"Name": "Termix",
"Comment": "A web-based server management platform",
"Keywords": "terminal;ssh;server;management;",
"Comment": "Self-hosted SSH and remote desktop management. ",
"Keywords": "docker;ssh;terminal;telnet;self-hosted;rdp;file-management;vnc;ssh-tunnel;server-stats;termix",
"StartupWMClass": "termix"
}
}
@@ -112,8 +117,8 @@
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.inherit.plist",
"entitlements": "packaging/build/entitlements.mac.plist",
"entitlementsInherit": "packaging/build/entitlements.mac.inherit.plist",
"type": "distribution",
"minimumSystemVersion": "10.15",
"mergeASARs": false,
@@ -124,11 +129,12 @@
"artifactName": "termix_macos_${arch}_dmg.${ext}",
"sign": true
},
"afterSign": "build/notarize.cjs",
"afterPack": "packaging/build/after-pack.cjs",
"afterSign": "packaging/build/notarize.cjs",
"mas": {
"provisioningProfile": "build/Termix_Mac_App_Store.provisionprofile",
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.inherit.plist",
"provisioningProfile": "packaging/build/Termix_Mac_App_Store.provisionprofile",
"entitlements": "packaging/build/entitlements.mas.plist",
"entitlementsInherit": "packaging/build/entitlements.mas.inherit.plist",
"hardenedRuntime": false,
"gatekeeperAssess": false,
"type": "distribution",
+8
View File
@@ -0,0 +1,8 @@
function getUnpackedAppRoot(appRoot) {
return appRoot.replace(
/app(-[a-z0-9]+)?\.asar(?!\.unpacked)/,
"app$1.asar.unpacked",
);
}
module.exports = { getUnpackedAppRoot };
+711 -142
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -31,6 +31,13 @@ contextBridge.exposeInMainWorld("electronAPI", {
startC2SAutoStartTunnels: () =>
ipcRenderer.invoke("start-c2s-autostart-tunnels"),
onRemoteSyncStatusChanged: (callback) => {
const listener = (_event, status) => callback(status);
ipcRenderer.on("remote-sync-status-changed", listener);
return () =>
ipcRenderer.removeListener("remote-sync-status-changed", listener);
},
clearSessionCookies: () => ipcRenderer.invoke("clear-session-cookies"),
getSessionCookie: (name, targetUrl) =>
ipcRenderer.invoke("get-session-cookie", name, targetUrl),
@@ -43,6 +50,29 @@ contextBridge.exposeInMainWorld("electronAPI", {
timeoutMs,
),
oidcSystemBrowserAuth: (authUrl, callbackPort) =>
ipcRenderer.invoke("oidc-system-browser-auth", authUrl, callbackPort),
openExternalEditor: (fileData) =>
ipcRenderer.invoke("open-external-editor", fileData),
closeExternalEditor: (editId) =>
ipcRenderer.invoke("close-external-editor", editId),
onExternalEditorSaved: (callback) => {
const listener = (_event, payload) => callback(payload);
ipcRenderer.on("external-editor-saved", listener);
return () => ipcRenderer.removeListener("external-editor-saved", listener);
},
showSaveDialog: (options) => ipcRenderer.invoke("show-save-dialog", options),
showOpenDialog: (options) => ipcRenderer.invoke("show-open-dialog", options),
createTempFile: (fileData) =>
ipcRenderer.invoke("create-temp-file", fileData),
createTempFolder: (folderData) =>
ipcRenderer.invoke("create-temp-folder", folderData),
startDragToDesktop: (dragData) =>
ipcRenderer.invoke("start-drag-to-desktop", dragData),
cleanupTempFile: (tempId) => ipcRenderer.invoke("cleanup-temp-file", tempId),
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
});
+15
View File
@@ -0,0 +1,15 @@
const SYNCED_ENTITY_TYPES = Object.freeze([
// Ordered by reference dependency: hosts and snippets resolve credential,
// vault and folder syncIds, so those have to exist on the other side first.
"sshCredentials",
"vaultProfiles",
"sshFolders",
"snippetFolders",
"hosts",
"snippets",
"dashboardServiceLinks",
"homepageItems",
"userPreferences",
]);
module.exports = { SYNCED_ENTITY_TYPES };
+573
View File
@@ -0,0 +1,573 @@
// Remote sync engine for the desktop app's optional connection to a
// self-hosted Termix server. Runs entirely in the Electron main process:
// - Holds the remote JWT (safeStorage-encrypted on disk, never exposed to
// the renderer's localStorage) and the local embedded backend's JWT
// (cached in memory only, handed over by the renderer at local-login
// time via notify-local-login).
// - On a timer, pulls + pushes each synced entity type between the
// embedded backend (always localhost:30001) and the configured remote
// server, reconciling by syncId with last-write-wins on updatedAt, and
// propagating tombstones (deletions) in both directions.
// - Pushes connection/sync status to the renderer via IPC so the Settings
// UI and a global banner can reflect it without polling.
const { app, safeStorage } = require("electron");
const fs = require("fs");
const path = require("path");
const { SYNCED_ENTITY_TYPES } = require("./remote-sync-entities.cjs");
const SYNC_INTERVAL_MS = 90 * 1000;
const EMBEDDED_BASE_URL = "http://127.0.0.1:30001";
function dataPath(filename) {
return path.join(app.getPath("userData"), filename);
}
function readJson(filePath, fallback) {
try {
if (!fs.existsSync(filePath)) return fallback;
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch {
return fallback;
}
}
function writeJson(filePath, value) {
const userDataPath = app.getPath("userData");
if (!fs.existsSync(userDataPath)) {
fs.mkdirSync(userDataPath, { recursive: true });
}
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
}
function getDesktopSettingsPath() {
return dataPath("desktop-settings.json");
}
function getRemoteSyncConfigPath() {
return dataPath("remote-sync-config.json");
}
function getRemoteSyncCredentialPath() {
return dataPath("remote-sync-credential.json");
}
function getRemoteSyncStatePath() {
return dataPath("remote-sync-state.json");
}
function getDesktopSettings() {
return readJson(getDesktopSettingsPath(), {
defaultConnectionOrigin: "local",
migrationNoticeAcknowledged: false,
});
}
function saveDesktopSettings(settings) {
writeJson(getDesktopSettingsPath(), settings);
return { success: true };
}
function getRemoteSyncConfig() {
return readJson(getRemoteSyncConfigPath(), null);
}
function saveRemoteSyncConfig(config) {
writeJson(getRemoteSyncConfigPath(), config);
return { success: true };
}
function clearRemoteSyncConfig() {
try {
fs.unlinkSync(getRemoteSyncConfigPath());
} catch {
// already absent
}
return { success: true };
}
function getSafeStorageAvailable() {
try {
return safeStorage.isEncryptionAvailable();
} catch {
return false;
}
}
function saveRemoteSyncJwt(token) {
if (!getSafeStorageAvailable()) {
return { success: false, error: "Encryption unavailable on this system" };
}
writeJson(getRemoteSyncCredentialPath(), {
encrypted: true,
value: safeStorage.encryptString(token).toString("base64"),
obtainedAt: new Date().toISOString(),
});
return { success: true };
}
function getRemoteSyncJwt() {
const record = readJson(getRemoteSyncCredentialPath(), null);
if (!record?.encrypted || !getSafeStorageAvailable()) return null;
try {
return safeStorage.decryptString(Buffer.from(record.value, "base64"));
} catch {
return null;
}
}
function clearRemoteSyncJwt() {
try {
fs.unlinkSync(getRemoteSyncCredentialPath());
} catch {
// already absent
}
return { success: true };
}
async function getRemoteSyncUserInfo() {
const config = getRemoteSyncConfig();
const token = getRemoteSyncJwt();
if (!config?.serverUrl || !token || isJwtExpiredOrExpiringSoon(token)) {
return null;
}
const baseUrl = config.serverUrl.replace(/\/$/, "");
const userResponse = await fetch(`${baseUrl}/users/me`, {
headers: { Authorization: `Bearer ${token}`, "X-Electron-App": "true" },
});
if (!userResponse.ok) return null;
const user = await userResponse.json();
const rolesResponse = await fetch(
`${baseUrl}/rbac/users/${encodeURIComponent(user.userId)}/roles`,
{
headers: { Authorization: `Bearer ${token}`, "X-Electron-App": "true" },
},
);
const roles = rolesResponse.ok
? (await rolesResponse.json()).roles || []
: [];
return {
userId: user.userId,
username: user.username,
is_admin: !!user.is_admin,
is_oidc: !!user.is_oidc,
is_dual_auth: !!user.is_dual_auth,
totp_enabled: !!user.totp_enabled,
roles,
};
}
function decodeJwtExpiry(token) {
try {
const payloadB64 = token.split(".")[1];
const payload = JSON.parse(
Buffer.from(payloadB64, "base64").toString("utf8"),
);
return typeof payload.exp === "number" ? payload.exp * 1000 : null;
} catch {
return null;
}
}
function isJwtExpiredOrExpiringSoon(token, marginMs = 60 * 1000) {
const expiresAt = decodeJwtExpiry(token);
if (expiresAt === null) return false;
return Date.now() + marginMs >= expiresAt;
}
class RemoteSyncEngine {
constructor(getMainWindow) {
this.getMainWindow = getMainWindow;
this.localJwt = null;
this.timer = null;
this.syncing = false;
this.status = {
connected: false,
syncing: false,
lastSyncedAt: null,
lastError: null,
needsReauth: false,
};
}
setLocalJwt(token) {
this.localJwt = token || null;
}
emitStatus() {
const win = this.getMainWindow?.();
if (!win || win.isDestroyed()) return;
win.webContents.send("remote-sync-status-changed", this.status);
}
updateStatus(patch) {
this.status = { ...this.status, ...patch };
this.emitStatus();
}
start() {
const config = getRemoteSyncConfig();
this.status.connected = !!config?.serverUrl;
if (this.timer) clearInterval(this.timer);
this.timer = setInterval(() => this.syncNow(), SYNC_INTERVAL_MS);
if (config?.serverUrl) {
// Fire an initial sync shortly after startup rather than waiting a
// full interval, but don't block app boot on it.
setTimeout(() => this.syncNow(), 5000);
}
}
stop() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
async syncNow() {
if (this.syncing) return this.status;
const config = getRemoteSyncConfig();
if (!config?.serverUrl) {
this.updateStatus({ connected: false, syncing: false });
return this.status;
}
const remoteJwt = getRemoteSyncJwt();
if (!remoteJwt) {
this.updateStatus({
connected: true,
syncing: false,
needsReauth: true,
lastError: "Not signed in to remote server",
});
return this.status;
}
if (isJwtExpiredOrExpiringSoon(remoteJwt)) {
this.updateStatus({
connected: true,
syncing: false,
needsReauth: true,
lastError: "Remote session expired",
});
return this.status;
}
if (!this.localJwt) {
// Local login hasn't handed us a token yet -- this is expected for the
// first tick or two right after a cold boot (renderer hasn't finished
// its own session check yet), but if it never arrives (e.g. a gap in
// whichever code path establishes the local session), sync would
// otherwise silently no-op forever with no visible error. Surface it
// as a normal, non-alarming "not synced yet" status rather than
// leaving lastSyncedAt/lastError untouched.
this.updateStatus({
connected: true,
syncing: false,
lastError: "Waiting for local session",
});
return this.status;
}
this.syncing = true;
this.updateStatus({ connected: true, syncing: true, lastError: null });
try {
const state = readJson(getRemoteSyncStatePath(), { entities: {} });
let sawAuthFailure = false;
for (const entityType of SYNCED_ENTITY_TYPES) {
const entityState = state.entities[entityType] || {
lastPulledAt: null,
lastPushedAt: null,
};
const result = await this.syncEntity({
entityType,
remoteBaseUrl: config.serverUrl.replace(/\/$/, ""),
remoteJwt,
since: entityState.lastPulledAt,
});
if (result.authFailure) {
sawAuthFailure = true;
break;
}
state.entities[entityType] = {
lastPulledAt: result.syncedAt,
lastPushedAt: result.syncedAt,
};
}
if (sawAuthFailure) {
this.updateStatus({
syncing: false,
needsReauth: true,
lastError: "Remote server rejected the session",
});
return this.status;
}
writeJson(getRemoteSyncStatePath(), state);
writeJson(getRemoteSyncConfigPath(), {
...config,
lastSyncedAt: new Date().toISOString(),
lastSyncStatus: "ok",
lastSyncError: null,
});
this.updateStatus({
connected: true,
syncing: false,
needsReauth: false,
lastSyncedAt: new Date().toISOString(),
lastError: null,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
writeJson(getRemoteSyncConfigPath(), {
...config,
lastSyncStatus: "error",
lastSyncError: message,
});
this.updateStatus({ syncing: false, lastError: message });
} finally {
this.syncing = false;
}
return this.status;
}
async fetchJson(url, token, options = {}) {
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...(options.headers || {}),
},
});
if (res.status === 401 || res.status === 403) {
const err = new Error(`Auth failed (${res.status})`);
err.authFailure = true;
throw err;
}
if (!res.ok) {
throw new Error(`Request failed (${res.status}): ${url}`);
}
const text = await res.text();
// A reverse-proxy SSO in front of the remote server (Pangolin, Authelia,
// etc.) can intercept even an authenticated, Bearer-token'd request and
// serve its own login page instead of forwarding to Termix -- that comes
// back as a normal 200 OK, so the status checks above don't catch it.
// This is NOT the same as needsReauth/a bad Termix JWT: sync runs as a
// plain server-to-server fetch() in this main process, with no browser
// cookie jar at all, so re-authenticating through the login iframe (which
// only affects the renderer's browser session) can never fix this --
// reconnecting would tell the user to do something that doesn't help.
// The proxy has to allow this traffic through some other way (an API
// bypass rule, a separate hostname/port that isn't proxy-gated, etc.),
// so this gets its own distinct, honest error rather than piggybacking
// on needsReauth or a raw JSON.parse crash.
const looksLikeHtml =
text.includes("<html") ||
text.includes("<!DOCTYPE") ||
text.includes("<head>") ||
text.includes("<body>");
if (looksLikeHtml) {
const err = new Error(
"The reverse proxy in front of this server is blocking sync traffic with its own login page. Reconnecting won't fix this -- the proxy needs to let Termix's API requests through (e.g. an SSO bypass rule for the sync API, or a non-proxied hostname/port for it).",
);
err.proxyBlocked = true;
throw err;
}
try {
return JSON.parse(text);
} catch {
throw new Error(`Server returned invalid JSON: ${url}`);
}
}
async pullSide(baseUrl, token, entityType, since) {
const url = `${baseUrl}/sync/${entityType}${since ? `?since=${encodeURIComponent(since)}` : ""}`;
const data = await this.fetchJson(url, token);
return data.rows || [];
}
/**
* Every syncId a side currently holds, ignoring the incremental window.
* Used only to decide whether a deletion still has something to delete.
*/
async pullSyncIds(baseUrl, token, entityType) {
const rows = await this.pullSide(baseUrl, token, entityType, null);
return new Set(rows.filter((row) => row.syncId).map((row) => row.syncId));
}
async pullTombstones(baseUrl, token, entityType, since) {
const url = `${baseUrl}/sync/${entityType}/tombstones${since ? `?since=${encodeURIComponent(since)}` : ""}`;
const data = await this.fetchJson(url, token);
return data.tombstones || [];
}
async pushRow(baseUrl, token, entityType, row) {
await this.fetchJson(`${baseUrl}/sync/${entityType}`, token, {
method: "POST",
body: JSON.stringify({ row }),
});
}
async pushTombstone(baseUrl, token, entityType, syncId) {
await this.fetchJson(`${baseUrl}/sync/tombstones`, token, {
method: "POST",
body: JSON.stringify({ entityType, syncId }),
});
}
async syncEntity({ entityType, remoteBaseUrl, remoteJwt, since }) {
const syncedAt = new Date().toISOString();
try {
const [localRows, remoteRows, localTombstones, remoteTombstones] =
await Promise.all([
this.pullSide(EMBEDDED_BASE_URL, this.localJwt, entityType, since),
this.pullSide(remoteBaseUrl, remoteJwt, entityType, since),
this.pullTombstones(
EMBEDDED_BASE_URL,
this.localJwt,
entityType,
since,
),
this.pullTombstones(remoteBaseUrl, remoteJwt, entityType, since),
]);
const tombstonedSyncIds = new Set([
...localTombstones.map((t) => t.syncId),
...remoteTombstones.map((t) => t.syncId),
]);
const localBySyncId = new Map(
localRows.filter((r) => r.syncId).map((r) => [r.syncId, r]),
);
const remoteBySyncId = new Map(
remoteRows.filter((r) => r.syncId).map((r) => [r.syncId, r]),
);
const allSyncIds = new Set([
...localBySyncId.keys(),
...remoteBySyncId.keys(),
]);
for (const syncId of allSyncIds) {
if (tombstonedSyncIds.has(syncId)) continue;
const localRow = localBySyncId.get(syncId);
const remoteRow = remoteBySyncId.get(syncId);
if (localRow && !remoteRow) {
await this.pushRow(remoteBaseUrl, remoteJwt, entityType, localRow);
} else if (remoteRow && !localRow) {
await this.pushRow(
EMBEDDED_BASE_URL,
this.localJwt,
entityType,
remoteRow,
);
} else if (localRow && remoteRow) {
const localUpdatedAt = new Date(localRow.updatedAt || 0).getTime();
const remoteUpdatedAt = new Date(remoteRow.updatedAt || 0).getTime();
if (localUpdatedAt > remoteUpdatedAt) {
await this.pushRow(remoteBaseUrl, remoteJwt, entityType, localRow);
} else if (remoteUpdatedAt > localUpdatedAt) {
await this.pushRow(
EMBEDDED_BASE_URL,
this.localJwt,
entityType,
remoteRow,
);
}
}
}
// Apply tombstones to whichever side hasn't already deleted the row.
//
// The presence check cannot use localRows/remoteRows: those are the
// incremental window, and a row deleted on one side while untouched on
// the other is by definition outside it, so every deletion was dropped.
// It also cannot be skipped -- pushing unconditionally makes the
// receiving side record a fresh tombstone, which the next pass would push
// back, forever. So ask the receiving side what it actually still holds,
// and only when there is a deletion to apply.
if (localTombstones.length) {
const remoteSyncIds = await this.pullSyncIds(
remoteBaseUrl,
remoteJwt,
entityType,
);
for (const tombstone of localTombstones) {
if (remoteSyncIds.has(tombstone.syncId)) {
await this.pushTombstone(
remoteBaseUrl,
remoteJwt,
entityType,
tombstone.syncId,
);
}
}
}
if (remoteTombstones.length) {
const localSyncIds = await this.pullSyncIds(
EMBEDDED_BASE_URL,
this.localJwt,
entityType,
);
for (const tombstone of remoteTombstones) {
if (localSyncIds.has(tombstone.syncId)) {
await this.pushTombstone(
EMBEDDED_BASE_URL,
this.localJwt,
entityType,
tombstone.syncId,
);
}
}
}
return { syncedAt };
} catch (error) {
if (error?.authFailure) {
return { syncedAt, authFailure: true };
}
throw error;
}
}
}
let engine = null;
function initRemoteSync(getMainWindow) {
engine = new RemoteSyncEngine(getMainWindow);
engine.start();
return engine;
}
function getRemoteSyncEngine() {
return engine;
}
module.exports = {
initRemoteSync,
getRemoteSyncEngine,
getDesktopSettings,
saveDesktopSettings,
getRemoteSyncConfig,
saveRemoteSyncConfig,
clearRemoteSyncConfig,
saveRemoteSyncJwt,
getRemoteSyncJwt,
clearRemoteSyncJwt,
getRemoteSyncUserInfo,
isJwtExpiredOrExpiringSoon,
decodeJwtExpiry,
};
-49
View File
@@ -1,49 +0,0 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
export default tseslint.config([
globalIgnores(["dist", "release", "Mobile"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactRefresh.configs.vite,
],
plugins: {
"react-hooks": reactHooks,
"unused-imports": unusedImports,
},
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-expressions": "warn",
"no-empty": "warn",
"no-control-regex": "off",
"no-useless-assignment": "off",
"preserve-caught-error": "off",
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error",
"react-refresh/only-export-components": "warn",
},
},
]);
+102
View File
@@ -0,0 +1,102 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import unusedImports from "eslint-plugin-unused-imports";
import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
export default tseslint.config([
globalIgnores(["dist", "release", "Mobile", "src/mcp-server/node_modules"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactRefresh.configs.vite,
],
plugins: {
"react-hooks": reactHooks,
"unused-imports": unusedImports,
},
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-expressions": "warn",
"no-empty": "warn",
"no-control-regex": "off",
"no-useless-assignment": "off",
"preserve-caught-error": "off",
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error",
"react-refresh/only-export-components": "warn",
},
},
{
// MySQL has no RETURNING clause, and drizzle's mysql-core does not expose
// the method at all — a bare .returning() is a TypeError there, not a bad
// query, and it only fails on the engine no test in this repo runs against.
//
// 175 call sites were migrated off it. This is what stops number 176.
// Writes that need rows back go through repositories/returning.ts, which
// picks one statement or a read-then-write transaction per dialect.
files: ["src/backend/database/repositories/**/*.ts"],
ignores: [
// The two files whose job is to absorb these differences.
"src/backend/database/repositories/returning.ts",
"src/backend/database/repositories/mutation-result.ts",
],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "CallExpression[callee.property.name='returning']",
message:
"MySQL has no RETURNING. Use insertReturning/updateReturning/deleteReturning from ./returning.js, or rowsAffected() if you only need a count. Inside a proven sqlite-only branch, disable this rule with a comment saying so.",
},
{
// `||` concatenates on SQLite and Postgres. On MySQL it is logical OR
// unless the server runs with PIPES_AS_CONCAT, so a folder path built
// this way silently became 0. Use CONCAT, which all three agree on.
selector:
"TaggedTemplateExpression[tag.name='sql'] TemplateElement[value.raw=/\\|\\|/]",
message:
"`||` is logical OR on MySQL, not concatenation. Use CONCAT(...).",
},
{
// Postgres and SQLite spell it ON CONFLICT; MySQL spells it ON
// DUPLICATE KEY and names no columns, so drizzle's mysql-core has no
// onConflictDoUpdate at all — another TypeError, not a bad query.
selector: "CallExpression[callee.property.name='onConflictDoUpdate']",
message:
"MySQL has no ON CONFLICT. Use upsert() from ./returning.js.",
},
{
// better-sqlite3 puts these on a write result; node-postgres and
// mysql2 do not, so reading them directly yields undefined — and
// Number(undefined) is NaN, which reaches the database as the string
// "NaN" and fails an integer column. Three call sites did exactly
// this and only broke on Postgres.
selector:
"MemberExpression[property.name=/^(lastInsertRowid|changes)$/]",
message:
"lastInsertRowid and changes are better-sqlite3 only. Use insertedId() or rowsAffected() from ./mutation-result.js.",
},
],
},
},
]);
-11
View File
@@ -1,11 +0,0 @@
[Flatpak Ref]
Name=Termix
Branch=stable
Title=Termix - SSH Server Management Platform
IsRuntime=false
Url=https://github.com/Termix-SSH/Termix/releases/download/VERSION_PLACEHOLDER/termix_linux_flatpak.flatpak
RuntimeRepo=https://flathub.org/repo/flathub.flatpakrepo
Comment=Web-based server management platform with SSH terminal, tunneling, and file editing
Description=Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides SSH terminal access, tunneling capabilities, and remote file management.
Icon=https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/icon.png
Homepage=https://github.com/Termix-SSH/Termix
-77
View File
@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.karmaa.termix</id>
<name>Termix</name>
<summary>Web-based server management platform with SSH terminal, tunneling, and file editing</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>Apache-2.0</project_license>
<developer_name>bugattiguy527</developer_name>
<description>
<p>
Termix is an open-source, forever-free, self-hosted all-in-one server management platform.
It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface.
</p>
<p>Features:</p>
<ul>
<li>SSH terminal access with full terminal emulation</li>
<li>SSH tunneling capabilities for secure port forwarding</li>
<li>Remote file management with editor support</li>
<li>Server monitoring and management tools</li>
<li>Self-hosted solution - keep your data private</li>
<li>Modern, intuitive web interface</li>
</ul>
</description>
<launchable type="desktop-id">com.karmaa.termix.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/screenshots/terminal.png</image>
<caption>SSH Terminal Interface</caption>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/Termix-SSH/Termix</url>
<url type="bugtracker">https://github.com/Termix-SSH/Support/issues</url>
<url type="help">https://docs.termix.site</url>
<url type="vcs-browser">https://github.com/Termix-SSH/Termix</url>
<content_rating type="oars-1.1">
<content_attribute id="social-info">moderate</content_attribute>
</content_rating>
<releases>
<release version="VERSION_PLACEHOLDER" date="DATE_PLACEHOLDER">
<description>
<p>Latest release of Termix</p>
</description>
<url>https://github.com/Termix-SSH/Termix/releases</url>
</release>
</releases>
<categories>
<category>Development</category>
<category>Network</category>
<category>System</category>
</categories>
<keywords>
<keyword>ssh</keyword>
<keyword>terminal</keyword>
<keyword>server</keyword>
<keyword>management</keyword>
<keyword>tunnel</keyword>
<keyword>file-manager</keyword>
</keywords>
<provides>
<binary>termix</binary>
</provides>
<requires>
<internet>always</internet>
</requires>
</component>
+10 -4
View File
@@ -2,8 +2,11 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<meta name="theme-color" content="#09090b" />
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -12,8 +15,8 @@
content="black-translucent"
/>
<meta name="apple-mobile-web-app-title" content="Termix" />
<link rel="apple-touch-icon" href="/icons/512x512.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="icons/512x512.png" />
<link rel="manifest" href="manifest.json" />
<title>Termix</title>
<style>
.hide-scrollbar {
@@ -47,6 +50,9 @@
</style>
</head>
<body>
<script>
window.__TERMIX_BASE_PATH__ = "";
</script>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
+98
View File
@@ -0,0 +1,98 @@
{
"$schema": "https://unpkg.com/knip@latest/schema.json",
"entry": [
"src/backend/starter.ts",
"src/backend/swagger.ts",
"scripts/**/*.cjs"
],
"project": ["src/**/*.{ts,tsx}", "electron/**/*.cjs", "scripts/**/*.cjs"],
"ignoreBinaries": ["powershell"],
"ignoreDependencies": [
"@codemirror/autocomplete",
"@codemirror/commands",
"@codemirror/search",
"@codemirror/theme-one-dark",
"@codemirror/view",
"@electron/notarize",
"@monaco-editor/react",
"@radix-ui/react-accordion",
"@radix-ui/react-alert-dialog",
"@radix-ui/react-checkbox",
"@radix-ui/react-dialog",
"@radix-ui/react-dropdown-menu",
"@radix-ui/react-label",
"@radix-ui/react-popover",
"@radix-ui/react-progress",
"@radix-ui/react-scroll-area",
"@radix-ui/react-select",
"@radix-ui/react-separator",
"@radix-ui/react-slider",
"@radix-ui/react-slot",
"@radix-ui/react-switch",
"@radix-ui/react-tabs",
"@radix-ui/react-tooltip",
"@types/better-sqlite3",
"@types/cookie-parser",
"@types/cors",
"@types/express",
"@types/guacamole-common-js",
"@types/js-yaml",
"@types/jsonwebtoken",
"@types/multer",
"@types/qrcode",
"@types/speakeasy",
"@types/ssh2",
"@uiw/codemirror-extensions-langs",
"@uiw/codemirror-theme-github",
"@uiw/react-codemirror",
"@xterm/addon-clipboard",
"@xterm/addon-fit",
"@xterm/addon-unicode11",
"@xterm/addon-web-links",
"@xterm/xterm",
"axios",
"bcryptjs",
"better-sqlite3",
"body-parser",
"chalk",
"class-variance-authority",
"clsx",
"cmdk",
"cookie-parser",
"cors",
"cytoscape",
"drizzle-orm",
"express",
"guacamole-lite",
"guacamole-common-js",
"jose",
"js-yaml",
"jsonwebtoken",
"jszip",
"multer",
"nanoid",
"qrcode",
"i18next-browser-languagedetector",
"lucide-react",
"motion",
"radix-ui",
"react-cytoscapejs",
"react-h5-audio-player",
"react-hook-form",
"react-icons",
"react-markdown",
"react-pdf",
"react-photo-view",
"react-syntax-highlighter",
"react-xtermjs",
"remark-gfm",
"socks",
"speakeasy",
"ssh2",
"undici",
"husky",
"sharp",
"sonner",
"tailwind-merge"
]
}
+7553 -3477
View File
File diff suppressed because it is too large Load Diff
+113 -85
View File
@@ -1,11 +1,10 @@
{
"name": "termix",
"private": true,
"version": "2.2.1",
"description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
"version": "2.6.1",
"description": "Self-hosted SSH and remote desktop management.",
"author": "Karmaa",
"main": "electron/main.cjs",
"type": "module",
"engines": {
"node": ">=22.12.0",
"npm": ">=11"
@@ -13,89 +12,113 @@
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"postinstall": "node scripts/patch-app-builder-lib.cjs",
"biome:check": "biome check biome.json package.json",
"biome:fix": "biome check --write biome.json package.json",
"postinstall": "node scripts/patch-app-builder-lib.cjs && node scripts/patch-guacamole-lite.cjs && node scripts/patch-guacamole-common-js.cjs && node scripts/patch-better-sqlite3.cjs && node scripts/patch-nan.cjs && node scripts/patch-xterm-android-ime.cjs",
"prebuild": "node scripts/write-electron-build-info.cjs",
"lint": "eslint .",
"lint": "node scripts/generate-dialect-schema.cjs --check && eslint .",
"lint:fix": "eslint --fix .",
"type-check": "tsc --noEmit",
"test": "vitest run",
"verify:dialect": "tsx scripts/verify-dialects.mjs",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
"dev": "vite",
"build": "vite build && tsc -p tsconfig.node.json",
"build:backend": "tsc -p tsconfig.node.json",
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
"build": "vite build && tsc -p tsconfig.node.json && node -e \"require('fs').copyFileSync('src/backend/package.json','dist/backend/package.json')\"",
"build:backend": "tsc -p tsconfig.node.json && node -e \"require('fs').copyFileSync('src/backend/package.json','dist/backend/package.json')\"",
"dev:backend": "tsc -p tsconfig.node.json && node -e \"require('fs').copyFileSync('src/backend/package.json','dist/backend/package.json')\" && node ./dist/backend/backend/starter.js",
"dev:docker": "docker stop termix-dev 2>nul & docker rm termix-dev 2>nul & docker build -f docker/Dockerfile -t termix:dev --no-cache . && docker run -d --name termix-dev -p 3000:3000 -p 8080:8080 -p 30001-30006:30001-30006 -v \"%cd%\\db\\data:/app/data\" termix:dev",
"dev:docker:restart": "docker stop termix-dev 2>nul & docker rm termix-dev 2>nul & docker run -d --name termix-dev -p 8080:8080 -p 30001-30006:30001-30006 -v \"%cd%\\db\\data:/app/data\" termix:dev",
"generate:openapi": "tsc -p tsconfig.node.json && node ./dist/backend/backend/swagger.js",
"generate:openapi": "tsc -p tsconfig.node.json && node -e \"require('fs').copyFileSync('src/backend/package.json','dist/backend/package.json')\" && node ./dist/backend/backend/utils/swagger.js",
"preview": "vite preview",
"electron:dev": "concurrently \"npm run dev\" \"powershell -c \\\"Start-Sleep -Seconds 5\\\" && electron .\"",
"electron:patch-builder": "node scripts/patch-app-builder-lib.cjs",
"electron:rebuild": "electron-rebuild -f -w better-sqlite3",
"electron:rebuild": "electron-rebuild -f -w better-sqlite3 -w serialport",
"build:win-portable": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --win --dir",
"build:win-installer": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --win --publish=never",
"build:linux-portable": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --linux --dir",
"build:linux-appimage": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --linux AppImage",
"build:linux-targz": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --linux tar.gz",
"build:mac": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac --universal",
"build:mac-dev": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac dir --publish=never"
"build:mac-dev": "npm run build && npm run electron:rebuild && npm run electron:patch-builder && electron-builder --mac dir --publish=never",
"schema:generate": "node scripts/generate-dialect-schema.cjs",
"schema:check": "node scripts/generate-dialect-schema.cjs --check",
"schema:migrations": "drizzle-kit generate --config=drizzle.config.sqlite.ts && drizzle-kit generate --config=drizzle.config.pg.ts && drizzle-kit generate --config=drizzle.config.mysql.ts"
},
"dependencies": {
"axios": "^1.15.2",
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.2",
"@tanstack/react-virtual": "^3.14.9",
"@types/ldapjs": "^3.0.6",
"axios": "^1.19.0",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.9.0",
"body-parser": "^2.2.2",
"chalk": "^5.6.2",
"better-sqlite3": "^13.0.2",
"body-parser": "^2.3.0",
"chalk": "^6.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"drizzle-orm": "^0.45.2",
"express": "^5.2.1",
"guacamole-lite": "^1.2.0",
"https-proxy-agent": "^7.0.6",
"jose": "^6.2.2",
"js-yaml": "^4.1.1",
"jose": "^6.2.5",
"js-yaml": "^5.2.2",
"jsonwebtoken": "^9.0.3",
"jszip": "^3.10.1",
"motion": "^12.38.0",
"multer": "^2.1.1",
"nanoid": "^5.1.9",
"ldapjs": "^3.0.7",
"motion": "^12.43.0",
"multer": "^2.2.0",
"mysql2": "^3.23.2",
"nanoid": "^6.0.0",
"pg": "^8.22.0",
"qrcode": "^1.5.4",
"react-is": "^19.2.5",
"serialport": "^13.0.0",
"socks": "^2.8.7",
"speakeasy": "^2.0.0",
"ssh2": "^1.17.0",
"undici": "^7.0.0",
"ws": "^8.20.0"
"undici": "^8.9.0",
"ws": "^8.21.1"
},
"devDependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3",
"@codemirror/search": "^6.7.0",
"@biomejs/biome": "2.5.6",
"@codemirror/autocomplete": "^6.20.3",
"@codemirror/commands": "^6.10.4",
"@codemirror/search": "^6.7.1",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.41.1",
"@commitlint/cli": "^20.5.0",
"@commitlint/config-conventional": "^20.5.0",
"@codemirror/view": "^6.43.7",
"@commitlint/cli": "^21.2.1",
"@commitlint/config-conventional": "^21.2.0",
"@deadendjs/swagger-jsdoc": "^8.1.2",
"@electron/notarize": "^3.1.1",
"@electron/rebuild": "^4.0.4",
"@eslint/js": "^9.0.0",
"@hookform/resolvers": "^5.2.2",
"@electron/rebuild": "^4.2.0",
"@eslint/js": "^10.0.1",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@fontsource/fira-code": "^5.3.0",
"@fontsource/jetbrains-mono": "^5.3.0",
"@fontsource/source-code-pro": "^5.3.0",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.2.4",
"@radix-ui/react-accordion": "^1.2.20",
"@radix-ui/react-alert-dialog": "^1.1.23",
"@radix-ui/react-checkbox": "^1.3.11",
"@radix-ui/react-dialog": "^1.1.23",
"@radix-ui/react-dropdown-menu": "^2.1.24",
"@radix-ui/react-label": "^2.1.15",
"@radix-ui/react-popover": "^1.1.23",
"@radix-ui/react-progress": "^1.1.16",
"@radix-ui/react-scroll-area": "^1.2.18",
"@radix-ui/react-select": "^2.3.7",
"@radix-ui/react-separator": "^1.1.15",
"@radix-ui/react-slider": "^1.4.7",
"@radix-ui/react-slot": "^1.3.3",
"@radix-ui/react-switch": "^1.3.7",
"@radix-ui/react-tabs": "^1.1.21",
"@radix-ui/react-tooltip": "^1.2.16",
"@tailwindcss/vite": "^4.3.3",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^7.0.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/better-sqlite3": "^7.6.13",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
@@ -103,67 +126,72 @@
"@types/guacamole-common-js": "^1.5.5",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.1.0",
"@types/node": "^24.12.2",
"@types/multer": "^2.2.0",
"@types/node": "^26.1.2",
"@types/pg": "^8.20.0",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@types/speakeasy": "^2.0.10",
"@types/ssh2": "^1.15.5",
"@types/ws": "^8.18.1",
"@uiw/codemirror-extensions-langs": "^4.25.9",
"@uiw/codemirror-theme-github": "^4.25.9",
"@uiw/react-codemirror": "^4.25.9",
"@vitejs/plugin-react": "^6.0.1",
"@uiw/codemirror-extensions-langs": "^4.25.11",
"@uiw/codemirror-theme-github": "^4.25.11",
"@uiw/react-codemirror": "^4.25.11",
"@vitejs/plugin-react": "^6.0.5",
"@vitest/coverage-v8": "^4.1.10",
"@vitest/ui": "^4.1.10",
"@xterm/addon-clipboard": "^0.2.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-unicode11": "^0.8.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-search": "^0.16.0",
"@xterm/addon-unicode11": "^0.9.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"concurrently": "^9.2.1",
"cytoscape": "^3.33.2",
"electron": "^41.3.0",
"electron-builder": "^26.8.1",
"eslint": "^9.0.0",
"concurrently": "^10.0.4",
"cytoscape": "^3.34.0",
"drizzle-kit": "^0.31.10",
"electron": "^43.2.0",
"electron-builder": "^26.15.3",
"eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-react-refresh": "^0.5.3",
"eslint-plugin-unused-imports": "^4.4.1",
"globals": "^17.5.0",
"globals": "^17.8.0",
"guacamole-common-js": "^1.5.0",
"husky": "^9.1.7",
"i18next": "^26.0.8",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"lint-staged": "^16.4.0",
"lucide-react": "^1.11.0",
"next-themes": "^0.4.6",
"prettier": "3.8.3",
"react": "^19.2.5",
"jsdom": "^29.1.1",
"lint-staged": "^17.2.0",
"lucide-react": "^1.28.0",
"prettier": "3.9.6",
"radix-ui": "^1.6.7",
"react": "^19.2.8",
"react-cytoscapejs": "^2.0.0",
"react-dom": "^19.2.5",
"react-grid-layout": "^2.2.3",
"react-dom": "^19.2.8",
"react-h5-audio-player": "^3.10.2",
"react-hook-form": "^7.73.1",
"react-i18next": "^17.0.4",
"react-hook-form": "^7.79.0",
"react-i18next": "^17.0.11",
"react-icons": "^5.6.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.4.1",
"react-photo-view": "^1.2.7",
"react-resizable-panels": "^4.10.0",
"react-simple-keyboard": "^3.8.196",
"react-syntax-highlighter": "^16.1.1",
"react-xtermjs": "^1.0.10",
"recharts": "^3.8.1",
"remark-gfm": "^4.0.1",
"sharp": "^0.35.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.4",
"tw-animate-css": "^1.4.0",
"typescript": "~6.0.3",
"typescript-eslint": "^8.59.0",
"vite": "^8.0.10",
"zod": "^4.3.6"
"typescript-eslint": "^8.61.1",
"vite": "^8.0.16",
"vite-plugin-svgr": "^5.2.0",
"vitest": "^4.1.10"
},
"lint-staged": {
"*.{ts,tsx}": [
+12
View File
@@ -0,0 +1,12 @@
const fs = require("fs");
const path = require("path");
exports.default = async function afterPack(context) {
const { targets, appOutDir } = context;
const isDir = targets.some((t) => t.name === "dir");
if (!isDir) return;
const markerPath = path.join(appOutDir, ".portable");
fs.writeFileSync(markerPath, "");
};
@@ -14,16 +14,10 @@
<projectSourceUrl>https://github.com/Termix-SSH/Termix</projectSourceUrl>
<docsUrl>https://docs.termix.site/install</docsUrl>
<bugTrackerUrl>https://github.com/Termix-SSH/Support/issues</bugTrackerUrl>
<tags>docker ssh self-hosted file-management ssh-tunnel termix server-management terminal</tags>
<summary>Termix is a web-based server management platform with SSH terminal, tunneling, and file editing capabilities.</summary>
<tags>docker ssh terminal telnet self-hosted rdp file-management vnc ssh-tunnel server-stats termix</tags>
<summary>Self-hosted SSH and remote desktop management.</summary>
<description>
Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based solution for managing your servers and infrastructure through a single, intuitive interface.
Termix offers:
- SSH terminal access
- SSH tunneling capabilities
- Remote file management
- Server monitoring and management
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.
This package installs the desktop application version of Termix.
</description>
@@ -1,6 +1,6 @@
[Desktop Entry]
Name=Termix
Comment=Web-based server management platform with SSH terminal, tunneling, and file editing
Comment=Self-hosted SSH and remote desktop management.
Exec=run.sh %U
Icon=com.karmaa.termix
Terminal=false
@@ -0,0 +1,11 @@
[Flatpak Ref]
Name=Termix
Branch=stable
Title=Termix - SSH Server Management Platform
IsRuntime=false
Url=https://github.com/Termix-SSH/Termix/releases/download/VERSION_PLACEHOLDER/termix_linux_flatpak.flatpak
RuntimeRepo=https://flathub.org/repo/flathub.flatpakrepo
Comment=Self-hosted SSH and remote desktop management.
Description=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.
Icon=https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/icon.png
Homepage=https://github.com/Termix-SSH/Termix
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.karmaa.termix</id>
<name>Termix</name>
<summary>Self-hosted SSH and remote desktop management.</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>Apache-2.0</project_license>
<developer_name>bugattiguy527</developer_name>
<description>
<p>
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.
</p>
</description>
<launchable type="desktop-id">com.karmaa.termix.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/Termix-SSH/Termix/main/public/screenshots/terminal.png</image>
<caption>SSH Terminal Interface</caption>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/Termix-SSH/Termix</url>
<url type="bugtracker">https://github.com/Termix-SSH/Support/issues</url>
<url type="help">https://docs.termix.site</url>
<url type="vcs-browser">https://github.com/Termix-SSH/Termix</url>
<content_rating type="oars-1.1">
<content_attribute id="social-info">moderate</content_attribute>
</content_rating>
<releases>
<release version="VERSION_PLACEHOLDER" date="DATE_PLACEHOLDER">
<description>
<p>Latest release of Termix</p>
</description>
<url>https://github.com/Termix-SSH/Termix/releases</url>
</release>
</releases>
<categories>
<category>Development</category>
<category>Network</category>
<category>System</category>
</categories>
<keywords>
<keyword>docker</keyword>
<keyword>ssh</keyword>
<keyword>terminal</keyword>
<keyword>telnet</keyword>
<keyword>self-hosted</keyword>
<keyword>rdp</keyword>
<keyword>file-management</keyword>
<keyword>vnc</keyword>
<keyword>ssh-tunnel</keyword>
<keyword>server-stats</keyword>
<keyword>termix</keyword>
</keywords>
<provides>
<binary>termix</binary>
</provides>
<requires>
<internet>always</internet>
</requires>
</component>

Some files were not shown because too many files have changed in this diff Show More