Compare commits

..
Author SHA1 Message Date
Luke Gustafson b448ae48e0 New Crowdin updates (#827)
* 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-27 15:45:26 -05:00
LukeGus 48b6e4a595 chore: update readme images 2026-05-27 15:05:25 -05:00
LukeGus 84e3989511 chore: update crowdin to match new en.json path 2026-05-27 14:30:18 -05:00
Luke Gustafson 89c7bc0f76 v2.3.0
v2.3.0
2026-05-27 15:23:46 -04:00
LukeGusandClaude Sonnet 4.6 41d30bb585 chore: merge main into dev-2.3.0 to resolve PR conflicts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:23:20 -05:00
LukeGus ff399ec0bc feat: update c2s tunnel to match new UI 2026-05-27 14:10:19 -05:00
LukeGus abb1f60b71 fix: show electron change server header for localserver 2026-05-27 13:54:13 -05:00
LukeGus f81587888f fix: nan patch for electron build error 2026-05-27 13:20:11 -05:00
LukeGus fa29997bf4 fix: sqlite patch so electron builds 2026-05-27 12:40:49 -05:00
Luke Gustafson d14615659a fix: patch better-sqlite3 for Electron 42 rebuild (#819) 2026-05-27 01:22:50 -05:00
LukeGus 7e94235a28 chore: format 2026-05-27 01:07:06 -05:00
LukeGus a418a4df0e fix: make telnet icon different from terminal 2026-05-27 00:29:28 -05:00
LukeGus 2ea9a481a3 fix: oklch invalid wanring 2026-05-27 00:24:12 -05:00
Luke Gustafson a43efb11a3 Update README.md 2026-05-27 00:01:18 -05:00
LukeGus 4515111d7f chore: update readme's 2026-05-26 23:59:57 -05:00
LukeGus 8b209bc3c6 feat: add snippets features 2026-05-26 23:10:17 -05:00
LukeGus d01b1a03cb feat: add snippets features 2026-05-26 23:02:19 -05:00
LukeGus 0a32bf518e chore: releaes prep 2026-05-26 22:58:00 -05:00
LukeGus c5b9467186 chore: releaes prep 2026-05-26 22:36:45 -05:00
LukeGus 9c12a84a91 feat: continued general UI improvements 2026-05-26 22:05:58 -05:00
LukeGus 0fe3f8d126 feat: sidebar saves state across mobile swtich, guacd hosts not migraitng passwords, terminal wrong size 2026-05-26 17:48:19 -05:00
LukeGus 5c2c9c179e feat: seperated out sharing, improved host/credential loading, reduced host manager bloat 2026-05-26 16:57:35 -05:00
LukeGus 4df71e77cb feat: add CA-signed certificate authentication for SSH credentials (#812) 2026-05-26 15:44:37 -05:00
Marc Reinke 698a5648ec feat: add CA-signed certificate authentication for SSH credentials
Support OpenSSH certificate-based authentication (-cert.pub files) in
the Credentials manager. When a CA-signed certificate is stored alongside
a private key, Termix uses it during SSH connection establishment so that
servers relying on certificate-based authorization work out of the box.

Changes:
- db/schema.ts: add cert_public_key column to ssh_credentials table
- db/index.ts: auto-migration via addColumnIfNotExists
- routes/credentials.ts: expose certPublicKey in create/update/get endpoints
- ssh/auth-manager.ts: include certPublicKey in ResolvedCredentials
- ssh/host-resolver.ts: propagate certPublicKey when resolving credentials
- ssh/opkssh-cert-auth.ts: refactor shared logic into _applyCertToConnection;
  export new setupCACertAuth() with optional passphrase support
- ssh/terminal.ts: call setupCACertAuth() when a certificate is present
- utils/ssh-key-utils.ts: detect all OpenSSH cert types in public key parser
- types/index.ts: add certPublicKey to Credential, CredentialBackend,
  CredentialData interfaces
- CredentialAuthenticationTab.tsx: new CA Certificate section with file
  upload, paste editor and automatic cert-type badge
- CredentialEditor.tsx: certPublicKey wired into form schema and submit
- CredentialViewer.tsx: show certificate status in security tab
- locales/en.json: add i18n strings for new UI elements
2026-05-26 15:44:08 -05:00
Luke Gustafson d3a2992898 Update sha256 checksum for Termix cask 2026-05-26 15:42:18 -05:00
LukeGus 57d9e0c035 fix: jump hosts failing 2026-05-26 15:30:22 -05:00
LukeGus 0a3517f9b0 fix: jump hosts failing 2026-05-26 15:28:44 -05:00
LukeGus 3011de6abb fix: guacd hosts not migarting password 2026-05-22 17:16:39 -05:00
LukeGus 6b9784c8fc feat: improve sentinel system 2026-05-22 16:56:53 -05:00
LukeGus ee4520a1e0 fix: guacd connec tions migrating incorrectly from allowing all protocls in one host migration 2026-05-22 16:36:54 -05:00
LukeGus 4c2a317f24 fix: electron backend not starting 2026-05-22 16:16:14 -05:00
LukeGus 3bb305bc38 fix: websocket auth error electron 2026-05-22 15:55:03 -05:00
LukeGus b82111c273 fix: overalpping guacd ui 2026-05-22 00:14:33 -05:00
LukeGus a9f82a5a45 feat: readd mange mode for host manager, guacd toolbar improvements, connection issues, etc. 2026-05-22 00:02:50 -05:00
LukeGus 6bcbadcc25 feat: guacd improvements, created tool bar 2026-05-21 22:35:58 -05:00
LukeGus 23a0160ef0 fix: auth failed electron 2026-05-21 14:22:25 -05:00
LukeGus 170080399b feat: seperated host manager into 2 rail tabs, electron fixes for auth, shared credential fixes 2026-05-20 22:55:16 -05:00
LukeGus 15e815a632 chore: continued redesign readme 2026-05-20 17:11:37 -05:00
LukeGus 162a512729 chore: continued redesign readme 2026-05-20 17:10:28 -05:00
LukeGus 22ff064c96 chore: continued redesign readme 2026-05-20 17:06:16 -05:00
LukeGus df6fe0430b chore: continued redesign readme 2026-05-20 17:03:43 -05:00
LukeGus 5bb1cfc03c chore: continued redesign readme 2026-05-20 17:01:31 -05:00
LukeGus 4710b6507a chore: continued redesign readme 2026-05-20 16:58:50 -05:00
LukeGus aa71183d08 chore: continued redesign readme 2026-05-20 16:56:02 -05:00
LukeGus a045d660fc chore: continued redesign readme 2026-05-20 16:53:10 -05:00
LukeGus b91c8c3dd4 chore: continued redesign readme 2026-05-20 16:48:42 -05:00
LukeGus 2c5f7d1e66 chore: continued redesign readme 2026-05-20 16:45:12 -05:00
LukeGus 5b890e9538 chore: continued redesign readme 2026-05-20 16:44:27 -05:00
LukeGus 0bb127737c chore: remove unneeded npm packages and redesign readme 2026-05-20 16:38:29 -05:00
LukeGus fb882b9ce5 fix: build failure 2026-05-20 15:17:02 -05:00
LukeGus cf27e02b23 fix: build failure 2026-05-20 15:12:58 -05:00
LukeGus 7192a0fc82 fix: build failure 2026-05-20 15:04:37 -05:00
LukeGus d04ecc74ed fix: build failure 2026-05-20 15:01:09 -05:00
LukeGus ebeb9ede17 fix: build failure 2026-05-20 14:58:35 -05:00
LukeGus bc713aca7d feat: terminal theme improvemnts, file manager improvements, host manager improvements (ready for initial user testing?) 2026-05-20 14:55:41 -05:00
LukeGus 9f0aaa16a5 fix: no db entry for connection protocols 2026-05-20 14:02:26 -05:00
LukeGus dca2222689 Merge remote-tracking branch 'origin/dev-2.3.0' into dev-2.3.0 2026-05-20 13:58:28 -05:00
LukeGus ec69c338da feat: host manager improvements and added over electron files 2026-05-20 13:58:15 -05:00
3375733789 fix: resolve backend build errors, preserve sudo file read buffers, and format code (#798)
* fix: resolve backend TypeScript build errors and preserve sudo file read buffers

* style: format code

* fix: ssh2 ESM import failure

---------

Co-authored-by: LukeGus <bugattiguy527@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:18:42 -05:00
ZacharyZcR 21f24a8386 feat: add OIDC auto-provision setting independent of registration toggle (#795)
Allows OIDC-authenticated users to be auto-provisioned even when
"Allow new account registration" is disabled. Adds an admin UI toggle
and corresponding API endpoints (GET/PATCH /users/oidc-auto-provision).
Falls back to OIDC_ALLOW_REGISTRATION env var if the setting is unset.
2026-05-20 13:16:05 -05:00
ZacharyZcR ecb7f4f2c5 fix: add 3-way horizontal split variant and fix file manager overflow in split view (#796)
Adds a "3-Way (H)" layout option (2 top + 1 bottom) alongside the
existing "3-Way (V)" (1 left + 2 right).

Fixes file manager content overflowing its split pane by adding
min-h-0 to nested flex containers that were missing it.
2026-05-20 13:15:59 -05:00
TomyJan 5971bcfe88 feat(auth): add silent OIDC sign-in (#799) 2026-05-20 13:15:08 -05:00
LukeGus d3fa8c4420 feat: host manager bug fixes, i18n improvements, general tab fixes 2026-05-20 13:13:56 -05:00
ZacharyZcR a966cc930b fix: load Caskaydia Cove Nerd Font via @font-face declarations (#794)
The default terminal font was configured but never loaded via CSS,
causing browsers to fall back to generic monospace. This broke column
alignment and TUI rendering (ncurses menus, box-drawing chars).
2026-05-18 14:51:56 +08:00
ZacharyZcR 63c8d601b5 feat: add confirmation prompt before closing active session tabs (#793)
Shows a toast confirmation when closing terminal, RDP, VNC or telnet
tabs to prevent accidental session loss from misclicks.
2026-05-18 05:53:15 +08:00
ZacharyZcR 3d42642e94 fix: flush in-memory database to disk on graceful shutdown (#792)
SIGTERM/SIGINT handlers previously called process.exit(0) immediately
without persisting the in-memory SQLite database. This caused session
data loss on container restart, forcing users to re-authenticate.
2026-05-18 05:30:13 +08:00
ZacharyZcRandLukeGus 65d67e6f79 fix: host connections failing with "Authentication failed" (#791)
Co-authored-by: LukeGus <bugattiguy527@gmail.com>
2026-05-18 05:17:10 +08:00
ZacharyZcR a354a45b1c Merge pull request #765 from ZacharyZcR/feat/ctrl-alt-delete-button
feat: add ctrl+alt+delete button for rdp/vnc sessions
2026-05-18 04:47:35 +08:00
ZacharyZcR c488f92e00 Merge pull request #767 from ZacharyZcR/feat/clone-host
feat: implement clone host functionality
2026-05-18 04:47:30 +08:00
ZacharyZcR 125ba1e82c Merge pull request #770 from ZacharyZcR/fix/default-session-persistence
fix: enable terminal session persistence by default
2026-05-18 04:47:26 +08:00
ZacharyZcR d74e1b141a Merge pull request #781 from ZacharyZcR/feat/guacd-status-check
feat: check guacd availability before RDP/VNC connection
2026-05-18 04:47:22 +08:00
ZacharyZcR 629961f70b Merge pull request #789 from ZacharyZcR/feat/tmux-experience-improvements
feat: improve tmux session management and UX
2026-05-18 04:47:18 +08:00
ZacharyZcR fc9860c4d0 feat: add ctrl+alt+delete button for rdp/vnc sessions 2026-05-18 04:43:53 +08:00
ZacharyZcR 6a98e3db7a feat: implement clone host functionality 2026-05-18 04:41:49 +08:00
ZacharyZcR dd0bfb5190 fix: enable terminal session persistence by default 2026-05-18 04:40:27 +08:00
ZacharyZcR a1a442281b feat: check guacd availability before RDP/VNC connection 2026-05-18 04:39:46 +08:00
ZacharyZcR 47d660a509 feat: improve tmux session management and UX
Replace hardcoded setTimeout timing with exec channel polling for
reliable tmux session readiness detection. Add meaningful session
naming (termix-<hostId>-<rand>), aggressive-resize for multi-client
support, and a detach button in the terminal UI.
2026-05-18 04:38:19 +08:00
ZacharyZcR ebed4a6d2e Merge pull request #774 from ZacharyZcR/fix/proxmox-bare-metal-deploy
feat: add bare-metal deployment configs for Proxmox/LXC
2026-05-18 04:36:45 +08:00
ZacharyZcR e156052cc7 Merge pull request #760 from ZacharyZcR/fix/folder-drag-upload
fix: support folder drag-and-drop upload in file manager
2026-05-18 04:36:41 +08:00
ZacharyZcR 8e725bd181 Merge pull request #759 from ZacharyZcR/fix/jumphost-shared-access
fix: resolve jump hosts for shared access users
2026-05-18 04:36:37 +08:00
ZacharyZcR 396a672a1c Merge pull request #776 from ZacharyZcR/feat/per-user-shared-host-credentials
feat: support per-user credentials on shared hosts
2026-05-18 04:36:33 +08:00
ZacharyZcR cfb9e4bcaa Merge pull request #780 from ZacharyZcR/feat/rdp-over-ssh-tunnel
feat: support RDP/VNC over SSH tunnel via jump hosts
2026-05-18 04:36:29 +08:00
ZacharyZcR c9886b1a76 Merge pull request #772 from ZacharyZcR/fix/large-file-download-streaming
fix: use streaming download for large files in file manager
2026-05-18 04:36:25 +08:00
ZacharyZcR c35555056e Merge pull request #787 from ZacharyZcR/fix/editor-sudo-fallback
fix: add sudo fallback for file read/write in editor
2026-05-18 04:36:10 +08:00
ZacharyZcR db657444cb Merge pull request #761 from ZacharyZcR/fix/ssh-agent-forwarding
fix: implement ssh agent forwarding with in-memory agent
2026-05-18 04:36:02 +08:00
ZacharyZcR 5b1d84656b Merge pull request #788 from ZacharyZcR/feat/system-browser-oidc-auth
feat: implement RFC 8252 system browser OIDC authentication for desktop
2026-05-18 04:35:55 +08:00
ZacharyZcR 3d5296cdaa Merge pull request #763 from ZacharyZcR/feat/pinned-hosts-quick-actions
feat: show pinned hosts in quick actions card
2026-05-18 04:35:52 +08:00
ZacharyZcR d79fb809de Merge pull request #786 from ZacharyZcR/feat/cloudflare-tunnel-ssh
feat: support SSH connections through Cloudflare Tunnels
2026-05-18 04:35:47 +08:00
ZacharyZcR 58fa7b8211 Merge pull request #784 from ZacharyZcR/fix/guacamole-protocol-version
fix: patch guacamole-lite to support guacd 1.6.0 protocol version
2026-05-18 04:35:43 +08:00
ZacharyZcR 0933cd56c1 Merge pull request #764 from ZacharyZcR/feat/opkssh-host-sharing
feat: allow sharing opkssh-authenticated hosts
2026-05-18 04:35:39 +08:00
ZacharyZcR 8b2d44e151 Merge pull request #783 from ZacharyZcR/fix/chacha20-cipher-support
fix: properly detect chacha20-poly1305 support before advertising it
2026-05-18 04:35:24 +08:00
ZacharyZcR a1123cb51c Merge pull request #782 from ZacharyZcR/feat/oidc-admin-group
feat: sync admin status from OIDC group membership on login
2026-05-18 04:35:20 +08:00
ZacharyZcR 982cfab978 Merge pull request #775 from ZacharyZcR/fix/guacamole-drive-redirection
fix: implement guacamole drive redirection file transfer
2026-05-18 04:35:16 +08:00
ZacharyZcR d27d8ad149 Merge pull request #769 from ZacharyZcR/fix/electron-url-handler
fix: validate terminal link URLs before opening in external browser
2026-05-18 04:35:11 +08:00
ZacharyZcR 93716c9d64 Merge pull request #766 from ZacharyZcR/feat/runtime-base-path
feat: support runtime base path configuration via BASE_PATH env var
2026-05-18 04:35:07 +08:00
ZacharyZcR c833b47a4a Merge pull request #778 from ZacharyZcR/feat/snippet-host-filter
feat: add host filter for snippets
2026-05-18 04:35:03 +08:00
ZacharyZcR e0db78ed6d Merge pull request #757 from ZacharyZcR/fix/oidc-callback-secret-decoding
fix: decode oidc client_secret before token exchange
2026-05-18 04:34:59 +08:00
ZacharyZcR bf840bef2a Merge pull request #762 from ZacharyZcR/fix/rdp-ignore-cert-default
fix: default ignore-cert to true for rdp connections
2026-05-18 04:34:44 +08:00
ZacharyZcR 5f93ffe4b3 Merge pull request #771 from ZacharyZcR/fix/file-manager-auto-sudo
fix: auto-set sudo password in file manager from host config
2026-05-18 04:34:40 +08:00
ZacharyZcR 7796014559 Merge pull request #768 from ZacharyZcR/fix/linux-electron-fork-cwd
fix: resolve linux electron backend fork ENOTDIR error
2026-05-18 04:34:36 +08:00
ZacharyZcR c5bc870481 Merge pull request #785 from ZacharyZcR/fix/docker-guacd-arm64
fix: improve docker-compose guacd config for ARM64 and network isolation
2026-05-18 04:34:31 +08:00
ZacharyZcR cfd4b08396 Merge pull request #779 from ZacharyZcR/fix/database-import-order
fix: disable foreign key constraints during database import
2026-05-18 04:34:27 +08:00
ZacharyZcR 748b01de1f Merge pull request #777 from ZacharyZcR/fix/vnc-tls-support
fix: enable VNC TLS/VeNCrypt support with security mode negotiation
2026-05-18 04:34:22 +08:00
ZacharyZcR 608852cd04 Merge pull request #773 from ZacharyZcR/fix/macos-electron-esm-error
fix: override package.json type to commonjs for electron builds
2026-05-18 04:34:11 +08:00
LukeGus 99d4ecad12 feat: continued improvements 2026-05-14 00:55:32 -05:00
LukeGus c26288e024 feat: continued improvements 2026-05-14 00:55:12 -05:00
LukeGus 2a45cfea9a feat: update readme 2026-05-13 23:51:00 -05:00
LukeGus ab2b35b733 feat: update readme 2026-05-13 23:50:07 -05:00
LukeGus ae7eba8936 feat: update readme 2026-05-13 23:48:45 -05:00
LukeGus d295c0b15e feat: update readme 2026-05-13 23:46:34 -05:00
LukeGus 165ef058d9 feat: update readme 2026-05-13 23:44:02 -05:00
Luke Gustafson b30ed8eabe Update sha256 checksum for Termix cask 2026-05-13 19:06:44 -05:00
LukeGus 5bf59f87c0 feat: improve all connection types 2026-05-13 17:44:13 -05:00
ZacharyZcR 4f23e3d22c feat: implement RFC 8252 system browser OIDC authentication for desktop 2026-05-13 19:18:44 +08:00
ZacharyZcR 88e73bd69c fix: add sudo fallback for file read/write in editor 2026-05-13 19:10:12 +08:00
ZacharyZcR 0619a5a038 feat: support SSH connections through Cloudflare Tunnels 2026-05-13 19:05:47 +08:00
ZacharyZcR a0162bccdd fix: improve docker-compose guacd config for ARM64 and network isolation 2026-05-13 18:52:47 +08:00
ZacharyZcR fe86e48785 fix: patch guacamole-lite to support protocol VERSION_1_5_0 2026-05-13 18:38:13 +08:00
ZacharyZcR 40d8f56c23 fix: properly detect chacha20-poly1305 support before advertising it 2026-05-13 18:29:39 +08:00
ZacharyZcR e4441f5929 feat: sync admin status from OIDC group membership on login 2026-05-13 18:25:15 +08:00
ZacharyZcR 939bc97181 feat: support RDP/VNC over SSH tunnel via jump hosts 2026-05-13 18:12:43 +08:00
ZacharyZcR 1ca299e141 fix: disable foreign key constraints during database import 2026-05-13 18:02:48 +08:00
ZacharyZcR 4055d076b4 feat: add host filter for snippets 2026-05-13 17:51:05 +08:00
ZacharyZcR c481a5c702 fix: enable VNC TLS/VeNCrypt support with security mode negotiation 2026-05-13 17:34:30 +08:00
ZacharyZcR 5c9d8bfa34 feat: support per-user credentials on shared hosts 2026-05-13 17:26:24 +08:00
ZacharyZcR 453b6ea138 fix: implement guacamole drive redirection file transfer 2026-05-13 17:17:16 +08:00
ZacharyZcR 106b556ce7 feat: add bare-metal deployment configs for Proxmox/LXC
Provide ready-to-use nginx, systemd, and environment configs
for bare-metal installs. Community Proxmox scripts currently
sed-hack the Docker nginx.conf with patterns that don't match,
causing broken pid/log paths and tmpfiles issues on reboot.
2026-05-13 17:12:04 +08:00
ZacharyZcR 1eacb52dab fix: override package.json type to commonjs for electron builds 2026-05-13 16:57:06 +08:00
ZacharyZcR fafc1437cd fix: use streaming download for large files in file manager 2026-05-13 16:52:56 +08:00
ZacharyZcR 9852b1f987 fix: auto-set sudo password in file manager from host config 2026-05-13 16:46:59 +08:00
ZacharyZcR dcc75c980b fix: validate terminal link URLs before opening in external browser 2026-05-13 16:30:41 +08:00
ZacharyZcR 69fbce9594 fix: resolve linux electron backend fork ENOTDIR error 2026-05-13 16:27:31 +08:00
ZacharyZcR cf27af00ac fix: sanitize dpi value before passing to guacd 2026-05-13 16:25:18 +08:00
ZacharyZcR 1727601fbe feat: support runtime base path configuration via BASE_PATH env var 2026-05-13 16:13:20 +08:00
ZacharyZcR 634a84e93d feat: allow sharing opkssh-authenticated hosts 2026-05-13 16:02:24 +08:00
ZacharyZcR 9f316922c3 feat: show pinned hosts in quick actions card 2026-05-13 15:56:49 +08:00
ZacharyZcR 066a503a7a fix: default ignore-cert to true for rdp connections 2026-05-13 15:44:06 +08:00
ZacharyZcR 28734abc6e fix: implement ssh agent forwarding with in-memory agent 2026-05-13 15:36:14 +08:00
ZacharyZcR 77c1a1b518 fix: support folder drag-and-drop upload in file manager 2026-05-13 15:25:21 +08:00
ZacharyZcR 53d4e256ae fix: resolve jump hosts for shared access users 2026-05-13 15:19:18 +08:00
LukeGus be43718260 fix: host connections failing with "Authentication failed" 2026-05-13 02:01:47 -05:00
ZacharyZcR 51d469008c fix: decode oidc client_secret before token exchange 2026-05-13 14:56:45 +08:00
LukeGus 84a8dfa050 fix: remove git commit head line 2026-05-13 01:36:35 -05:00
LukeGus ec87f8a4d1 feat: improve admin settings and user profile 2026-05-13 01:31:11 -05:00
LukeGus 1be5d20744 feat: improve network graph 2026-05-13 01:31:10 -05:00
LukeGus 6212a24567 feat: improve dashboard and commpand pallete 2026-05-13 01:31:10 -05:00
LukeGus 835b98a102 feat: continued migration 2026-05-13 01:30:24 -05:00
LukeGus 0efcb7de7e fix: eslint error and format 2026-05-13 01:29:44 -05:00
LukeGus 93fa749d09 feat: continue migrating content for ui redesign 2026-05-13 01:29:43 -05:00
1982 changed files with 177376 additions and 1863737 deletions

No files matched your search

+15
View File
@@ -1,6 +1,21 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"chore",
"revert"
]
],
"subject-case": [0]
}
}
+45 -21
View File
@@ -5,22 +5,13 @@ yarn-error.log*
dist
build
coverage
release
.next
.nuxt
# Local data, secrets and editor state
db
old_db
ssl
uploads
plugins
.env
.env.*
.mcp.json
.claude
CLAUDE.md
termix-plugin-signing.key
*.tmxplug.sig
.env.local
.env.development.local
.env.test.local
.env.production.local
.vscode
.idea
@@ -38,31 +29,64 @@ Thumbs.db
.git
.gitignore
.husky
README.md
CONTRIBUTING.md
CHANGELOG.md
LICENSE
repo-images/
uploads/
electron/
electron-builder.json
Casks
packaging
*.log
*.tmp
*.temp
logs
*.log
pids
*.pid
*.seed
*.pid.lock
coverage
.nyc_output
jspm_packages/
.npm
.eslintcache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.node_repl_history
*.tgz
.yarn-integrity
.cache
.parcel-cache
*.tgz
.node_repl_history
.next
.nuxt
.vuepress/dist
.serverless
.fusebox/
.dynamodb/
.tern-port
+14
View File
@@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{js,cjs,mjs,jsx,ts,tsx,json,css,scss,md,yml,yaml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+19 -2
View File
@@ -1,5 +1,22 @@
* text=auto eol=lf
*.js text eol=lf
*.cjs text eol=lf
*.mjs text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.sh text eol=lf
*.bash text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
@@ -8,10 +25,10 @@
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.ico binary
*.icns binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.icns binary
-75
View File
@@ -1,75 +0,0 @@
name: Beta feedback
description: A bug, rough edge or idea from a Termix beta
labels: [beta]
body:
- type: markdown
attributes:
value: |
Thanks for testing a beta. Short reports are fine. If you opened this from Termix, the version and setup are already filled in.
- type: input
id: termix-version
attributes:
label: Beta version
description: Shown in Settings under Updates, or your image tag.
placeholder: "e.g. 26.11.0-beta.2"
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: The bug, what felt off, or your idea. Screenshots help a lot.
validations:
required: true
- type: dropdown
id: regression
attributes:
label: Does this happen on the latest stable release?
options:
- "Not tested on stable"
- "Yes, it happens on stable too"
- "No, only on this beta"
- "Not a bug, just feedback"
validations:
required: false
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: As few steps as you can.
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
id: beta-plugins
attributes:
label: Plugins on a beta
description: Filled in by Termix. One plugin and version per line.
render: text
validations:
required: false
- type: textarea
id: environment
attributes:
label: Environment
description: Browser or desktop app, OS, and how the server is installed.
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs
description: Errors from the browser console, `docker logs termix`, or the error Termix shows. This is shown as code, so no backticks needed.
render: text
validations:
required: false
- type: checkboxes
id: checks
attributes:
label: Before you submit
options:
- label: I searched open issues and this is not a duplicate
required: true
-107
View File
@@ -1,107 +0,0 @@
name: Bug report
description: Something in Termix itself is broken
labels: [bug]
type: Bug
body:
- type: markdown
attributes:
value: |
Thanks for the report. A maintainer reads every issue, even when there is no reply right away. We usually reply once there is a fix or a workaround.
If you came from **Report an issue** in Termix, your versions and environment are already filled in below.
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Describe the bug. Screenshots help a lot.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: As few steps as you can.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect?
description: What should have happened instead?
validations:
required: false
- type: input
id: termix-version
attributes:
label: Termix version
description: Shown in Settings under About, or in your User Profile.
placeholder: "e.g. 26.10.0"
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: Where do you see the problem?
options:
- "Web browser"
- "Desktop app (Windows)"
- "Desktop app (macOS)"
- "Desktop app (Linux)"
- "Mobile app (iOS)"
- "Mobile app (Android)"
- "Other"
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: Server install
description: How is the Termix server installed?
options:
- "Docker"
- "Manual build"
- "Proxmox community script"
- "Desktop app (built-in server)"
- "Not sure"
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Browser and OS, plus anything special about your setup (reverse proxy, database, SSO).
placeholder: |
Web, Chrome on Windows. Behind nginx, Postgres.
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs
description: Errors from the browser console, `docker logs termix`, or the error Termix shows. This is shown as code, so no backticks needed.
render: text
validations:
required: false
- type: textarea
id: context
attributes:
label: Anything else?
description: Links, config, or anything that might help.
validations:
required: false
- type: checkboxes
id: checks
attributes:
label: Before you submit
options:
- label: I searched open and closed issues and this is not a duplicate
required: true
- label: I am on the latest version, or I said which version I am on
required: true
+5 -8
View File
@@ -1,11 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Problem with a plugin?
url: https://github.com/Termix-SSH/Termix#plugins
about: Each plugin has its own repo. Find it in this list, or use Report an issue on the plugin's page in Termix.
- name: Questions and chat
- name: Support Center
url: https://github.com/Termix-SSH/Support/issues
about: Report any feature requests or bugs in the support center
- name: Discord
url: https://discord.gg/jVQGdvHDrf
about: Ask the community on Discord. Bugs and ideas belong in issues.
- name: Business or priority support
url: https://termix.site/contact
about: For teams that need a faster response. Contact us to discuss terms.
about: Official Termix Discord server for general discussion and quick support
@@ -1,41 +0,0 @@
name: Feature request
description: Suggest an idea for Termix itself
labels: [feature]
type: Feature
body:
- type: markdown
attributes:
value: |
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type: markdown
attributes:
value: |
Thanks for the idea. Explain the problem first, then how you would solve it.
- type: textarea
id: problem
attributes:
label: What problem would this solve?
description: What are you trying to do, and what gets in the way today?
validations:
required: true
- type: textarea
id: solution
attributes:
label: What would you like?
description: Describe how it should work.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: Anything you tried or considered instead.
validations:
required: false
- type: textarea
id: context
attributes:
label: Anything else?
description: Screenshots, mockups or links.
validations:
required: false
+40
View File
@@ -0,0 +1,40 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
groups:
dev-patch-updates:
dependency-type: "development"
update-types:
- "patch"
dev-minor-updates:
dependency-type: "development"
update-types:
- "minor"
prod-patch-updates:
dependency-type: "production"
update-types:
- "patch"
prod-minor-updates:
dependency-type: "production"
update-types:
- "minor"
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "daily"
groups:
patch-updates:
update-types:
- "patch"
minor-updates:
update-types:
- "minor"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+32
View File
@@ -0,0 +1,32 @@
# Overview
_Short summary of what this PR does_
- [ ] Added: ...
- [ ] Updated: ...
- [ ] Removed: ...
- [ ] Fixed: ...
# Changes Made
_Detailed explanation of changes (if needed)_
- ...
# Related Issues
_Link any issues this PR addresses_
- Closes #ISSUE_NUMBER
- Related to #ISSUE_NUMBER
# Screenshots / Demos
_(Optional: add before/after screenshots, GIFs, or console output)_
# Checklist
- [ ] Code follows project style guidelines
- [ ] Supports mobile and desktop UI/app (if applicable)
- [ ] I have read [Contributing.md](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md)
- [ ] This is not a translation request. See [docs](https://docs.termix.site/translations)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

+25 -100
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Version to build (e.g., 26.10.0)"
description: "Version to build (e.g., 1.8.0)"
required: true
build_type:
description: "Build type"
@@ -13,154 +13,79 @@ 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., 26.10.0)"
required: true
type: string
build_type:
description: "Build type (Development, Beta 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:
# One image per plugin profile (docker/bundled-plugins.json). standard keeps
# the plain tags; minimal and full add a suffix.
build:
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
profile: [standard, minimal, full]
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v5
with:
ref: ${{ inputs.source_ref || github.ref }}
fetch-depth: 1
- name: Resolve source revision
run: |
echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
echo "BUILD_CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV"
# The image reads its version from package.json, so a beta has to say so.
- name: Stamp beta version
if: ${{ inputs.build_type == 'Beta' }}
run: node scripts/sync-version.cjs --version "${{ inputs.version }}"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
with:
platforms: linux/amd64,linux/arm64
- name: Setup Docker Buildx
uses: useblacksmith/setup-docker-builder@v2
with:
cache-key: termix-ssh/termix
uses: docker/setup-buildx-action@v4
- name: Determine tags
id: tags
run: |
VERSION=${{ inputs.version }}
BUILD_TYPE=${{ inputs.build_type }}
VERSION=${{ github.event.inputs.version }}
BUILD_TYPE=${{ github.event.inputs.build_type }}
TAGS=()
ALL_TAGS=()
SUFFIX=""
if [ "${{ matrix.profile }}" != "standard" ]; then
SUFFIX="-${{ matrix.profile }}"
fi
if [ "$BUILD_TYPE" = "Production" ]; then
TAGS+=("$VERSION$SUFFIX" "latest$SUFFIX")
elif [ "$BUILD_TYPE" = "Beta" ]; then
TAGS+=("beta$SUFFIX" "$VERSION$SUFFIX")
else
TAGS+=("dev-$VERSION$SUFFIX")
fi
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("ghcr.io/termix-ssh/termix:$tag")
if [ "$BUILD_TYPE" != "Development" ]; then
TAGS+=("release-$VERSION" "latest")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
fi
done
done
else
TAGS+=("dev-$VERSION")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
done
fi
echo "ALL_TAGS=$(IFS=,; echo "${ALL_TAGS[*]}")" >> $GITHUB_ENV
echo "TAGS=${TAGS[*]}" >> $GITHUB_ENV
- name: Login to GHCR
if: ${{ !inputs.dry_run }}
uses: docker/login-action@v4
with:
registry: ghcr.io
username: lukegus
password: ${{ secrets.TERMIX_PAT }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Login to Docker Hub (prod and beta only)
if: ${{ (inputs.build_type == 'Production' || inputs.build_type == 'Beta') && !inputs.dry_run }}
- name: Login to Docker Hub (prod only)
if: ${{ github.event.inputs.build_type == 'Production' }}
uses: docker/login-action@v4
with:
username: bugattiguy527
password: ${{ secrets.DOCKERHUB_BUGATTIGUY527_TOKEN }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push multi-arch image
uses: useblacksmith/build-push-action@v2
uses: docker/build-push-action@v7
with:
context: .
file: ./docker/Dockerfile
push: ${{ !inputs.dry_run }}
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ env.ALL_TAGS }}
build-args: |
BUILDKIT_INLINE_CACHE=1
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
PLUGIN_PROFILE=${{ matrix.profile }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ env.SOURCE_SHA }}
org.opencontainers.image.created=${{ env.BUILD_CREATED }}
outputs: ${{ inputs.dry_run && 'type=cacheonly' || 'type=registry,compression=zstd' }}
- name: Login to Docker Hub as termixssh
id: termix_hub
if: ${{ (inputs.build_type == 'Production' || inputs.build_type == 'Beta') && !inputs.dry_run }}
env:
TOKEN: ${{ secrets.DOCKERHUB_TERMIXSSH_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "DOCKERHUB_TERMIXSSH_TOKEN is not set, skipping docker.io/termixssh"
exit 0
fi
echo "$TOKEN" | docker login docker.io --username termixssh --password-stdin
echo "ready=true" >> "$GITHUB_OUTPUT"
- name: Copy image to docker.io/termixssh
if: ${{ steps.termix_hub.outputs.ready == 'true' }}
run: |
for tag in $TAGS; do
docker buildx imagetools create --tag "docker.io/termixssh/termix:$tag" "ghcr.io/termix-ssh/termix:$tag"
done
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.run_id }}
outputs: type=registry,compression=gzip,compression-level=9
- name: Cleanup Docker
if: always()
File diff suppressed because it is too large. Load diff
+32
View File
@@ -0,0 +1,32 @@
name: Generate OpenAPI Specification
on:
workflow_dispatch:
jobs:
generate-openapi:
name: Generate OpenAPI JSON
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Generate OpenAPI specification
run: npm run generate:openapi
- name: Upload OpenAPI artifact
uses: actions/upload-artifact@v4
with:
name: openapi-spec
path: openapi.json
retention-days: 90
+5 -89
View File
@@ -13,108 +13,24 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
# Bundled plugins are counted as downloads in the plugin store, so a
# build only fetches them when the pins change.
- name: Cache bundled plugins
uses: actions/cache@v6
with:
path: node_modules/.cache/termix-bundled-plugins
key: bundled-plugins-${{ hashFiles('docker/bundled-plugins.json') }}
enableCrossOsArchive: true
- name: Build plugin SDK
run: npm run build:sdk
- name: Lint
run: npm run lint
- name: Run ESLint
run: npx eslint .
- name: Run Prettier check
run: npx prettier --check .
- name: Type check
run: npm run type-check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Run unit tests
run: npm run test
database-dialects:
name: Postgres and MySQL
runs-on: blacksmith-2vcpu-ubuntu-2404
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
- name: Build plugin SDK
run: npm run build:sdk
- 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
- 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
-84
View File
@@ -1,84 +0,0 @@
name: Publish SDK
on:
workflow_dispatch:
inputs:
mode:
description: "Publish mode"
required: true
default: Publish
type: choice
options:
- Publish
- Dry run
permissions:
contents: write
id-token: write
jobs:
publish:
name: Publish @termix-ssh/plugin-sdk
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--max-old-space-size=4096"
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: npm
registry-url: https://registry.npmjs.org
- name: Update npm
run: npm install -g npm@^11.19.0
- run: npm ci
- name: Check the version is new
run: |
VERSION=$(node -p "require('./packages/plugin-sdk/package.json').version")
if npm view "@termix-ssh/plugin-sdk@$VERSION" version >/dev/null 2>&1; then
echo "@termix-ssh/plugin-sdk@$VERSION is already on npm. Bump packages/plugin-sdk/package.json first."
if [ "${{ inputs.mode }}" = "Publish" ]; then
exit 1
fi
fi
echo "Publishing @termix-ssh/plugin-sdk@$VERSION"
- run: npm run build:sdk
- run: npm run lint
- run: npm run type-check
- run: npm run test
- name: Publish
run: |
if [ "${{ inputs.mode }}" = "Dry run" ]; then
npm publish --workspace @termix-ssh/plugin-sdk --provenance --access public --dry-run
else
npm publish --workspace @termix-ssh/plugin-sdk --provenance --access public
fi
- name: Tag and release
if: ${{ inputs.mode == 'Publish' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(node -p "require('./packages/plugin-sdk/package.json').version")
node -e "
const fs = require('fs');
const { changelogSection } = require('./packages/plugin-sdk/dist/changelog.js');
const notes = changelogSection(fs.readFileSync('packages/plugin-sdk/CHANGELOG.md', 'utf8'), process.argv[1]);
fs.writeFileSync('sdk-notes.md', notes);
" "$VERSION"
gh release create "sdk-v$VERSION" \
--target "${{ github.sha }}" \
--title "Plugin SDK v$VERSION" \
--notes-file sdk-notes.md \
--latest=false
-682
View File
@@ -1,682 +0,0 @@
name: Release
# Stable: run by hand from a dev-X.Y.Z branch. Beta: run by hand from a dev
# branch, or let the Tuesday and Friday schedule snapshot the newest dev
# branch when it moved since the last beta.
on:
schedule:
- cron: "0 15 * * 2,5"
workflow_dispatch:
inputs:
channel:
description: "Stable release or beta"
required: true
default: Stable
type: choice
options:
- Stable
- Beta
mode:
description: "Release mode (Overwrite release is stable only)"
required: true
default: Everything
type: choice
options:
- Everything
- Overwrite release
- Dry run
- Skip submit
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
prep:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
channel: ${{ steps.mode.outputs.channel }}
mode: ${{ steps.mode.outputs.mode }}
skip: ${{ steps.beta.outputs.skip || 'false' }}
version: ${{ steps.info.outputs.version || steps.beta.outputs.version }}
release_tag: ${{ steps.info.outputs.release_tag || steps.beta.outputs.release_tag }}
mobile_version: ${{ steps.info.outputs.mobile_version }}
mobile_tag: ${{ steps.info.outputs.mobile_tag }}
dev_branch: ${{ steps.info.outputs.dev_branch || steps.beta.outputs.dev_branch }}
sha: ${{ steps.beta.outputs.sha }}
prev_ref: ${{ steps.beta.outputs.prev_ref }}
steps:
- name: Resolve channel and mode
id: mode
run: |
CHANNEL="${{ inputs.channel || 'Beta' }}"
MODE="${{ inputs.mode || 'Everything' }}"
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
echo "mode=$MODE" >> "$GITHUB_OUTPUT"
if [ "$CHANNEL" = "Beta" ] && [ "$MODE" = "Overwrite release" ]; then
echo "Overwrite release is for stable releases only."
exit 1
fi
- name: Guard branch (release modes)
if: ${{ github.event_name == 'workflow_dispatch' && steps.mode.outputs.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: ${{ steps.mode.outputs.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
if: ${{ steps.mode.outputs.channel == 'Stable' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ steps.mode.outputs.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=v$VERSION" >> "$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$//; s/^v//')
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"
echo "mobile_tag=$MOBILE_RAW" >> "$GITHUB_OUTPUT"
- name: Check CHANGELOG.md has notes for this version
if: ${{ steps.mode.outputs.channel == 'Stable' }}
run: |
node scripts/generate-release-body.cjs \
--version "${{ steps.info.outputs.version }}" \
--mobile-version "${{ steps.info.outputs.mobile_version }}" \
--mobile-tag "${{ steps.info.outputs.mobile_tag }}" > /dev/null
- name: Resolve beta version
id: beta
if: ${{ steps.mode.outputs.channel == 'Beta' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
if [ "${{ github.event_name }}" = "schedule" ]; then
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 "No dev-X.Y.Z branch is open, so no beta today."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
else
DEV_BRANCH="${GITHUB_REF#refs/heads/}"
fi
BASE=$(node scripts/parse-dev-branch.cjs "$DEV_BRANCH")
SHA=$(gh api "repos/${{ github.repository }}/branches/$DEV_BRANCH" -q .commit.sha)
TAGS=$(gh api "repos/${{ github.repository }}/git/matching-refs/tags/v$BASE-beta." -q '.[].ref' | sed 's#^refs/tags/##')
LATEST=$(printf '%s\n' "$TAGS" | node scripts/beta-version.cjs "$BASE" --latest)
VERSION=$(printf '%s\n' "$TAGS" | node scripts/beta-version.cjs "$BASE")
if [ -n "$LATEST" ]; then
LATEST_SHA=$(gh api "repos/${{ github.repository }}/commits/$LATEST" -q .sha)
if [ "$LATEST_SHA" = "$SHA" ] && [ "${{ github.event_name }}" = "schedule" ]; then
echo "$DEV_BRANCH has not moved since $LATEST, so no beta today."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
PREV="$LATEST"
else
PREV=$(gh release view --repo ${{ github.repository }} --json tagName -q .tagName 2>/dev/null || true)
fi
echo "Beta $VERSION from $DEV_BRANCH @ $SHA, changes since ${PREV:-nothing}"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "dev_branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "release_tag=v$VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "prev_ref=$PREV" >> "$GITHUB_OUTPUT"
# Translates new or changed strings with the Termix LLM server and pins the
# bundled plugins to their newest registry releases, then commits both to the
# dev branch so the beta or stable build includes them. If the server is down
# it warns and the release goes on with what is there.
translate:
needs: [prep]
if: ${{ needs.prep.outputs.skip != 'true' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
sha: ${{ steps.commit.outputs.sha }}
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.sha || needs.prep.outputs.dev_branch }}
fetch-depth: 1
token: ${{ secrets.TERMIX_PAT }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Translate
uses: Termix-SSH/Termix-Registry/translate@main
with:
source: src/ui/locales/en.json
out: src/ui/locales/translated
languages: src/ui/locales/languages.json
api-key: ${{ secrets.TERMIX_LLM_API_KEY }}
- name: Install dependencies
run: npm ci --ignore-scripts
# Newest stable plugin release whose engine.termix allows this version.
- name: Pin bundled plugins
run: node scripts/pin-bundled-plugins.cjs --all --core-version "${{ needs.prep.outputs.version }}"
- name: Commit translations and plugin pins
id: commit
run: |
git add src/ui/locales docker/bundled-plugins.json
if git diff --cached --quiet; then
echo "No translation changes."
elif [ "${{ needs.prep.outputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would commit these translation and pin changes:"
git --no-pager diff --cached --stat
else
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git commit -m "chore: update translations and plugin pins"
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
verify-beta:
needs: [prep, translate]
if: ${{ needs.prep.outputs.channel == 'Beta' && needs.prep.outputs.skip != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout beta commit
uses: actions/checkout@v7
with:
ref: ${{ needs.translate.outputs.sha || needs.prep.outputs.sha }}
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
# Bundled plugins are counted as downloads in the plugin store, so a
# build only fetches them when the pins change.
- name: Cache bundled plugins
uses: actions/cache@v6
with:
path: node_modules/.cache/termix-bundled-plugins
key: bundled-plugins-${{ hashFiles('docker/bundled-plugins.json') }}
enableCrossOsArchive: true
- name: Build plugin SDK
run: npm run build:sdk
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build
run: npm run build
- name: Run unit tests
run: npm run test
normalize:
needs: [prep, translate]
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.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.TERMIX_PAT }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
# Manifest validation in lint loads the built SDK.
- name: Build SDK
run: npm run build:sdk
- name: Lint and format
run: |
npm run lint:fix
npm run format
npm run lint
- 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 [ "${{ needs.prep.outputs.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 }}"
sync-main:
needs: [prep, normalize]
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Dry run' && needs.prep.outputs.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.TERMIX_PAT }}
- name: Merge main into dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git fetch origin main
# -X ours leaves modify/delete conflicts, so settle those to the dev side too
if ! git merge origin/main -X ours --no-edit; then
git diff --name-only --diff-filter=U | while read -r f; do
if git cat-file -e "HEAD:$f" 2>/dev/null; then
git checkout --ours -- "$f" && git add -- "$f"
else
git rm -q -- "$f"
fi
done
git commit --no-edit
fi
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
merge-to-main:
needs: [prep, translate, normalize, sync-main]
if: ${{ always() && needs.prep.result == 'success' && needs.prep.outputs.channel == 'Stable' && (needs.translate.result == 'success' || needs.translate.result == 'skipped') && (needs.normalize.result == 'success' || needs.normalize.result == 'skipped') && (needs.sync-main.result == 'success' || needs.sync-main.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 }}" \
--mobile-tag "${{ needs.prep.outputs.mobile_tag }}" > PR_BODY.md
- name: Open and squash-merge PR to main
id: merge
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
DEV_BRANCH="${{ needs.prep.outputs.dev_branch }}"
TITLE="v${{ needs.prep.outputs.version }}"
if [ "${{ needs.prep.outputs.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 [ "${{ needs.prep.outputs.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, translate, verify-beta, merge-to-main]
if: ${{ always() && needs.prep.result == 'success' && needs.prep.outputs.skip != 'true' && (needs.merge-to-main.result == 'success' || needs.verify-beta.result == 'success') }}
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.version }}
build_type: ${{ needs.prep.outputs.channel == 'Beta' && 'Beta' || 'Production' }}
dry_run: ${{ needs.prep.outputs.mode == 'Dry run' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref || needs.translate.outputs.sha || needs.prep.outputs.sha }}
secrets: inherit
create-release:
needs: [prep, merge-to-main, docker]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.docker.result == 'success' && needs.prep.outputs.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: ${{ needs.prep.outputs.mode == 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
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: ${{ needs.prep.outputs.mode != 'Overwrite release' }}
run: |
node scripts/generate-release-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--mobile-version "${{ needs.prep.outputs.mobile_version }}" \
--mobile-tag "${{ needs.prep.outputs.mobile_tag }}" > RELEASE_BODY.md
- name: Create or update GitHub release
if: ${{ needs.prep.outputs.mode != 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
TAG="${{ needs.prep.outputs.release_tag }}"
TITLE="Termix v${{ 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
create-beta-release:
needs: [prep, translate, docker]
if: ${{ always() && needs.prep.outputs.channel == 'Beta' && needs.docker.result == 'success' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout beta commit
uses: actions/checkout@v7
with:
ref: ${{ needs.translate.outputs.sha || needs.prep.outputs.sha }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Generate beta release body
run: |
node scripts/generate-beta-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--branch "${{ needs.prep.outputs.dev_branch }}" \
--sha "${{ needs.translate.outputs.sha || needs.prep.outputs.sha }}" \
--prev "${{ needs.prep.outputs.prev_ref }}" > BETA_BODY.md
- name: Create beta release
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
VERSION="${{ needs.prep.outputs.version }}"
TITLE="Termix v${VERSION%-beta.*} Beta ${VERSION##*-beta.}"
TAG="${{ needs.prep.outputs.release_tag }}"
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_BODY.md --prerelease
else
gh release create "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_BODY.md \
--prerelease --target "${{ needs.translate.outputs.sha || needs.prep.outputs.sha }}"
fi
electron-release:
needs:
[
prep,
translate,
merge-to-main,
docker,
create-release,
create-beta-release,
]
if: ${{ always() && needs.prep.outputs.skip != 'true' && needs.docker.result == 'success' && needs.create-release.result != 'failure' && needs.create-beta-release.result != 'failure' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: ${{ needs.prep.outputs.mode == 'Dry run' && 'file' || 'release' }}
release_tag: ${{ needs.prep.outputs.release_tag }}
version_override: ${{ needs.prep.outputs.channel == 'Beta' && needs.prep.outputs.version || '' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref || needs.translate.outputs.sha || needs.prep.outputs.sha }}
secrets: inherit
electron-submit:
needs: [prep, merge-to-main, electron-release]
if: ${{ always() && needs.electron-release.result == 'success' && needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Dry run' && needs.prep.outputs.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: ${{ always() && needs.electron-release.result == 'success' && needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.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.TERMIX_PAT }}
- 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
prune-betas:
needs: [prep, create-release]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.create-release.result == 'success' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Delete this version's beta releases (tags stay)
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
PREFIX="v${{ needs.prep.outputs.version }}-beta."
gh release list --repo ${{ github.repository }} --limit 200 --json tagName -q '.[].tagName' \
| grep -F "$PREFIX" | while read -r TAG; do
echo "Deleting release $TAG"
gh release delete "$TAG" --repo ${{ github.repository }} --yes || true
done
# The docs site reads the env catalog, API spec and download list from the
# newest stable release.
# Docs written for this release live on dev-X.Y.Z in the Docs repo. Squash
# merge it into main once the release is out. No branch means nothing to do.
docs-merge:
needs: [prep, electron-release]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.electron-release.result == 'success' && needs.prep.outputs.mode != 'Dry run' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Merge the docs dev branch into main
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
REPO: Termix-SSH/Docs
VERSION: ${{ needs.prep.outputs.version }}
run: |
BRANCH="dev-$VERSION"
if ! gh api "repos/$REPO/branches/$BRANCH" > /dev/null 2>&1; then
echo "$REPO has no $BRANCH branch, nothing to merge."
exit 0
fi
AHEAD=$(gh api "repos/$REPO/compare/main...$BRANCH" -q .ahead_by)
if [ "$AHEAD" = "0" ]; then
echo "$BRANCH has nothing main doesn't, deleting it."
gh api -X DELETE "repos/$REPO/git/refs/heads/$BRANCH"
exit 0
fi
PR_NUMBER=$(gh pr list --repo "$REPO" --head "$BRANCH" --base main --state open --json number -q '.[0].number')
if [ -z "$PR_NUMBER" ]; then
PR_NUMBER=$(gh pr create --repo "$REPO" --base main --head "$BRANCH" \
--title "v$VERSION" --body "Docs for Termix v$VERSION." | grep -oE '[0-9]+$')
fi
gh pr merge "$PR_NUMBER" --repo "$REPO" --squash --admin --delete-branch
STATE=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json state -q .state)
if [ "$STATE" != "MERGED" ]; then
echo "Docs PR #$PR_NUMBER did not merge (state: $STATE)."
exit 1
fi
docs-refresh:
needs: [prep, electron-release, docs-merge]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.electron-release.result == 'success' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Tell the docs site
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: gh api repos/Termix-SSH/Docs/dispatches -f event_type=core-released
cleanup:
needs: [prep, merge-to-main, electron-release, cask-commit-back]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && (needs.prep.outputs.mode == 'Everything' || needs.prep.outputs.mode == 'Skip submit') && needs.merge-to-main.result == 'success' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Delete dev branch in Termix
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
git push https://x-access-token:${{ secrets.TERMIX_PAT }}@github.com/${{ github.repository }}.git \
--delete "${{ needs.prep.outputs.dev_branch }}" || true
-53
View File
@@ -1,53 +0,0 @@
name: Translate
on:
workflow_dispatch:
inputs:
retranslate:
description: "Translate every string again, not only new or changed ones"
type: boolean
default: false
permissions:
contents: write
concurrency:
group: translate-${{ github.ref }}
cancel-in-progress: false
jobs:
translate:
if: ${{ startsWith(github.ref, 'refs/heads/dev-') }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
token: ${{ secrets.TERMIX_PAT }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Translate
uses: Termix-SSH/Termix-Registry/translate@main
with:
source: src/ui/locales/en.json
out: src/ui/locales/translated
languages: src/ui/locales/languages.json
retranslate: ${{ inputs.retranslate }}
api-key: ${{ secrets.TERMIX_LLM_API_KEY }}
- name: Commit translations
run: |
git add src/ui/locales
if git diff --cached --quiet; then
echo "No translation changes."
exit 0
fi
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git commit -m "chore: update translations"
git push origin HEAD:"${GITHUB_REF_NAME}"
+1 -15
View File
@@ -7,23 +7,13 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
src/mcp-server/node_modules
dist
dist-ssr
coverage
*.local
# Plugin build output. termix-plugin build writes it, the packaged server
# gets its copy under dist/plugins.
*.tmxplug
*.tmxplug.sig
termix-plugin-signing.key
/src/**/*.js
.vscode/
.idea/
.idea
.DS_Store
Thumbs.db
*.suo
*.ntvs*
*.njsproj
@@ -41,7 +31,3 @@ electron/build-info.cjs
/.mcp.json
/CLAUDE.md
/old_db/
/scripts/auto-fix.mjs
/scripts/auto-fix-blocklist.json
/scripts/auto-fix-state.json
/scripts/auto-fix-report-*.json
+1 -1
View File
@@ -1 +1 @@
npx --no -- commitlint --edit "$1"
npx --no -- commitlint --edit $1
+1 -3
View File
@@ -1,3 +1 @@
# The .cmd shims on Windows add their own paths to each command, so keep
# lint-staged's chunks well under cmd.exe's 8191 character limit.
npx lint-staged --max-arg-length 4000
npx lint-staged
+9 -4
View File
@@ -1,13 +1,18 @@
build
coverage
dist
node_modules
package-lock.json
dist-ssr
release
node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
db
.env
*.min.js
*.min.css
openapi.json
drizzle/
-204
View File
@@ -1,204 +0,0 @@
# Changelog
## 26.10.6
### Added
- The mobile app can sign in through your browser with any login method, including passkeys and security keys
## 26.10.5
### Added
- Safe mode: start the server with TERMIX_SAFE_MODE=1 to run only layout and theme plugins, so a broken plugin can be turned off
- TERMIX_PLUGIN_REGISTRY_PRIVATE_HOSTS lets plugin registries live on a private address, like a registry in your LAN
- Plugin registry browsing and downloads use HTTP_PROXY and HTTPS_PROXY when they are set
- Docker image sizes: the default image now ships the most used plugins, the minimal tag ships only the look plugins and the full tag ships every official plugin
- Plugins an older image shipped and you had on are downloaded from the registry on the first start of a smaller image, so nothing goes missing
### Changed
- Card grids in the Plugins tab and in plugins can show Auto (as many as fit) or a set number of cards per row
### Fixed
- The macOS app asks for Local Network access, so it can reach servers on your LAN
- The desktop app can change the connection origin of a host shared with you, instead of failing to save
## 26.10.4
### Fixed
- Pin classic layout plugin (fixes proxmox install)
## 26.10.3
### Added
- Choose the layout of the whole app in Settings, Appearance. Admins can also set the default layout and theme for everyone
- Plugins can add layouts and themes
- A repair screen shows when the chosen layout cannot load, with ways to update, switch, reset or turn it off. Open it any time with ?safe-layout=1, or for everyone with TERMIX_SAFE_LAYOUT=1
- Hovering a theme in Settings previews it
- Restore the bundled copy of a plugin from the repair screen, which also pins the shipped version
- Switching layouts closes the tabs only the old layout could show, like its Settings tab
- A chosen layout whose plugin was uninstalled falls back to the default instead of the repair screen
- Filter plugins by type, like layouts, themes or tabs, from a filter list beside the results
- Browse shows plugins a page at a time
- Plugins that need a newer Termix are hidden in Browse, with a toggle to show them
- Hosts can be managed from a config file through a plugin. Managed hosts are read only for everyone, but pins and order still work
- A plugin can block adding hosts by hand, so every host comes from one place
- Plugins can set login rules: sign out after a time without activity, limit how long a session lasts, turn off remember me and require a second factor
- A user who must have a second factor but has none can sign in only to set one up
- Admins can redirect HTTP to HTTPS and send HSTS while HTTPS is on. Off by default so reverse proxies keep working
- A plugin can answer all sync requests and use the replica store, change feed and linked server list, so sync no longer lives in the core app
- The desktop app can hold several accounts, each with its own copy of a server's data. The app signs in as the active one and its auto session follows a switch
- The linked server list holds one link per account, with room for more connection options per server
- A plugin can carry SSH connections over its own stream, and core relays a host's connection over a socket for another Termix
- Plugins can create a folder with a color and icon and remove it when it is empty
- Another Termix server can manage this one with an API key, which can now ask for a one minute ticket to open plugin sockets as its user
- FIDO2 security keys (ed25519-sk and ecdsa-sk) work through SSH Agent authentication when Termix runs in the desktop app. The key asks for a touch or PIN on your own machine
- Pasting a security key file now explains how to use it instead of failing to decode it
- Plugins can add a panel that sits beside a host tab, like a terminal. You can open, resize and move it
- The desktop app can link to a server that requires a client certificate (mutual TLS). It uses a PFX or P12 file for sync and the certificate in your system store for the app window
- Plugins can show a web page in a native view in the desktop app, for sites that do not load in an iframe
- The desktop app draws its own title bar in the colors of your theme instead of the native white one
### Changed
- Server and desktop sync moved out of the core app into a plugin, including the Sync screen, the link wizard and the first run step. Existing links and offline copies keep working
- The icon rail, sidebar, tab bar, dock, Dashboard, Manage, Settings and command palette now come from a bundled layout. It can be swapped for another layout but not removed, and a fix to it no longer needs a Termix release
- Your theme is saved to your account so your other devices use it too, unless you keep settings on this device only
- The themes besides light and dark now come from a bundled themes plugin. It can be swapped but not removed, and Termix keeps a basic light and dark theme of its own
- A theme that no longer exists shows as System instead of an unstyled page
- Installs and updates from a registry check the Termix version a plugin needs, not only its plugin API
- Host online and offline dots now come from the Host Status plugin. The check interval setting moved to its settings and keeps its value. Without the plugin hosts show no dot
- Audit log forwarding and the CSV and NDJSON export now come from the Audit Tools plugin. The collector URL and token move to its settings on upgrade. The audit log viewer, retention and the audit log itself stay in Termix
- Client tunnels on your own computer and opening a host in Windows Remote Desktop moved out of the desktop app into plugins, so fixes to them no longer need a new desktop build
- The local terminal, the local side of the file manager with its transfers, and opening a file in an external editor moved out of the desktop app into plugins, so fixes to them no longer need a new desktop build
- The recent activity list and the first run steps for the preset and for theme, accent, font and language now come from bundled plugins. Existing entries and choices are kept, and people who went through those steps are not asked again
- Branding (app name, tagline and logo) is now saved by the bundled layout plugin. An existing setup keeps its branding, and the sign in screen still reads it from Termix
- Without the plugin that stores recent activity, the list stays empty and nothing is recorded
- Importing hosts from a JSON or SSH config file and exporting hosts now come from the Import and Export plugin. Exports are now written to the audit log. Without the plugin there is no import or export
- Generating and checking SSH keys in the credential editor and deploying a key to a host now come from the Credential Tools plugin. Deploys are now written to the audit log. Without the plugin those buttons are hidden
### Fixed
- Linking the desktop app to a server no longer stalls on the server dashboard after sign in
- Exporting your data no longer fails when a plugin table has a text key, like saved passkeys
- The Windows on ARM desktop app starts again instead of failing with a local server error
### Removed
- The desktop app no longer has its own client tunnel and native Remote Desktop code
## 26.10.2
### Added
- Hide sidebar after opening, in Customize Sidebar, closes the host list once you open a host
- Sort the dashboard's Host Status card by name, IP address or status
- Windows on ARM builds of the desktop app (installer, MSI and portable)
- Plugins get an event when a host is added
- Plugins can start a short-lived session as a user and read the user's language, for tasks that run without a browser
- Plugins can pin a panel to the right edge of the window that pushes the page over
### Changed
- The Docker Compose files no longer start a guacd container. npm run dev -- --guacd also points remote desktop at the guacd it starts
- The side menu now opens over the page when it expands on hover, so open sessions like remote desktop are not resized each time. Turn on Push content when it expands in Settings to get the old behavior back
- On macOS, moving between split panes is now Cmd+Option+Arrow, so Option+Arrow can move the cursor by word in a terminal
### Fixed
- Picking a section lower down in the host editor, like Warpgate, stuttered on the way and sometimes stopped before it got there
- Sorting hosts by IP address put 10.0.0.210 before 10.0.0.3
- The local terminal failed to start in the universal macOS build
- Hosts shared with you never showed up on a linked desktop
- A linked desktop could never turn on an unsigned plugin the server had enabled
- Creating, cloning or deleting hosts failed with "no such table: main.p_vault_profiles" after the Vault plugin was removed
- Opening a user in Settings, Users blocked the other admin sections until you went back to the user list
- A linked desktop could keep showing the same sync conflicts, or delete a local host, when the server could not send its copy
- Usernames are now unique
- Importing user data keeps foreign keys on
- Trusted proxy login checks the address nginx saw, not nginx itself
- The desktop login frame only trusts the server's own origin
- External login URLs and callback ports are checked on the desktop app
- A changed jump host key is no longer accepted without asking
- Synced plugins signed only by a key the server sent are no longer turned on by themselves
- Two connects to the same host at once could overwrite each other's connection
- Indonesian uses the same technical terms everywhere
- When the server was down or still starting, the login page said it could not connect to the database and stopped trying. It now says the server is not responding and continues on its own once the server is back
- nginx answered 404 instead of 502 while the backend was down
- A client tunnel that could not start always said "Endpoint SSH connection closed" on the desktop app instead of the real reason, like the target refusing the connection
- The Docker server crashed and restarted every few hours in ssh2's native crypto on Node 24
- Plugin names and descriptions showed in English in the plugin manager, setup picker and host editor no matter the UI language
- Creating a host through the API without an auth type failed with a server error. It now picks the type from the credential, key or password, or says auth type is required
- The desktop app would not start its local server when another program used port 30001. It now picks a free port on its own
- A linked desktop could never finish syncing with a server on Postgres
- Routine plugin activity filled the audit log and pushed out real security entries
- Opening a full-screen view link while signed out showed a spinner forever. It now shows the login and opens the link after
- A linked desktop behind a proxy that needs extra headers, or with an allowed self-signed certificate, could not open remote terminals or tunnels
## 26.10.1
### Added
- Slovak, Croatian, Slovenian, Lithuanian, Latvian, Estonian and Malay
- The desktop app can update itself. Pick Automatic, Ask first or Never in Settings, Updates. Store and package manager installs keep updating the way they do now
- Community plugins. Turn on the community registry in Plugins, Registries to browse and install plugins made by other people, each version reviewed before it is listed
- Custom plugin registries. Add your own registry by its index URL; its signing key is pinned when you add it
### Changed
- Every language is now fully translated, in Termix and in every plugin, and stays up to date with each release
- Languages with more than two plural forms, like Russian, Polish and Arabic, now use the right one
- The Plugins tab shows how many times each plugin was downloaded, in a Downloads column
- Plugins, Browse hides plugins you already have. Turn on Show installed to see them
### Fixed
- Regional browser languages like `es-MX`, `pt` or `zh-HK` showed English instead of the closest translation
- Counts like "1 keys" or "Moved 1 host(s)" now use the singular
- Plugin updates on the desktop app, synced or installed from the Plugins tab, failed to start with "Cannot find package"
- The desktop app showed plugins that only run on the server as not running and out of date
- Keep the terminal WebSocket open past nginx's 60s default
- Plugin download counts now show in the Plugins tab without waiting for the background check
- Clicking into a saved password, key passphrase or sudo password field and saving without typing erased it, which stopped sudo autofill
- The desktop app now says when its data is from 2.8 or older and needs 2.9 first, instead of only saying the local server could not start
- Client tunnels on the desktop app stopped with "Endpoint SSH connection closed" when a reverse proxy dropped their idle connection
- Typing right after opening a host from the sidebar went to the host search instead of the new session
- SSH config import failed on files that put a tab or `=` between a keyword and its value
- App icons and the favicon have rounded corners again
## 26.10.0
### Added
- Plugins. Every feature beyond hosts, credentials and sharing is now its own plugin with its own repo, and you pick which ones you want
- A Plugins tab to install, update, pin, turn off and remove plugins from the official registry, with release notes and a beta channel per plugin
- Developer mode to install a plugin from a file, and a plugin SDK on npm to build your own
- The community plugin registry is open for submissions. Termix can install community plugins in a later update
- Redesigned UI with a Manage tab for hosts and credentials and a Settings tab with search
- New full screen onboarding where you choose which plugins to start with
- Beta channel for Termix itself in Settings > Updates
- Docs links on every settings page, in the Plugins tab, in plugin settings and in the command palette
- Plugins can link their own docs and list the environment variables they read, shown on their page in the Plugins tab
- Report a bug links that open a prefilled issue in the right repo
- `TRUSTED_PROXIES` to choose which proxies nginx takes client IPs from
### Changed
- Termix now uses calendar versions (year, month, patch), starting with 26.10.0
- Redesigned host defaults with admin, user and folder levels
- Simpler host status: online or offline, kept live without a refresh
- Bug reports and feature requests now go to each repo instead of the Support repo
### Fixed
- A lot of bugs across the app, the desktop app and every plugin
- Several security issues reported through GitHub advisories
### Removed
- Downgrading to 2.8 is no longer supported. A 2.8 install has to start 2.9 once before upgrading
- The donation reminder popup
+128
View File
@@ -0,0 +1,128 @@
# 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.
+65 -34
View File
@@ -1,10 +1,8 @@
# Contributing
Termix is a small core plus plugins. This repo is the core: hosts, credentials, users, sharing, sessions, the app shell and the plugin runtime. Every other feature lives in its own `Plugin-<Name>` repo. If your change is about the terminal, file manager, Docker or any other plugin feature, open it in that plugin's repo instead.
## Prerequisites
- [Node.js](https://nodejs.org/en/download/) 22.12 or newer (built with v24)
- [Node.js](https://nodejs.org/en/download/) (built with v24)
- [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
- [Git](https://git-scm.com/downloads)
@@ -21,55 +19,88 @@ Termix is a small core plus plugins. This repo is the core: hosts, credentials,
## Running the development server
Run the following commands:
```sh
npm run dev
npm run dev:backend
```
This builds everything, starts the backend and then the Vite server at `http://localhost:5173/`. Backend changes restart the backend, and plugin changes in `../Plugins` rebuild and reload that plugin. `npm run dev:electron` does the same with the desktop app, and `npm run dev -- --help` lists the other options.
This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.
## Useful commands
## Contributing
```sh
npm run test # run the test suite
npm run type-check # type-check the core
npm run lint # lint and the other repo checks
npm run format # format the code with Prettier
```
## Making a change
1. Fork the [repo](https://github.com/Termix-SSH/Termix).
2. Create a branch:
1. **Fork the repository**: Click the "Fork" button at the top right of
the [repository page](https://github.com/Termix-SSH/Termix).
2. **Create a new branch**:
```sh
git checkout -b feature/my-new-feature
```
3. Make your changes and add or update tests for them.
4. Commit your changes:
3. **Make your changes**: Implement your feature, fix, or improvement.
4. **Commit your changes**:
```sh
git commit -m "feat: add my new feature"
git commit -m "Feature request my new feature"
```
5. Push to your fork:
5. **Push to your fork**:
```sh
git push origin feature/my-new-feature
git push origin feature/my-feature-request
```
6. Open a pull request against the current `dev-*` branch with a clear description.
6. **Open a pull request**: Go to the original repository and create a PR with a clear description.
## Guidelines
- Follow the existing code style. The frontend uses React, Tailwind CSS and shadcn components in `src/ui/`.
- Never hardcode text. Use `t()` and add the English string to `src/ui/locales/en.json`. That's the only locale file you need to update. Other languages are translated automatically, and if an automatic translation is wrong you can fix it in that language's file.
- Keep the UI flat and square. Avoid rounded corners on panels, cards and dialogs.
- Use the theme variables (like `var(--bg-base)` and `var(--text-primary)`) instead of fixed colors.
- There is one component tree for desktop and mobile. Use `src/ui/hooks/use-mobile.ts` for layout changes.
- Core never names a plugin. If core needs something from a plugin, add a slot, action or component id that the plugin fills.
- Tests live in `src/backend/tests/` and `src/ui/tests/`, mirroring the source tree. `npm run test` must pass.
- Every API route gets an OpenAPI JSDoc comment like the routes around it.
- Use short commit messages that start with `feat:`, `fix:` or `chore:`, and link related issues.
- Follow the existing code style. Use Tailwind CSS with shadcn components.
- Use the below color scheme with the respective CSS variable placed in the `className` of a div/component.
- Place all API routes in the `main-axios.ts` file. Updating the `openapi.json` is unneeded.
- Include meaningful commit messages.
- Link related issues when applicable.
- `MobileApp.tsx` renders when the users screen width is less than 768px, otherwise it loads the usual `DesktopApp.tsx`.
## Building a plugin
## Color Scheme
Start from the [plugin template](https://github.com/Termix-SSH/Termix-Plugin-Template). It talks to Termix only through `@termix-ssh/plugin-sdk` and has its own build, test and release setup.
### Background Colors
| CSS Variable | Color Value | Usage | Description |
| ----------------------------- | ----------- | --------------------------- | ---------------------------------------- |
| `--color-dark-bg` | `#18181b` | Main dark background | Primary dark background color |
| `--color-dark-bg-darker` | `#0e0e10` | Darker backgrounds | Darker variant for panels and containers |
| `--color-dark-bg-darkest` | `#09090b` | Darkest backgrounds | Darkest background (terminal) |
| `--color-dark-bg-light` | `#141416` | Light dark backgrounds | Lighter variant of dark background |
| `--color-dark-bg-very-light` | `#101014` | Very light dark backgrounds | Very light variant of dark background |
| `--color-dark-bg-panel` | `#1b1b1e` | Panel backgrounds | Background for panels and cards |
| `--color-dark-bg-panel-hover` | `#232327` | Panel hover states | Background for panels on hover |
### Element-Specific Backgrounds
| CSS Variable | Color Value | Usage | Description |
| ------------------------ | ----------- | ------------------ | --------------------------------------------- |
| `--color-dark-bg-input` | `#222225` | Input fields | Background for input fields and form elements |
| `--color-dark-bg-button` | `#23232a` | Button backgrounds | Background for buttons and clickable elements |
| `--color-dark-bg-active` | `#1d1d1f` | Active states | Background for active/selected elements |
| `--color-dark-bg-header` | `#131316` | Header backgrounds | Background for headers and navigation bars |
### Border Colors
| CSS Variable | Color Value | Usage | Description |
| ---------------------------- | ----------- | --------------- | ---------------------------------------- |
| `--color-dark-border` | `#303032` | Default borders | Standard border color |
| `--color-dark-border-active` | `#2d2d30` | Active borders | Border color for active elements |
| `--color-dark-border-hover` | `#434345` | Hover borders | Border color on hover states |
| `--color-dark-border-light` | `#5a5a5d` | Light borders | Lighter border color for subtle elements |
| `--color-dark-border-medium` | `#373739` | Medium borders | Medium weight border color |
| `--color-dark-border-panel` | `#222224` | Panel borders | Border color for panels and cards |
### Interactive States
| CSS Variable | Color Value | Usage | Description |
| ------------------------ | ----------- | ----------------- | --------------------------------------------- |
| `--color-dark-hover` | `#2d2d30` | Hover states | Background color for hover effects |
| `--color-dark-active` | `#2a2a2c` | Active states | Background color for active elements |
| `--color-dark-pressed` | `#1a1a1c` | Pressed states | Background color for pressed/clicked elements |
| `--color-dark-hover-alt` | `#2a2a2d` | Alternative hover | Alternative hover state color |
## Support
Bugs and ideas for Termix itself go in [this repo](https://github.com/Termix-SSH/Termix/issues/new/choose). Plugin problems go in that plugin's own `Plugin-<Name>` repo. Add as much detail as you can, in English if possible. You can also ask in the [Discord](https://discord.gg/jVQGdvHDrf) server.
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.
+4 -6
View File
@@ -1,10 +1,10 @@
cask "termix" do
version "26.10.5"
sha256 "808c467c5c34402193c7950ce61df0fa0445782f276348cf31ef235a6427d21c"
version "2.2.1"
sha256 "7fe7bde53df568c3212a212e6dc0ca4b77c24a3d3d9b740dddadcd765330e93d"
url "https://github.com/Termix-SSH/Termix/releases/download/v#{version}/termix_macos_universal_dmg.dmg"
url "https://github.com/Termix-SSH/Termix/releases/download/release-#{version}-tag/termix_macos_universal_dmg.dmg"
name "Termix"
desc "Self-hosted, plugin-based server management"
desc "Web-based server management platform with SSH terminal, tunneling, and file editing"
homepage "https://github.com/Termix-SSH/Termix"
livecheck do
@@ -12,8 +12,6 @@ cask "termix" do
strategy :github_latest
end
auto_updates true
app "Termix.app"
zap trash: [
+10 -200
View File
@@ -1,203 +1,13 @@
Copyright 2026 Luke Gustafson
Copyright 2025 Luke Gustafson
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
http://www.apache.org/licenses/LICENSE-2.0
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+255 -146
View File
@@ -4,113 +4,175 @@
<h1>Termix</h1>
<p>Self-hosted, plugin-based server management</p>
<p>Self-hosted SSH management and remote desktop access</p>
<p>
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>
</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>
<br />
<img src="./repo-images/Termix Header.png" alt="Termix Banner" width="900" />
<br />
<br />
<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>
<img src="repo-images/Repo of the Day.png" alt="Repo of the Day Achievement" width="280" />
<br />
<sub>Achieved on September 1st, 2025</sub>
</p>
<a href="https://www.youtube.com/watch?v=vhtkP-UqV5U"><img src="./.github/readme/overview.jpg" width="720" alt="Watch the Termix overview on YouTube" /></a>
</div>
<br />
## Overview
Termix is a free, open source, self-hosted server management platform built on plugins. The core keeps your hosts, credentials, users, sharing and sessions. Everything else, like the SSH terminal, remote desktop, file manager, Docker and metrics, is a plugin you can turn on, off or remove.
It runs on web, desktop and mobile. Think of it as a free, self-hosted Termius.
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 />
## Try the Demo
## Features
Try it at [demo.termix.site](https://demo.termix.site/). Any username and password works.
<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.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**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.
</td>
<td width="50%" valign="top">
**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.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Server Stats:**
View CPU, memory, and disk usage along with network, uptime, system information, firewall, port monitor, on most Linux based servers.
</td>
<td width="50%" valign="top">
**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.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**RBAC:**
Create roles and share hosts across users/roles.
</td>
<td width="50%" valign="top">
**Database Encryption:**
Backend stored as encrypted SQLite database files. View [docs](https://docs.termix.site/security) for more.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Network Graph:**
Customize your Dashboard to visualize your homelab based off your SSH connections with status support.
</td>
<td width="50%" valign="top">
**SSH Tools:**
Create reusable command snippets that execute with a single click. Run one command simultaneously across multiple open terminals.
</td>
</tr>
<tr>
<td width="50%" valign="top">
**Persistent Tabs:**
SSH sessions and tabs stay open across devices/refreshes if enabled in user profile.
</td>
<td width="50%" valign="top">
**Languages:**
Built-in support ~30 languages (managed by [Crowdin](https://docs.termix.site/translations)).
</td>
</tr>
</table>
<br />
## Core
What every install has, with or without plugins:
- Hosts: Save hosts in nested folders with tags and parent hosts, bulk edit, import and export, and use Quick Connect for one-off connections
- Credentials: Reuse saved passwords and keys across hosts, with host defaults you can set for the server, a user or a folder
- Sharing and Roles: Share hosts, credentials and folders with users or roles at four levels: connect, view, edit and manage
- Accounts: Local accounts, sessions you can see and revoke, trusted devices, API keys and an audit log
- Security: Passwords, keys and other secrets are encrypted per user, and the database can be encrypted on disk
- Desktop Sync: The desktop app runs on its own and can link to a Termix server to keep an offline copy of your account
- Interface: Tabs and split screen, a command palette (double tap left shift), rebindable shortcuts, themes, branding and around 30 languages
- Databases: SQLite by default, with PostgreSQL and MySQL supported too
<details>
<summary><b>More features</b></summary>
<br />
## Plugins
- **Dashboard** - View server information at a glance on your dashboard
- **API Keys** - Create user-scoped API keys with expiration dates to be used for automation/CI
- **Data Export/Import** - Export and import SSH hosts, credentials, and file manager data
- **Automatic SSL Setup** - Built-in SSL certificate generation and management with HTTPS redirects
- **Modern UI** - Clean desktop/mobile-friendly interface built with React, Tailwind CSS, and Shadcn. Choose between many different UI themes including light, dark, Dracula, etc. Use URL routes to open any connection in full-screen.
- **Command History** - Auto-complete and view previously ran SSH commands
- **Quick Connect** - Connect to a server without having to save the connection data
- **Command Palette** - Double tap left shift to quickly access SSH connections with your keyboard
- **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.
Plugins add everything else. The official ones ship with Termix, and admins can install, update or remove plugins from the Plugins tab. Each plugin has its own repo.
| Plugin | What it does |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| [ACME Certificates](https://github.com/Termix-SSH/Plugin-ACME-SSL) | Gets the Termix HTTPS certificate from Let's Encrypt or any ACME provider and renews it for you. |
| [AI Assistant](https://github.com/Termix-SSH/Plugin-AI) | An AI assistant that reads your setup and proposes changes for you to approve. |
| [Alerts](https://github.com/Termix-SSH/Plugin-Alerts) | One inbox for every alert in Termix, with delivery to webhooks, ntfy, Discord and email. |
| [Audit Tools](https://github.com/Termix-SSH/Plugin-Audit-Tools) | Send the audit log to your SIEM and export it as CSV or NDJSON. |
| [Automations](https://github.com/Termix-SSH/Plugin-Automations) | Run steps on your hosts on a schedule or when something happens. |
| [Classic Layout](https://github.com/Termix-SSH/Plugin-Classic-Layout) | The classic Termix layout with an icon rail, sidebar, tab bar and dock. |
| [Classic Themes](https://github.com/Termix-SSH/Plugin-Classic-Themes) | Light, dark and seven more color themes for Termix. |
| [Cloud Providers](https://github.com/Termix-SSH/Plugin-Cloud-Providers) | Add hosts from AWS, Azure, Google Cloud, DigitalOcean, Hetzner and other clouds, and keep them in sync. |
| [Credential Tools](https://github.com/Termix-SSH/Plugin-Credential-Tools) | Generate SSH keys, check them and copy a public key to a host. |
| [Databases](https://github.com/Termix-SSH/Plugin-Databases) | Browse and edit MySQL, MariaDB, PostgreSQL and SQLite databases on your hosts from one tab. |
| [Docker](https://github.com/Termix-SSH/Plugin-Docker) | Manage Docker and Podman containers on your hosts over SSH, with logs, stats and a console. |
| [File Manager](https://github.com/Termix-SSH/Plugin-File-Manager) | Browse, edit and move files over SFTP, including straight from one server to another. |
| [Fleets](https://github.com/Termix-SSH/Plugin-Fleets) | Group hosts into fleets and run commands, package actions and file transfers on all of them at once. |
| [HashiCorp Vault](https://github.com/Termix-SSH/Plugin-Vault) | Connect to hosts with short-lived SSH certificates signed by HashiCorp Vault. |
| [Homepage](https://github.com/Termix-SSH/Plugin-Homepage) | A drag and drop widget page for your servers and services, plus service links for the dashboard. |
| [Host Metrics](https://github.com/Termix-SSH/Plugin-Host-Metrics) | Live CPU, memory, disk, network and GPU stats, plus tools to manage your hosts. |
| [Host Status](https://github.com/Termix-SSH/Plugin-Host-Status) | Shows if each host is online with a dot in the host list, checked by connecting to its port. |
| [Hosts Config](https://github.com/Termix-SSH/Plugin-Hosts-Config) | Keep hosts, folders and sharing in a YAML or JSON file. Termix loads them as read only hosts and reloads when the file changes. |
| [Import and Export](https://github.com/Termix-SSH/Plugin-Import-Export) | Import hosts from a JSON file or an SSH config file and export them to share or back up. |
| [Kubernetes](https://github.com/Termix-SSH/Plugin-Kubernetes) | Manage Kubernetes clusters from your hosts over SSH, with logs, metrics, YAML and a shell. |
| [LDAP](https://github.com/Termix-SSH/Plugin-LDAP) | Sign in with your LDAP or Active Directory username and password. |
| [Login Security](https://github.com/Termix-SSH/Plugin-Login-Security) | Sign people out when idle, limit session length, turn off remember me and require a second factor at login. |
| [Network Topology](https://github.com/Termix-SSH/Plugin-Network-Topology) | Draws your hosts and the links between them as a live, interactive graph. |
| [OPKSSH](https://github.com/Termix-SSH/Plugin-OPKSSH) | Connect to hosts with short-lived OpenPubkey SSH certificates after signing in with your identity provider. |
| [Passkeys](https://github.com/Termix-SSH/Plugin-WebAuthn) | Sign in with a passkey or security key instead of a password. |
| [Proxmox](https://github.com/Termix-SSH/Plugin-Proxmox) | Import Proxmox VE guests as hosts, keep them in sync and watch node stats. |
| [Remote Desktop](https://github.com/Termix-SSH/Plugin-Remote-Desktop) | RDP, VNC and Telnet sessions in your browser, with clipboard, file transfer, sharing and recording. |
| [Reverse Proxy](https://github.com/Termix-SSH/Plugin-Reverse-Proxy) | Reverse proxy for TCP, UDP, HTTP and HTTPS with free certificates and Termix login, managed per host. |
| [Samba Shares](https://github.com/Termix-SSH/Plugin-Samba) | Create and manage Samba (SMB) file shares, users and permissions on your hosts from one tab. |
| [Secret Sources](https://github.com/Termix-SSH/Plugin-Secret-Sources) | Pull host passwords and keys from 1Password when you connect instead of storing them in Termix. |
| [Serial](https://github.com/Termix-SSH/Plugin-Serial) | Open a serial console to a router, switch or other device plugged into your computer. |
| [Services](https://github.com/Termix-SSH/Plugin-Service-Manager) | Manage services, timers and logs on your hosts over SSH. |
| [Session Recording](https://github.com/Termix-SSH/Plugin-Session-Recording) | Record terminal and remote desktop sessions and play them back or download them later. |
| [Session Sharing](https://github.com/Termix-SSH/Plugin-Session-Sharing) | Share live sessions by link or with another user, and present sessions to a group in meeting rooms. |
| [Simple Layout](https://github.com/Termix-SSH/Plugin-Simple-Layout) | A simple Termix layout for small setups with a top bar, a Home screen and only the basics. |
| [Single sign-on](https://github.com/Termix-SSH/Plugin-SSO) | Sign in with any OpenID Connect provider, GitHub or Google. |
| [Snippets](https://github.com/Termix-SSH/Plugin-Snippets) | Save the commands you run often and run them in a terminal or on many hosts in one click. |
| [SSH Terminal](https://github.com/Termix-SSH/Plugin-SSH-Terminal) | SSH terminal sessions with tabs, split screen, history, macros and a local terminal in the desktop app. |
| [Step CA](https://github.com/Termix-SSH/Plugin-Step-CA) | Connect to hosts with short-lived SSH certificates from a smallstep step-ca server. |
| [Sync](https://github.com/Termix-SSH/Plugin-Sync) | Sync the desktop app with one or more servers, and manage other Termix servers as remote sites from a master. |
| [Tailscale](https://github.com/Termix-SSH/Plugin-Tailscale) | Add tailnet devices as hosts and connect with Tailscale SSH. |
| [Termix Identity](https://github.com/Termix-SSH/Plugin-Termix-Identity) | Publish your SSH public keys under a public handle and run your own SSH certificate authority. |
| [Tmux Monitor](https://github.com/Termix-SSH/Plugin-Tmux-Monitor) | Browse and control tmux sessions, windows and panes across your hosts. |
| [TOTP](https://github.com/Termix-SSH/Plugin-TOTP) | Two-factor sign in with a code from an authenticator app, with backup codes. |
| [Tunnels](https://github.com/Termix-SSH/Plugin-Tunnels) | SSH port forwarding that reconnects on its own, plus client tunnels from the desktop app. |
| [Usage Statistics](https://github.com/Termix-SSH/Plugin-Telemetry) | Sends a small anonymous daily report so we can see how Termix is used. |
| [Wake-on-LAN](https://github.com/Termix-SSH/Plugin-Wake-On-LAN) | Wake sleeping machines by sending a Wake-on-LAN packet to their MAC address. |
| [Warpgate](https://github.com/Termix-SSH/Plugin-Warpgate) | Connect to hosts through a Warpgate SSH bastion. |
| [Web Endpoint](https://github.com/Termix-SSH/Plugin-Web-Endpoint) | Open a host's web UI inside Termix, directly or over an SSH tunnel. |
| [Workspaces](https://github.com/Termix-SSH/Plugin-Workspaces) | Save your open tabs and split layouts and reopen them in one click. |
Want to build your own? Start from the [plugin template](https://github.com/Termix-SSH/Termix-Plugin-Template). Plugins you can install are listed in the [registry](https://github.com/Termix-SSH/Termix-Registry). Turn on the community registry in Plugins, Registries to install plugins made by other people, or add your own registry. See [submitting a plugin](https://docs.termix.site/develop/community-registry) and [hosting your own registry](https://docs.termix.site/develop/registries#host-your-own-registry).
</details>
<br />
@@ -123,27 +185,27 @@ Want to build your own? Start from the [plugin template](https://github.com/Term
</tr>
<tr>
<td align="center"><b>Web</b></td>
<td>Any modern browser (Chrome, Safari, Firefox) | PWA support</td>
<td>Any modern browser (Chrome, Safari, Firefox) · PWA support</td>
</tr>
<tr>
<td align="center"><b>Windows</b> <sub>x64/ia32</sub></td>
<td>Portable | EXE and MSI Installer | Chocolatey</td>
<td>Portable · MSI Installer · Chocolatey</td>
</tr>
<tr>
<td align="center"><b>Linux</b> <sub>x64/arm64/armv7l</sub></td>
<td>Portable | AUR | AppImage | Deb | Flatpak</td>
<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>Universal/x64/arm64, v12.0+</sub></td>
<td>Apple App Store | DMG | Homebrew</td>
<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>
<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>
<td>Google Play Store · APK</td>
</tr>
</table>
@@ -151,14 +213,14 @@ Want to build your own? Start from the [plugin template](https://github.com/Term
## Installation
See the [docs](https://docs.termix.site/install) for install steps on every platform.
Visit the [Termix Docs](https://docs.termix.site/install) for full installation instructions across all platforms.
Sample Docker Compose file. Remote desktop (RDP, VNC and Telnet) is built in, so nothing else needs to run next to it:
Sample Docker Compose file (you can omit `guacd` and the network if you don't plan on using remote desktop features):
```yaml
services:
termix:
image: ghcr.io/termix-ssh/termix:latest
image: ghcr.io/lukegus/termix:latest
container_name: termix
restart: unless-stopped
ports:
@@ -167,84 +229,131 @@ services:
- termix-data:/app/data
environment:
PORT: "8080"
depends_on:
- guacd
networks:
- termix-net
guacd:
image: guacamole/guacd:1.6.0
container_name: guacd
restart: unless-stopped
ports:
- "4822:4822"
networks:
- termix-net
volumes:
termix-data:
driver: local
networks:
termix-net:
driver: bridge
```
### Cloud Hosting
If Termix runs on the network it manages, an outage takes it down right when you need it. A VPS keeps it reachable, gives you a static IP and works from anywhere without a VPN or port forward.
[GINERNET](https://docs.termix.site/install/ginernet) sponsors Termix and the docs have a setup guide for their VPS.
<br />
## Donate
Termix has no paid plans. Donations cover servers, domains and development time.
[Donate](https://donate.termix.site/)
<br />
## Sponsors
For a paid sponsor spot, email [mail@termix.site](mailto:mail@termix.site).
<!-- SPONSORS:START -->
## Screenshots
<div align="center">
<br />
<a href="https://www.blacksmith.sh/">
<img src="https://termix.site/img/sponsors/blacksmith.svg" height="40" alt="Blacksmith" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.cloudflare.com/">
<img src="https://termix.site/img/sponsors/cloudflare.png" height="40" alt="Cloudflare" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://akamai.com/">
<img src="https://termix.site/img/sponsors/akamai.svg" height="40" alt="Akamai" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://aws.amazon.com/">
<img src="https://termix.site/img/sponsors/aws.png" height="40" alt="AWS" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://rackgenius.com/">
<img src="https://termix.site/img/sponsors/rackgenius.png" height="40" alt="Rack Genius" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://ginernet.com/">
<img src="https://termix.site/img/sponsors/ginernet.png" height="40" alt="Ginernet" />
</a>
&nbsp;&nbsp;&nbsp;
<a href="https://www.hetzner.com/?mtm_campaign=termix&mtm_medium=referral&mtm_content=sponsoring_link">
<img src="https://termix.site/img/sponsors/hetzner.png" height="40" alt="Hetzner" />
</a>
[![YouTube](./repo-images/YouTube.png)](https://www.youtube.com/@TermixSSH/videos)
<sub>Watch update overviews on 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>
</table>
<sub>Some videos and images may be out of date or may not perfectly showcase features.</sub>
</div>
<!-- SPONSORS:END -->
<br />
## Planned Features
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 />
## Sponsors
<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>
</div>
<br />
## Support
Bugs and ideas for Termix itself (login, hosts, credentials, sharing, sync, the app and desktop app) go in this repo: [report a bug](https://github.com/Termix-SSH/Termix/issues/new?template=bug_report.yml) or [request a feature](https://github.com/Termix-SSH/Termix/issues/new?template=feature_request.yml).
Every plugin has its own repo, so a problem with the terminal, file manager, Docker, remote desktop or any other plugin goes in that plugin's repo (see the table above, or use Report an issue on the plugin's page in the app, which fills in your versions for you). Not sure where it goes? Open it here and it will be moved.
You need a GitHub account. Add as much detail as you can, in English if possible.
For discussions and questions, join the [Discord](https://discord.gg/jVQGdvHDrf) server. Update videos are on [YouTube](https://www.youtube.com/@TermixSSH/videos).
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 />
## License
Apache License 2.0. See [LICENSE](LICENSE).
Distributed under the Apache License Version 2.0. See `LICENSE` for more information.
+5
View File
@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report any vulnerabilities to [GitHub Security](https://github.com/Termix-SSH/Termix/security/advisories).
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -13,11 +13,17 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<projectSourceUrl>https://github.com/Termix-SSH/Termix</projectSourceUrl>
<docsUrl>https://docs.termix.site/install</docsUrl>
<bugTrackerUrl>https://github.com/Termix-SSH/Termix/issues</bugTrackerUrl>
<tags>docker ssh terminal telnet self-hosted rdp file-management vnc ssh-tunnel server-stats termix</tags>
<summary>Self-hosted, plugin-based server management.</summary>
<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>
<description>
Termix is a free, open source, self-hosted platform for managing your servers, built on plugins. The core keeps your hosts, credentials, users and sharing, and plugins add the SSH terminal, remote desktop (RDP, VNC, Telnet), file manager, tunnels, Docker, metrics and more. It runs on web, desktop and mobile, and it is a free, self-hosted alternative to Termius.
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
This package installs the desktop application version of Termix.
</description>
+2 -2
View File
@@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/ui/index.css",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
@@ -15,7 +15,7 @@
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
+3
View File
@@ -0,0 +1,3 @@
files:
- source: /src/ui/locales/en.json
translation: /src/ui/locales/translated/%locale_with_underscore%.json
+10 -82
View File
@@ -1,20 +1,14 @@
# Stage 1: Install dependencies
# The builders only produce JS, so they run on the build machine's own arch
# once instead of under QEMU per target. Rolldown hangs under arm64 emulation.
FROM --platform=$BUILDPLATFORM node:24-slim AS deps
FROM node:24-slim AS deps
WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
COPY package*.json ./
COPY packages/plugin-sdk/package.json ./packages/plugin-sdk/package.json
COPY .npmrc ./
COPY vendor ./vendor
COPY scripts/patch-ssh2-agent.cjs ./scripts/
RUN npm ci --ignore-scripts && \
node scripts/patch-ssh2-agent.cjs && \
npm cache clean --force
# Stage 2: Build frontend
@@ -26,7 +20,7 @@ COPY . .
RUN find public/fonts -name "*.ttf" ! -name "*Regular.ttf" ! -name "*Bold.ttf" ! -name "*Italic.ttf" -delete
RUN npm cache clean --force && \
TERMIX_RUNTIME_BASE_PATH=true NODE_OPTIONS="--max-old-space-size=3072" npm run build
NODE_OPTIONS="--max-old-space-size=3072" npm run build
# Stage 3: Build backend
FROM deps AS backend-builder
@@ -34,92 +28,38 @@ WORKDIR /app
COPY . .
RUN npm run build:backend:core
RUN npm rebuild better-sqlite3
# Stage 3b: Stage the bundled plugins for one profile (minimal, standard or
# full, see docker/bundled-plugins.json). Only this stage and the final one
# differ between profiles, so the other stages are built once.
FROM deps AS plugins
WORKDIR /app
ARG PLUGIN_PROFILE=standard
COPY scripts/build-plugins.cjs ./scripts/build-plugins.cjs
COPY scripts/lib/bundled-plugins.cjs ./scripts/lib/bundled-plugins.cjs
COPY docker/bundled-plugins.json ./docker/bundled-plugins.json
# Bundled plugin downloads show in the plugin store, so keep them between builds.
RUN --mount=type=cache,target=/app/node_modules/.cache/termix-bundled-plugins \
node scripts/build-plugins.cjs --profile "$PLUGIN_PROFILE"
RUN npm run build:backend
# Stage 4: Download OPKSSH binary for the target platform so the image works offline
FROM node:24-slim AS opkssh-downloader
ARG TARGETARCH
ARG OPKSSH_VERSION=v0.16.0
ARG OPKSSH_SHA256_AMD64=c018c3e7baf98612b923e742dd87be38650bf61e3b755fb2bc90de177568b1bf
ARG OPKSSH_SHA256_ARM64=9dd10c2b6ce99cde18e52c054877ca014134b291fd82afe71741c68db4f83d44
WORKDIR /opkssh
RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*
RUN case "$TARGETARCH" in \
amd64) OPKSSH_ARCH=amd64; OPKSSH_SHA256="$OPKSSH_SHA256_AMD64" ;; \
arm64) OPKSSH_ARCH=arm64; OPKSSH_SHA256="$OPKSSH_SHA256_ARM64" ;; \
*) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \
esac && \
curl -fSL -o "opkssh-linux-${OPKSSH_ARCH}" \
"https://github.com/openpubkey/opkssh/releases/download/${OPKSSH_VERSION}/opkssh-linux-${OPKSSH_ARCH}" && \
echo "$OPKSSH_SHA256 opkssh-linux-${OPKSSH_ARCH}" | sha256sum -c - && \
chmod 755 "opkssh-linux-${OPKSSH_ARCH}" && \
echo -n "$OPKSSH_VERSION" > version.txt
# Stage 5: Production dependencies only
# Stage 4: Production dependencies only
FROM node:24-slim AS production-deps
WORKDIR /app
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
COPY package*.json ./
# The plugin SDK is a workspace package core imports at runtime, so npm ci
# has to see it to link it and install its dependencies.
COPY packages/plugin-sdk/package.json ./packages/plugin-sdk/package.json
COPY .npmrc ./
COPY vendor ./vendor
COPY scripts/patch-ssh2-agent.cjs ./scripts/
# ssh2's native crypto crashes Node 24 when a cipher is freed, so ssh2 uses
# OpenSSL instead (chacha20-poly1305 is then left out).
RUN npm ci --omit=dev --ignore-scripts && \
node scripts/patch-ssh2-agent.cjs && \
rm -rf node_modules/better-sqlite3/prebuilds && \
npm run build-release --prefix node_modules/better-sqlite3 && \
test -f node_modules/better-sqlite3/build/Release/better_sqlite3.node && \
npm rebuild bcryptjs && \
rm -rf node_modules/ssh2/lib/protocol/crypto/build && \
npm rebuild better-sqlite3 bcryptjs && \
npm cache clean --force
# Stage 6: Final optimized image
# Stage 5: Final optimized image
FROM node:24-slim
WORKDIR /app
# The release the opkssh-downloader stage baked in, so the plugin trusts that
# binary when the build picks a version other than its pinned one.
ARG OPKSSH_VERSION=v0.16.0
ARG OPKSSH_SHA256_AMD64=c018c3e7baf98612b923e742dd87be38650bf61e3b755fb2bc90de177568b1bf
ARG OPKSSH_SHA256_ARM64=9dd10c2b6ce99cde18e52c054877ca014134b291fd82afe71741c68db4f83d44
ENV DATA_DIR=/app/data \
OPKSSH_BUNDLED_DIR=/app/opkssh-bundled \
OPKSSH_VERSION=$OPKSSH_VERSION \
OPKSSH_SHA256_AMD64=$OPKSSH_SHA256_AMD64 \
OPKSSH_SHA256_ARM64=$OPKSSH_SHA256_ARM64 \
PORT=8080 \
NODE_ENV=production
RUN apt-get update && apt-get install -y nginx gettext-base openssl ca-certificates gosu wget && \
update-ca-certificates && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /app/data /app/uploads /app/nginx /tmp/nginx && \
mkdir -p /app/data /app/uploads /app/data/.opk /app/nginx /tmp/nginx && \
chown -R node:node /app /tmp/nginx && \
chmod 755 /app/data /app/uploads /app/nginx /tmp/nginx
chmod 755 /app/data /app/uploads /app/data/.opk /app/nginx /tmp/nginx
COPY docker/nginx.conf /app/nginx/nginx.conf.template
COPY docker/nginx-https.conf /app/nginx/nginx-https.conf.template
@@ -128,23 +68,11 @@ COPY --chown=node:node --from=frontend-builder /app/dist /app/html
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
# Plugins of the chosen profile pinned in docker/bundled-plugins.json, resolved
# as dist/plugins by getBundledPluginsDir().
COPY --chown=node:node --from=plugins /app/dist/plugins ./dist/plugins
# Target of node_modules/@termix-ssh/plugin-sdk, which core and the plugins import.
COPY --chown=node:node --from=backend-builder /app/packages/plugin-sdk/package.json ./packages/plugin-sdk/package.json
COPY --chown=node:node --from=backend-builder /app/packages/plugin-sdk/dist ./packages/plugin-sdk/dist
# Pre-baked OPKSSH binary. The opkssh plugin uses it when its checksum
# matches, so an offline install never downloads one.
COPY --chown=node:node --from=opkssh-downloader /opkssh /app/opkssh-bundled
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}
EXPOSE ${PORT} 30001 30002 30003 30004 30005 30006
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget -q -O /dev/null http://localhost:30001/health || exit 1
-413
View File
@@ -1,413 +0,0 @@
{
"plugins": [
{
"id": "acme-ssl",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-ACME-SSL/releases/download/v1.1.0/acme-ssl-1.1.0.tmxplug",
"sha256": "fb75541fae110007b45ed69d58aa4b9b6f6a79b6b1b762c6e54b635aa7cb6e4a",
"tier": "full"
},
{
"id": "ai",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-AI/releases/download/v1.3.0/ai-1.3.0.tmxplug",
"sha256": "87ad17324cec6036dc224135fee342250c37413dccb2963f5f6b554d4ab8f5a6",
"tier": "full"
},
{
"id": "alerts",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Alerts/releases/download/v1.1.0/alerts-1.1.0.tmxplug",
"sha256": "a7ce9e20ce3c15f77c15479b4901563adf3c793e24a3aacd7226950491f0434c",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "audit-tools",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Audit-Tools/releases/download/v1.0.0/audit-tools-1.0.0.tmxplug",
"sha256": "131479c294d692b1c0ed4d6cec273730a3a67a28ecd44825ffd36bade9aa863b",
"tier": "full"
},
{
"id": "automations",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Automations/releases/download/v1.2.0/automations-1.2.0.tmxplug",
"sha256": "2ec9c718d73a094be3b59121aea049f0237e5783f53d193c4ea24a524584aeea",
"tier": "full"
},
{
"id": "classic-layout",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Classic-Layout/releases/download/v1.0.2/classic-layout-1.0.2.tmxplug",
"sha256": "cf54f9b2e81c33b7e58cd3a21e203805f08ba37b3bc78412a08eb21b9fef8f14",
"tier": "minimal",
"defaultLook": true
},
{
"id": "classic-themes",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Classic-Themes/releases/download/v1.0.0/classic-themes-1.0.0.tmxplug",
"sha256": "919283f95701d41727c826637a7d349bd6a646abd61f9ac9017149bd0bee782c",
"tier": "minimal",
"defaultLook": true
},
{
"id": "cloud-providers",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Cloud-Providers/releases/download/v1.1.0/cloud-providers-1.1.0.tmxplug",
"sha256": "7f61eafc3b99bdf1328fffa95791db1d27f8e1b683e5a5925603b5840057a9c7",
"tier": "full"
},
{
"id": "credential-tools",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Credential-Tools/releases/download/v1.0.0/credential-tools-1.0.0.tmxplug",
"sha256": "413eff710b5c832a2575a890bbab1c90d28ea55f10cd7a7b94b851d51dd77dce",
"tier": "standard"
},
{
"id": "databases",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Databases/releases/download/v1.1.0/databases-1.1.0.tmxplug",
"sha256": "4d9504cde705688a6f1f7706139d4e4c49b2276eccc67538fdee8251f29920b8",
"tier": "full"
},
{
"id": "docker",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Docker/releases/download/v1.2.1/docker-1.2.1.tmxplug",
"sha256": "b2ca797465d88cfb549a04c3f0161708669def22a5ca73cb107879847c2db8ff",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "file-manager",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-File-Manager/releases/download/v1.1.1/file-manager-1.1.1.tmxplug",
"sha256": "ebba0b7d14facb3354f22a00e059e9f5a69e233458cb24985bf4ed5e5f8d2abd",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "fleets",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Fleets/releases/download/v1.0.2/fleets-1.0.2.tmxplug",
"sha256": "50dab38e1c6c95fdb4d0544ce50cdf84dae84789e64aa34ea9d04fdac3a38c59",
"tier": "full"
},
{
"id": "homepage",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Homepage/releases/download/v1.1.0/homepage-1.1.0.tmxplug",
"sha256": "5a9d5d2cc337fcee5590ea957e35ddeea5a71596aeb59410bf1420065b9b5b00",
"tier": "full"
},
{
"id": "host-metrics",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Host-Metrics/releases/download/v1.2.0/host-metrics-1.2.0.tmxplug",
"sha256": "8522cd23b558c3209821cee6d177daeb80f439b8ff652c577c5e4955315cc7a3",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "host-status",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Host-Status/releases/download/v1.0.0/host-status-1.0.0.tmxplug",
"sha256": "1ba62577dc7373a2a2787bab7efdb89b544a30a8003b1de91ce51f372fc4f170",
"tier": "standard"
},
{
"id": "hosts-config",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Hosts-Config/releases/download/v1.0.0/hosts-config-1.0.0.tmxplug",
"sha256": "60a2b2a6d9ba13bad12a511c93af70a30a10fbc560066b6419acf533d806a3ad",
"tier": "full"
},
{
"id": "import-export",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Import-Export/releases/download/v1.0.0/import-export-1.0.0.tmxplug",
"sha256": "6670b6c147e81eed0ddfd249f9a7c9564f1ab50e45f2771161d2e30c47f6fd58",
"tier": "standard"
},
{
"id": "kubernetes",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Kubernetes/releases/download/v1.0.1/kubernetes-1.0.1.tmxplug",
"sha256": "31d6b200579d326d07244712713ae291ff473270d44b478a884d07c863a394f0",
"tier": "full"
},
{
"id": "ldap",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-LDAP/releases/download/v1.0.2/ldap-1.0.2.tmxplug",
"sha256": "9ef269ad281f1c68cea53e0d43ccb7a5d4f949f11b30b22eac3d01047b20eb03",
"tier": "full"
},
{
"id": "login-security",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Login-Security/releases/download/v1.0.0/login-security-1.0.0.tmxplug",
"sha256": "b21dff5722e72a02022d563ef27130e8b56a53e273f1668fade4ec7d030374aa",
"tier": "full"
},
{
"id": "network-topology",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Network-Topology/releases/download/v1.0.2/network-topology-1.0.2.tmxplug",
"sha256": "8d32340a0d0f9674b872da5d3d53e90f529b80dfe0d73ab294eed00e565fbb56",
"tier": "full"
},
{
"id": "opkssh",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-OPKSSH/releases/download/v1.0.2/opkssh-1.0.2.tmxplug",
"sha256": "4bb8f1ac5d39bd18ac3d0fe82ddbf2f26ab4c01bd75e6f7a073d65c2e9aef219",
"tier": "full"
},
{
"id": "proxmox",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Proxmox/releases/download/v1.1.0/proxmox-1.1.0.tmxplug",
"sha256": "8937d239ad40033d69fba59085fe8896ee25c61951c21f66ff98531fd4c63cbf",
"tier": "full"
},
{
"id": "remote-desktop",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Remote-Desktop/releases/download/v1.2.1/remote-desktop-1.2.1.tmxplug",
"sha256": "ad244f31016f0ea5df5c303bf6f48af35c6c8e52b12098d381c8296626b73de7",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "reverse-proxy",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Reverse-Proxy/releases/download/v1.1.0/reverse-proxy-1.1.0.tmxplug",
"sha256": "c8a9851da7ca6291c1f47cf6ab235da5057455a86b4ed0dc1c1a3026afb30aa7",
"tier": "full"
},
{
"id": "samba",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Samba/releases/download/v1.0.0/samba-1.0.0.tmxplug",
"sha256": "6e131ccdca998a9d0802becdd439a88305b64fcd7f07335bec9c7ed6791c0f53",
"tier": "full"
},
{
"id": "secret-sources",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Secret-Sources/releases/download/v1.0.2/secret-sources-1.0.2.tmxplug",
"sha256": "82e2b1e95b0837dd3e5fee277be4eea2d1ce68567b0e204941c48150f415d134",
"tier": "full"
},
{
"id": "serial",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Serial/releases/download/v1.0.2/serial-1.0.2.tmxplug",
"sha256": "6ede7e8726a7312429473f34a46f93ba6657c5b7ebc12bed18f80ec62c211715",
"tier": "standard",
"onboarding": {
"recommended": "desktop",
"consent": false
}
},
{
"id": "service-manager",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Service-Manager/releases/download/v1.1.0/service-manager-1.1.0.tmxplug",
"sha256": "82b1c70abd97ed535f217535c667fe6c48e5e01e2bf8037a89b58b80d35206bf",
"tier": "full"
},
{
"id": "session-recording",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Session-Recording/releases/download/v1.1.0/session-recording-1.1.0.tmxplug",
"sha256": "31cd78d67e061242f8776849a2fae5f84ab48c7c765d487b32c5e1693c54b46a",
"tier": "full"
},
{
"id": "session-sharing",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Session-Sharing/releases/download/v1.1.0/session-sharing-1.1.0.tmxplug",
"sha256": "2988b39221d93b3dbc5e2a81e104e5dc01ce264afb1e66df3407492658ab9fdc",
"tier": "full"
},
{
"id": "simple-layout",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Simple-Layout/releases/download/v1.0.1/simple-layout-1.0.1.tmxplug",
"sha256": "838f08050335b44b62295b87006b50d1db1029699bfe6a35fa75808d4aea2cde",
"tier": "full"
},
{
"id": "snippets",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Snippets/releases/download/v1.1.0/snippets-1.1.0.tmxplug",
"sha256": "e2d7d1ebe5bdf2986ffb4961ac6f6d32b392b02c30a64a333a7eeb2ce6a46bb3",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "ssh-terminal",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-SSH-Terminal/releases/download/v1.1.1/ssh-terminal-1.1.1.tmxplug",
"sha256": "5cfb381a2de10b7a6fab108d79645bb1e6e7316fb5e4150c4529a3e76a2193d8",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "sso",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-SSO/releases/download/v1.0.4/sso-1.0.4.tmxplug",
"sha256": "2aad33177085fbc92fe06cd007facffbfc8f42f9c9fa688ad5f37d68f62093d9",
"tier": "standard",
"onboarding": {
"recommended": false,
"consent": false,
"enabledByEnv": ["OIDC_CLIENT_ID"]
}
},
{
"id": "step-ca",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Step-CA/releases/download/v1.0.2/step-ca-1.0.2.tmxplug",
"sha256": "685b644fe44c2d3fa007baee16e8ea8a7d5204f039ebc8aa65e8093ad93adf8a",
"tier": "full"
},
{
"id": "sync",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Sync/releases/download/v1.0.1/sync-1.0.1.tmxplug",
"sha256": "66a301fd04fb8d2a3ece18dfb230069cf78e1138ee78941dc511dfbd8ee4f91e",
"tier": "standard"
},
{
"id": "tailscale",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Tailscale/releases/download/v1.1.0/tailscale-1.1.0.tmxplug",
"sha256": "8784a77a1fc1b0011d8243d9520574300df31bc40c31f86339868f4c9b76d1bf",
"tier": "full"
},
{
"id": "telemetry",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Telemetry/releases/download/v1.0.2/telemetry-1.0.2.tmxplug",
"sha256": "5ffdc58b05cd8b37fd0a207f99d2d3a256164130c950032501c499f869edb9ec",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": true
}
},
{
"id": "termix-identity",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Termix-Identity/releases/download/v1.0.2/termix-identity-1.0.2.tmxplug",
"sha256": "ac6c90a48b4f1e9cff6773a5fd267b2bf7319d2fc7e2aa2cd4004cec82f0f927",
"tier": "full"
},
{
"id": "tmux-monitor",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Tmux-Monitor/releases/download/v1.1.0/tmux-monitor-1.1.0.tmxplug",
"sha256": "3cdc54a34e1ca30c4e3c217bbc612e40ef3cd3e4d5de704ea92d914b32d98006",
"tier": "full"
},
{
"id": "totp",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-TOTP/releases/download/v1.0.2/totp-1.0.2.tmxplug",
"sha256": "94a0772660fa8e4dfd9afc15b68d5bc9fe02fe28a1acfd35e65a57c304d460c0",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "tunnels",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Tunnels/releases/download/v1.2.1/tunnels-1.2.1.tmxplug",
"sha256": "2871a8d31854e1a31416c4afe5839099a4c489e34d9d31573659f73b2515f159",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "vault",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Vault/releases/download/v1.0.2/vault-1.0.2.tmxplug",
"sha256": "48ef402d030912a836489edf0fdfcaf7929cbd67c21f16a2079cb4925134cc28",
"tier": "full"
},
{
"id": "wake-on-lan",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Wake-On-LAN/releases/download/v1.1.0/wake-on-lan-1.1.0.tmxplug",
"sha256": "1afb708c12631ededec40938aed3ab587bc9a519215fa5e93350b1f0a843d2a8",
"tier": "full"
},
{
"id": "warpgate",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Warpgate/releases/download/v1.0.2/warpgate-1.0.2.tmxplug",
"sha256": "5c2c1c9aef40dbbf98424d02303f2ee77e6e869334247881e8aa0fcd8c415b47",
"tier": "full"
},
{
"id": "web-endpoint",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Web-Endpoint/releases/download/v1.1.0/web-endpoint-1.1.0.tmxplug",
"sha256": "79e691abc456b17c8ce651017b47097a7ff71e94267e668341548a05b911a92c",
"tier": "full"
},
{
"id": "webauthn",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-WebAuthn/releases/download/v1.1.0/webauthn-1.1.0.tmxplug",
"sha256": "83a61a530db0273f9fef2080d6aa027a82ef8374fb4e0f158a8559a2770299de",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
},
{
"id": "workspaces",
"source": "tmxplug",
"url": "https://github.com/Termix-SSH/Plugin-Workspaces/releases/download/v1.1.0/workspaces-1.1.0.tmxplug",
"sha256": "7189d767c3f7cddfb59366f6cceeca27a0b29e41da1dcc89d57a25317a0fe4bf",
"tier": "standard",
"onboarding": {
"recommended": true,
"consent": false
}
}
]
}
+9 -3
View File
@@ -12,9 +12,15 @@ services:
environment:
PORT: "8080"
NODE_ENV: development
# REDIS_URL: "redis://redis:6379"
# TERMIX_REDIS_PREFIX: "termix:collab"
# TERMIX_STEP_CA_REDIS_PREFIX: "termix:step-ca"
depends_on:
- guacd-dev
networks:
- termix-dev-net
guacd-dev:
image: guacamole/guacd:1.6.0
container_name: guacd-dev
restart: unless-stopped
networks:
- termix-dev-net
+11 -22
View File
@@ -1,6 +1,6 @@
services:
termix:
image: ghcr.io/termix-ssh/termix:latest
image: ghcr.io/lukegus/termix:latest
container_name: termix
restart: unless-stopped
ports:
@@ -9,27 +9,16 @@ services:
- termix-data:/app/data
environment:
PORT: "8080"
# Multi-instance deployments should point every backend at the same
# Redis. Without it, collaboration rooms keep their single-instance
# in-memory fallback.
# REDIS_URL: "redis://redis:6379"
# TERMIX_REDIS_PREFIX: "termix:collab"
# TERMIX_STEP_CA_REDIS_PREFIX: "termix:step-ca"
# Hardened deployments can require keys from environment variables or
# Docker secrets mounted through JWT_SECRET_FILE, DATABASE_KEY_FILE,
# ENCRYPTION_KEY_FILE and INTERNAL_AUTH_TOKEN_FILE.
# TERMIX_REQUIRE_EXTERNAL_SECRETS: "true"
# Trusted reverse-proxy authentication is disabled by default. When
# enabled, do not expose this container directly to untrusted clients.
# TRUSTED_PROXY_AUTH_ENABLED: "true"
# List the address your authenticating proxy connects from, as this
# container sees it (for a proxy container on termix-net, its IP there).
# Keep it narrow: published ports can reach the container from the
# Docker gateway, so do not trust whole private ranges.
# TRUSTED_PROXY_AUTH_TRUSTED_PROXIES: "172.20.0.10"
# TRUSTED_PROXY_AUTH_ROLE_MAP: '{"operators":["user"]}'
# TRUSTED_PROXY_AUTH_USERNAME_HEADER: "x-forwarded-username"
# TRUSTED_PROXY_AUTH_ROLE_HEADER: "x-forwarded-role"
GUACD_HOST: "guacd"
depends_on:
- guacd
networks:
- termix-net
guacd:
image: guacamole/guacd:1.6.0
container_name: guacd
restart: unless-stopped
networks:
- termix-net
+27 -81
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 /app/html /tmp/nginx 2>/dev/null || true
chown -R node:node /app/data /app/uploads /tmp/nginx 2>/dev/null || true
echo "User node is now UID: $PUID, GID: $PGID"
@@ -22,38 +22,11 @@ if [ "$(id -u)" = "0" ]; then
fi
fi
DATA_DIR=${DATA_DIR:-/app/data}
RUNTIME_ENABLE_SSL_SET=${ENABLE_SSL+x}
RUNTIME_ENABLE_SSL=${ENABLE_SSL-}
RUNTIME_SSL_PORT_SET=${SSL_PORT+x}
RUNTIME_SSL_PORT=${SSL_PORT-}
RUNTIME_SSL_CERT_PATH_SET=${SSL_CERT_PATH+x}
RUNTIME_SSL_CERT_PATH=${SSL_CERT_PATH-}
RUNTIME_SSL_KEY_PATH_SET=${SSL_KEY_PATH+x}
RUNTIME_SSL_KEY_PATH=${SSL_KEY_PATH-}
RUNTIME_SSL_DOMAIN_SET=${SSL_DOMAIN+x}
RUNTIME_SSL_DOMAIN=${SSL_DOMAIN-}
if [ -f "$DATA_DIR/.env" ]; then
echo "Loading persisted SSL settings from $DATA_DIR/.env"
set -a
. "$DATA_DIR/.env"
set +a
fi
[ "$RUNTIME_ENABLE_SSL_SET" = "x" ] && ENABLE_SSL=$RUNTIME_ENABLE_SSL
[ "$RUNTIME_SSL_PORT_SET" = "x" ] && SSL_PORT=$RUNTIME_SSL_PORT
[ "$RUNTIME_SSL_CERT_PATH_SET" = "x" ] && SSL_CERT_PATH=$RUNTIME_SSL_CERT_PATH
[ "$RUNTIME_SSL_KEY_PATH_SET" = "x" ] && SSL_KEY_PATH=$RUNTIME_SSL_KEY_PATH
[ "$RUNTIME_SSL_DOMAIN_SET" = "x" ] && SSL_DOMAIN=$RUNTIME_SSL_DOMAIN
export PORT=${PORT:-8080}
export ENABLE_SSL=${ENABLE_SSL:-false}
export SSL_PORT=${SSL_PORT:-8443}
export SSL_CERT_PATH=${SSL_CERT_PATH:-/app/data/ssl/termix.crt}
export SSL_KEY_PATH=${SSL_KEY_PATH:-/app/data/ssl/termix.key}
export TERMIX_SSL_TERMINATED_BY_NGINX=true
echo "Configuring web UI to run on port: $PORT"
@@ -68,40 +41,31 @@ fi
mkdir -p /tmp/nginx
envsubst '${PORT} ${SSL_PORT} ${SSL_CERT_PATH} ${SSL_KEY_PATH}' < $NGINX_CONF_SOURCE > /tmp/nginx/nginx.conf
# Which proxies may set the client IP through X-Forwarded-For. Unset keeps
# the private ranges in the template.
if [ -n "$TRUSTED_PROXIES" ]; then
REAL_IP_LINES=""
for proxy in $(echo "$TRUSTED_PROXIES" | tr ',' ' '); do
case "$proxy" in
*[!0-9a-fA-F:./]*) echo "Ignoring invalid TRUSTED_PROXIES entry: $proxy" ;;
*) REAL_IP_LINES="${REAL_IP_LINES} set_real_ip_from ${proxy};\n" ;;
esac
done
echo "Trusting X-Forwarded-For from: $TRUSTED_PROXIES"
awk -v lines="$REAL_IP_LINES" '
/# BEGIN TRUSTED_PROXIES/ { printf "%s", lines; skip = 1; next }
/# END TRUSTED_PROXIES/ { skip = 0; next }
!skip
' /tmp/nginx/nginx.conf > /tmp/nginx/nginx.conf.tmp
mv /tmp/nginx/nginx.conf.tmp /tmp/nginx/nginx.conf
fi
if [ "$ENABLE_SSL" = "true" ] && [ "$PORT" = "$SSL_PORT" ]; then
echo "HTTP and HTTPS use port $SSL_PORT; disabling the HTTP redirect listener"
sed -i '/# BEGIN HTTP_REDIRECT_SERVER/,/# END HTTP_REDIRECT_SERVER/d' /tmp/nginx/nginx.conf
fi
mkdir -p /app/data /app/uploads
chmod 755 /app/data /app/uploads 2>/dev/null || true
mkdir -p /app/data /app/uploads /app/data/.opk
chmod 755 /app/data /app/uploads /app/data/.opk 2>/dev/null || true
if [ -w /app/data ]; then
echo "Data directory is writable"
else
echo "WARNING: Data directory is not writable. Plugins that keep files (OPKSSH, recordings) may fail."
echo "WARNING: Data directory is not writable. OPKSSH may fail."
ls -ld /app/data
fi
if [ -w /app/data/.opk ]; then
echo "OPKSSH directory is writable"
else
echo "WARNING: OPKSSH directory is not writable. OPKSSH authentication will fail."
ls -ld /app/data/.opk
fi
OPKSSH_DIR="${DATA_DIR:-/app/data}/opkssh"
if [ ! -d "$OPKSSH_DIR" ]; then
echo "WARNING: OPKSSH binary directory not found at $OPKSSH_DIR"
echo "OPKSSH will be downloaded automatically on first use."
else
echo "OPKSSH binary directory found at $OPKSSH_DIR"
fi
if [ "$ENABLE_SSL" = "true" ]; then
echo "Checking SSL certificate configuration..."
mkdir -p /app/data/ssl
@@ -115,16 +79,8 @@ if [ "$ENABLE_SSL" = "true" ]; then
if openssl x509 -in /app/data/ssl/termix.crt -checkend 2592000 -noout >/dev/null 2>&1; then
echo "SSL certificates are valid and will be reused for domain: $DOMAIN"
else
SUBJECT=$(openssl x509 -in /app/data/ssl/termix.crt -noout -subject 2>/dev/null | sed 's/^subject=//')
ISSUER=$(openssl x509 -in /app/data/ssl/termix.crt -noout -issuer 2>/dev/null | sed 's/^issuer=//')
# Only replace our own self-signed certificate. A CA-issued one is
# kept until it expires, even when nothing renews it.
if [ -n "$SUBJECT" ] && [ "$SUBJECT" = "$ISSUER" ]; then
echo "Self-signed SSL certificate is expired or expiring soon, regenerating..."
rm -f /app/data/ssl/termix.crt /app/data/ssl/termix.key
else
echo "WARNING: SSL certificate expires within 30 days and was not issued by Termix. Renew it."
fi
echo "SSL certificate is expired or expiring soon, regenerating..."
rm -f /app/data/ssl/termix.crt /app/data/ssl/termix.key
fi
else
echo "SSL certificates not found, will generate new ones..."
@@ -182,21 +138,7 @@ 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%/}"
case "$CLEAN_BASE_PATH" in
/*) ;;
*) echo "BASE_PATH must start with /" >&2; exit 1 ;;
esac
case "$CLEAN_BASE_PATH" in
*[!A-Za-z0-9_./~-]*) echo "BASE_PATH contains unsupported characters" >&2; exit 1 ;;
esac
find /app/html -name "index.html" -exec sed -i "s|name=\"termix-base-path\" content=\"\"|name=\"termix-base-path\" content=\"$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" {} \;
find /app/html -name "index.html" -exec sed -i "s|window.__TERMIX_BASE_PATH__ = \"\"|window.__TERMIX_BASE_PATH__ = \"$BASE_PATH\"|g" {} \;
fi
echo "Starting backend services..."
@@ -214,4 +156,8 @@ else
echo "Warning: package.json not found"
fi
exec node dist/backend/backend/starter.js
node dist/backend/backend/starter.js
echo "All services started"
tail -f /dev/null
+350 -251
View File
@@ -11,8 +11,6 @@ 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;
@@ -23,32 +21,10 @@ http {
sendfile on;
keepalive_timeout 65;
# Static assets only. API responses arrive already gzipped from the node
# backend, and gzip_proxied would otherwise have nginx decompress and
# recompress them for nothing.
gzip on;
gzip_vary on;
gzip_min_length 2048;
gzip_comp_level 5;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/wasm
image/svg+xml;
client_header_timeout 300s;
# BEGIN TRUSTED_PROXIES
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;
# END TRUSTED_PROXIES
real_ip_header X-Forwarded-For;
real_ip_recursive on;
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
@@ -71,52 +47,27 @@ http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# BEGIN HTTP_REDIRECT_SERVER
server {
listen ${PORT};
server_name _;
# ACME http-01 checks arrive on plain HTTP and must not be redirected.
location ^~ /.well-known/acme-challenge/ {
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;
}
location / {
return 301 https://$host:${SSL_PORT}$request_uri;
}
return 301 https://$host:${SSL_PORT}$request_uri;
}
# END HTTP_REDIRECT_SERVER
server {
listen ${SSL_PORT} ssl;
server_name _;
client_max_body_size 50m;
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;
location ^~ /.well-known/acme-challenge/ {
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;
}
add_header X-XSS-Protection "1; mode=block" always;
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;
}
@@ -124,68 +75,31 @@ 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 ^~ /assets/ {
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
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 Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-0FPou4vGHwupDie6Xais6VntpJ4yaK9E6TyhXLwZe8o='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
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;
@@ -200,9 +114,6 @@ http {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
# The TCP peer before set_real_ip_from rewrites $remote_addr from
# X-Forwarded-For; trusted proxy login checks this address.
proxy_set_header X-Termix-Proxy-Peer $realip_remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
@@ -215,7 +126,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/releases(/.*)?$ {
@@ -224,7 +135,16 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/alerts(/.*)?$ {
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;
}
location ~ ^/rbac(/.*)?$ {
@@ -233,7 +153,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/credentials(/.*)?$ {
@@ -242,152 +162,38 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location ~ ^/ssh-auth(/.*)?$ {
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;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/sync(/.*)?$ {
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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/audit-logs(/.*)?$ {
location ~ ^/terminal(/.*)?$ {
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 ~ ^/plugins(/.*)?$ {
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;
}
# Plugin frontend bundles, stylesheets and locales. Public like
# /assets; the backend picks the cache policy from the ?v= query.
location ^~ /plugin-assets/ {
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;
}
# Every plugin's HTTP routes. One block, no per-plugin entries: a
# plugin serves at /plugin-api/<id>/ on the main backend and never
# gets a port or a block of its own. Buffering is off so SSE streams
# (AI, Proxmox discovery) reach the browser as they are produced, and
# request buffering is off so large uploads stream through.
location ^~ /plugin-api/ {
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 0;
}
# Every plugin's WebSockets: terminal, Guacamole display, Docker
# console. Served at /plugin-ws/<id>/<path> from the same backend.
location ^~ /plugin-ws/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
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 ~ ^/host-sidebar(/.*)?$ {
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 ~ ^/credential-sidebar(/.*)?$ {
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 ~ ^/ui-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;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/database(/.*)?$ {
@@ -399,7 +205,26 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location ~ ^/db(/.*)?$ {
client_max_body_size 5G;
client_body_timeout 300s;
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_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -415,7 +240,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /host/quick-connect {
@@ -427,17 +252,37 @@ 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;
}
location ~ ^/host/opkssh-chooser(/.*)?$ {
proxy_pass http://127.0.0.1:30001/host/opkssh-chooser$1$is_args$args;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
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;
# A terminal is quiet most of the time - waiting on a prompt, reading
# output, running something long. Every console plugin dials this route
# (terminal, serial, remote desktop), and nginx's default
# proxy_read_timeout of 60s closed the socket about a minute into any
# pause, which reaches the user as the SSH server disconnecting. The
# plugin WebSocket routes below already ask for 86400s; this is the same
# class of socket, so it asks for the same.
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
location ~ ^/host/opkssh-callback(/.*)?$ {
proxy_pass http://127.0.0.1:30001/host/opkssh-callback$1$is_args$args;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
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_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
location /host/ {
@@ -446,13 +291,178 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
# File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved
# to the file-manager plugin, served under /plugin-api/file-manager/.
# Session logs moved to the session-recording plugin, served under
# /plugin-api/session-recording/.
location /ssh/websocket/ {
proxy_pass http://127.0.0.1:30002/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
location ^~ /guacamole/websocket/ {
proxy_pass http://127.0.0.1:30008/;
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 $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
location ~ ^/guacamole(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
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;
}
location /host/tunnel/ {
proxy_pass http://127.0.0.1:30003;
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;
}
location /ssh/tunnel/ {
proxy_pass http://127.0.0.1:30003;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_cache off;
}
location /host/file_manager/recent {
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;
}
location /host/file_manager/pinned {
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;
}
location /host/file_manager/shortcuts {
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;
}
location /host/file_manager/sudo-password {
proxy_pass http://127.0.0.1:30004;
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;
}
location /ssh/file_manager/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location /host/file_manager/ssh/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location ~ ^/network-topology(/.*)?$ {
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;
}
location /health {
proxy_pass http://127.0.0.1:30001;
@@ -460,28 +470,117 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Service links and the homepage widget canvas moved to the homepage
# plugin, served under /plugin-api/homepage/.
location /dashboard/ {
proxy_pass http://127.0.0.1:30001;
location ~ ^/status(/.*)?$ {
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_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/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 $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location ~ ^/(refresh|host-updated)$ {
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 $scheme;
}
location ~ ^/global-settings(/.*)?$ {
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 $scheme;
}
location ~ ^/uptime(/.*)?$ {
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;
}
location ~ ^/activity(/.*)?$ {
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;
}
location ~ ^/dashboard/preferences(/.*)?$ {
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;
}
location ^~ /docker/console/ {
proxy_pass http://127.0.0.1:30009/;
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 $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
error_page 500 502 503 504 /50x.html;
# Only nginx's own errors land here (the backend is down or too slow).
# There is no 50x page, so answer directly instead of a 404.
location = /50x.html {
internal;
default_type application/json;
return 502 '{"error":"The Termix server is not responding"}';
root /app/html;
}
}
}
+350 -231
View File
@@ -11,8 +11,6 @@ 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;
@@ -23,32 +21,10 @@ http {
sendfile on;
keepalive_timeout 65;
# Static assets only. API responses arrive already gzipped from the node
# backend, and gzip_proxied would otherwise have nginx decompress and
# recompress them for nothing.
gzip on;
gzip_vary on;
gzip_min_length 2048;
gzip_comp_level 5;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/wasm
image/svg+xml;
client_header_timeout 300s;
# BEGIN TRUSTED_PROXIES
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;
# END TRUSTED_PROXIES
real_ip_header X-Forwarded-For;
real_ip_recursive on;
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $http_x_forwarded_proto;
@@ -73,25 +49,14 @@ http {
server {
listen ${PORT};
server_name _;
client_max_body_size 50m;
absolute_redirect off;
server_name localhost;
add_header X-Content-Type-Options nosniff always;
location ^~ /.well-known/acme-challenge/ {
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;
}
add_header X-XSS-Protection "1; mode=block" always;
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;
}
@@ -99,62 +64,31 @@ 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 ^~ /assets/ {
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
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 Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'sha256-0FPou4vGHwupDie6Xais6VntpJ4yaK9E6TyhXLwZe8o='; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: http: https:; font-src 'self' data:; connect-src 'self' http: https: ws: wss:; media-src 'self' data: blob: http: https:; worker-src 'self' blob:; frame-src http: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" 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;
@@ -169,9 +103,6 @@ http {
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
# The TCP peer before set_real_ip_from rewrites $remote_addr from
# X-Forwarded-For; trusted proxy login checks this address.
proxy_set_header X-Termix-Proxy-Peer $realip_remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
@@ -184,7 +115,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/releases(/.*)?$ {
@@ -193,7 +124,16 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/alerts(/.*)?$ {
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;
}
location ~ ^/rbac(/.*)?$ {
@@ -202,7 +142,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/credentials(/.*)?$ {
@@ -211,152 +151,38 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location ~ ^/ssh-auth(/.*)?$ {
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;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/sync(/.*)?$ {
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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/audit-logs(/.*)?$ {
location ~ ^/terminal(/.*)?$ {
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 ~ ^/plugins(/.*)?$ {
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;
}
# Plugin frontend bundles, stylesheets and locales. Public like
# /assets; the backend picks the cache policy from the ?v= query.
location ^~ /plugin-assets/ {
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;
}
# Every plugin's HTTP routes. One block, no per-plugin entries: a
# plugin serves at /plugin-api/<id>/ on the main backend and never
# gets a port or a block of its own. Buffering is off so SSE streams
# (AI, Proxmox discovery) reach the browser as they are produced, and
# request buffering is off so large uploads stream through.
location ^~ /plugin-api/ {
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 0;
}
# Every plugin's WebSockets: terminal, Guacamole display, Docker
# console. Served at /plugin-ws/<id>/<path> from the same backend.
location ^~ /plugin-ws/ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
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 ~ ^/host-sidebar(/.*)?$ {
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 ~ ^/credential-sidebar(/.*)?$ {
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 ~ ^/ui-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;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/database(/.*)?$ {
@@ -368,7 +194,26 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location ~ ^/db(/.*)?$ {
client_max_body_size 5G;
client_body_timeout 300s;
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_connect_timeout 60s;
proxy_send_timeout 300s;
@@ -384,7 +229,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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /host/quick-connect {
@@ -396,17 +241,37 @@ 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;
}
location ~ ^/host/opkssh-chooser(/.*)?$ {
proxy_pass http://127.0.0.1:30001/host/opkssh-chooser$1$is_args$args;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
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;
# A terminal is quiet most of the time - waiting on a prompt, reading
# output, running something long. Every console plugin dials this route
# (terminal, serial, remote desktop), and nginx's default
# proxy_read_timeout of 60s closed the socket about a minute into any
# pause, which reaches the user as the SSH server disconnecting. The
# plugin WebSocket routes below already ask for 86400s; this is the same
# class of socket, so it asks for the same.
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
location ~ ^/host/opkssh-callback(/.*)?$ {
proxy_pass http://127.0.0.1:30001/host/opkssh-callback$1$is_args$args;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
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_cache_bypass 1;
proxy_no_cache 1;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
location /host/ {
@@ -415,13 +280,178 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
# File manager routes (recent/pinned/shortcuts, SFTP, transfers) moved
# to the file-manager plugin, served under /plugin-api/file-manager/.
# Session logs moved to the session-recording plugin, served under
# /plugin-api/session-recording/.
location /ssh/websocket/ {
proxy_pass http://127.0.0.1:30002/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
location ^~ /guacamole/websocket/ {
proxy_pass http://127.0.0.1:30008/;
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 $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
location ~ ^/guacamole(/.*)?$ {
proxy_pass http://127.0.0.1:30001;
proxy_http_version 1.1;
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;
}
location /host/tunnel/ {
proxy_pass http://127.0.0.1:30003;
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;
}
location /ssh/tunnel/ {
proxy_pass http://127.0.0.1:30003;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_buffering off;
proxy_cache off;
}
location /host/file_manager/recent {
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;
}
location /host/file_manager/pinned {
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;
}
location /host/file_manager/shortcuts {
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;
}
location /host/file_manager/sudo-password {
proxy_pass http://127.0.0.1:30004;
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;
}
location /ssh/file_manager/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location /host/file_manager/ssh/ {
client_max_body_size 5G;
client_body_timeout 300s;
add_header Cache-Control "no-store, no-cache, must-revalidate" always;
proxy_pass http://127.0.0.1:30004;
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_request_buffering off;
proxy_buffering off;
}
location ~ ^/network-topology(/.*)?$ {
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;
}
location /health {
proxy_pass http://127.0.0.1:30001;
@@ -429,28 +459,117 @@ 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 $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Service links and the homepage widget canvas moved to the homepage
# plugin, served under /plugin-api/homepage/.
location /dashboard/ {
proxy_pass http://127.0.0.1:30001;
location ~ ^/status(/.*)?$ {
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_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/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 $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location ~ ^/(refresh|host-updated)$ {
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 $scheme;
}
location ~ ^/global-settings(/.*)?$ {
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 $scheme;
}
location ~ ^/uptime(/.*)?$ {
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;
}
location ~ ^/activity(/.*)?$ {
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;
}
location ~ ^/dashboard/preferences(/.*)?$ {
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;
}
location ^~ /docker/console/ {
proxy_pass http://127.0.0.1:30009/;
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 $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $http_host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 10s;
proxy_buffering off;
proxy_request_buffering off;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
}
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_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
error_page 500 502 503 504 /50x.html;
# Only nginx's own errors land here (the backend is down or too slow).
# There is no 50x page, so answer directly instead of a 404.
location = /50x.html {
internal;
default_type application/json;
return 502 '{"error":"The Termix server is not responding"}';
root /app/html;
}
}
}
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "mysql",
schema: "./src/backend/database/db/schema.mysql.ts",
out: "./drizzle/mysql",
});
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./src/backend/database/db/schema.pg.ts",
out: "./drizzle/postgres",
});
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/backend/database/db/schema.ts",
out: "./drizzle/sqlite",
});
-890
View File
@@ -1,890 +0,0 @@
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;
-10
View File
@@ -1,10 +0,0 @@
CREATE TABLE `host_sidebar_preferences` (
`user_id` varchar(255) NOT NULL,
`data` text NOT NULL,
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `host_sidebar_preferences_user_id` PRIMARY KEY(`user_id`)
);
--> statement-breakpoint
ALTER TABLE `ssh_data` ADD `sort_order` int;--> statement-breakpoint
ALTER TABLE `ssh_folders` ADD `sort_order` int;--> statement-breakpoint
ALTER TABLE `host_sidebar_preferences` ADD CONSTRAINT `host_sidebar_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
-10
View File
@@ -1,10 +0,0 @@
CREATE TABLE `credential_sidebar_preferences` (
`user_id` varchar(255) NOT NULL,
`data` text NOT NULL,
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `credential_sidebar_preferences_user_id` PRIMARY KEY(`user_id`)
);
--> statement-breakpoint
ALTER TABLE `ssh_credentials` ADD `pin` boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE `ssh_credentials` ADD `sort_order` int;--> statement-breakpoint
ALTER TABLE `credential_sidebar_preferences` ADD CONSTRAINT `credential_sidebar_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
-1
View File
@@ -1 +0,0 @@
ALTER TABLE `snippets` ADD `is_note` boolean DEFAULT false NOT NULL;
@@ -1,28 +0,0 @@
CREATE TABLE `proxmox_node_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 `proxmox_node_history_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `proxmox_stats_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 `proxmox_stats_preferences_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_proxmox_stats_prefs_user_host` UNIQUE(`user_id`,`host_id`)
);
--> statement-breakpoint
ALTER TABLE `ssh_data` ADD `enable_proxmox_stats` boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD `proxmox_stats_config` text;--> statement-breakpoint
ALTER TABLE `proxmox_node_history` ADD CONSTRAINT `proxmox_node_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 `proxmox_stats_preferences` ADD CONSTRAINT `proxmox_stats_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `proxmox_stats_preferences` ADD CONSTRAINT `proxmox_stats_preferences_host_id_ssh_data_id_fk` FOREIGN KEY (`host_id`) REFERENCES `ssh_data`(`id`) ON DELETE cascade ON UPDATE no action;
-1
View File
@@ -1 +0,0 @@
ALTER TABLE `ssh_data` ADD `enable_terminal_toolbar` boolean DEFAULT true NOT NULL;
@@ -1,45 +0,0 @@
CREATE TABLE `fleet_inventory` (
`id` int AUTO_INCREMENT NOT NULL,
`host_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`os_pretty_name` text,
`kernel` text,
`architecture` text,
`hostname` text,
`uptime_seconds` int,
`ip` text,
`package_manager` text,
`collected_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `fleet_inventory_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_fleet_inventory_host` UNIQUE(`host_id`,`user_id`)
);
--> statement-breakpoint
CREATE TABLE `fleet_members` (
`id` int AUTO_INCREMENT NOT NULL,
`fleet_id` int NOT NULL,
`host_id` int NOT NULL,
`added_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `fleet_members_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_fleet_members_fleet_host` UNIQUE(`fleet_id`,`host_id`)
);
--> statement-breakpoint
CREATE TABLE `fleets` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`color` text,
`icon` text,
`tag_rules` text,
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `fleets_id` PRIMARY KEY(`id`),
CONSTRAINT `fleets_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
ALTER TABLE `fleet_inventory` ADD CONSTRAINT `fleet_inventory_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 `fleet_inventory` ADD CONSTRAINT `fleet_inventory_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `fleet_members` ADD CONSTRAINT `fleet_members_fleet_id_fleets_id_fk` FOREIGN KEY (`fleet_id`) REFERENCES `fleets`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `fleet_members` ADD CONSTRAINT `fleet_members_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 `fleets` ADD CONSTRAINT `fleets_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE `ssh_data` ADD `parent_host_id` int;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD CONSTRAINT `ssh_data_parent_host_id_ssh_data_id_fk` FOREIGN KEY (`parent_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE set null ON UPDATE no action;
-18
View File
@@ -1,18 +0,0 @@
CREATE TABLE `user_workspaces` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`color` text,
`icon` text,
`kind` text NOT NULL DEFAULT ('manual'),
`is_default` boolean NOT NULL DEFAULT false,
`payload` text NOT NULL DEFAULT ('{}'),
`sync_id` varchar(255),
`created_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`last_used_at` text,
CONSTRAINT `user_workspaces_id` PRIMARY KEY(`id`),
CONSTRAINT `user_workspaces_sync_id_unique` UNIQUE(`sync_id`)
);
--> statement-breakpoint
ALTER TABLE `user_workspaces` ADD CONSTRAINT `user_workspaces_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
-26
View File
@@ -1,26 +0,0 @@
ALTER TABLE `api_keys` MODIFY COLUMN `expires_at` varchar(255);--> statement-breakpoint
ALTER TABLE `audit_logs` MODIFY COLUMN `action` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `audit_logs` MODIFY COLUMN `resource_type` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `audit_logs` MODIFY COLUMN `timestamp` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_access` MODIFY COLUMN `expires_at` varchar(255);--> statement-breakpoint
ALTER TABLE `opkssh_tokens` MODIFY COLUMN `expires_at` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `recent_activity` MODIFY COLUMN `timestamp` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `session_shares` MODIFY COLUMN `expires_at` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `sessions` MODIFY COLUMN `expires_at` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `snippet_access` MODIFY COLUMN `expires_at` varchar(255);--> statement-breakpoint
ALTER TABLE `trusted_devices` MODIFY COLUMN `expires_at` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `vault_tokens` MODIFY COLUMN `expires_at` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `webauthn_credentials` MODIFY COLUMN `credential_id` varchar(255) NOT NULL;--> statement-breakpoint
CREATE INDEX `idx_audit_logs_timestamp` ON `audit_logs` (`timestamp`);--> statement-breakpoint
CREATE INDEX `idx_audit_logs_user_ts` ON `audit_logs` (`user_id`,`timestamp`);--> statement-breakpoint
CREATE INDEX `idx_audit_logs_action_ts` ON `audit_logs` (`action`,`timestamp`);--> statement-breakpoint
CREATE INDEX `idx_audit_logs_resource_ts` ON `audit_logs` (`resource_type`,`timestamp`);--> statement-breakpoint
CREATE INDEX `idx_host_access_user_id` ON `host_access` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_host_access_role_id` ON `host_access` (`role_id`);--> statement-breakpoint
CREATE INDEX `idx_host_access_host_id` ON `host_access` (`host_id`);--> statement-breakpoint
CREATE INDEX `idx_host_access_expires_at` ON `host_access` (`expires_at`);--> statement-breakpoint
CREATE INDEX `idx_ssh_data_user_id` ON `ssh_data` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_data_parent_host` ON `ssh_data` (`parent_host_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_data_credential` ON `ssh_data` (`credential_id`);--> statement-breakpoint
CREATE INDEX `idx_sessions_user_id` ON `sessions` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_sessions_expires_at` ON `sessions` (`expires_at`);
@@ -1,8 +0,0 @@
CREATE TABLE `ui_preferences` (
`user_id` varchar(255) NOT NULL,
`data` text NOT NULL,
`updated_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ui_preferences_user_id` PRIMARY KEY(`user_id`)
);
--> statement-breakpoint
ALTER TABLE `ui_preferences` ADD CONSTRAINT `ui_preferences_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;
-32
View File
@@ -1,32 +0,0 @@
ALTER TABLE `alert_firings` MODIFY COLUMN `fired_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `session_recordings` MODIFY COLUMN `started_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `session_shares` MODIFY COLUMN `session_id` varchar(255) NOT NULL;--> statement-breakpoint
CREATE INDEX `idx_alert_firings_rule` ON `alert_firings` (`rule_id`,`fired_at`);--> statement-breakpoint
CREATE INDEX `idx_alert_firings_host` ON `alert_firings` (`host_id`);--> statement-breakpoint
CREATE INDEX `idx_api_keys_user_id` ON `api_keys` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_command_history_user_host` ON `command_history` (`user_id`,`host_id`);--> statement-breakpoint
CREATE INDEX `idx_dismissed_alerts_user_id` ON `dismissed_alerts` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_file_manager_pinned_user` ON `file_manager_pinned` (`user_id`,`host_id`);--> statement-breakpoint
CREATE INDEX `idx_file_manager_recent_user` ON `file_manager_recent` (`user_id`,`host_id`);--> statement-breakpoint
CREATE INDEX `idx_file_manager_shortcuts_user` ON `file_manager_shortcuts` (`user_id`,`host_id`);--> statement-breakpoint
CREATE INDEX `idx_fleet_inventory_user` ON `fleet_inventory` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_fleet_members_host` ON `fleet_members` (`host_id`);--> statement-breakpoint
CREATE INDEX `idx_homepage_items_user_id` ON `homepage_items` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_recent_activity_user_ts` ON `recent_activity` (`user_id`,`timestamp`);--> statement-breakpoint
CREATE INDEX `idx_session_recordings_user_started` ON `session_recordings` (`user_id`,`started_at`);--> statement-breakpoint
CREATE INDEX `idx_session_recordings_host` ON `session_recordings` (`host_id`);--> statement-breakpoint
CREATE INDEX `idx_session_shares_session_id` ON `session_shares` (`session_id`);--> statement-breakpoint
CREATE INDEX `idx_session_shares_host_id` ON `session_shares` (`host_id`);--> statement-breakpoint
CREATE INDEX `idx_snippet_access_user_id` ON `snippet_access` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_snippet_access_snippet_id` ON `snippet_access` (`snippet_id`);--> statement-breakpoint
CREATE INDEX `idx_snippet_access_role_id` ON `snippet_access` (`role_id`);--> statement-breakpoint
CREATE INDEX `idx_snippets_user_id` ON `snippets` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_credential_usage_credential` ON `ssh_credential_usage` (`credential_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_credential_usage_user` ON `ssh_credential_usage` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_credentials_user_id` ON `ssh_credentials` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_ssh_folders_user_id` ON `ssh_folders` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_transfer_recent_user` ON `transfer_recent` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_trusted_devices_user_id` ON `trusted_devices` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_user_open_tabs_user_id` ON `user_open_tabs` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_user_roles_role_id` ON `user_roles` (`role_id`);--> statement-breakpoint
CREATE INDEX `idx_user_workspaces_user_id` ON `user_workspaces` (`user_id`);
-224
View File
@@ -1,224 +0,0 @@
CREATE TABLE `ai_conversations` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`title` text,
`provider_id` int,
`model` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ai_conversations_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `ai_messages` (
`id` int AUTO_INCREMENT NOT NULL,
`conversation_id` int NOT NULL,
`role` text NOT NULL,
`content` text NOT NULL DEFAULT (''),
`tool_calls` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ai_messages_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `ai_proposals` (
`id` int AUTO_INCREMENT NOT NULL,
`conversation_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`kind` text NOT NULL,
`summary` text,
`payload` text NOT NULL DEFAULT ('{}'),
`status` varchar(255) NOT NULL DEFAULT 'pending',
`applied_at` text,
`result_summary` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ai_proposals_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `ai_providers` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`provider_type` text NOT NULL,
`label` varchar(255) NOT NULL,
`base_url` text,
`api_key` text,
`api_key_prefix` text,
`default_model` text,
`enabled` boolean NOT NULL DEFAULT true,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `ai_providers_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_ai_providers_user_label` UNIQUE(`user_id`,`label`)
);
--> statement-breakpoint
CREATE TABLE `automation_channels` (
`id` int AUTO_INCREMENT NOT NULL,
`automation_id` int NOT NULL,
`channel_id` int NOT NULL,
CONSTRAINT `automation_channels_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_automation_channels_pair` UNIQUE(`automation_id`,`channel_id`)
);
--> statement-breakpoint
CREATE TABLE `automation_run_steps` (
`id` int AUTO_INCREMENT NOT NULL,
`run_id` int NOT NULL,
`step_index` int NOT NULL,
`step_id` text NOT NULL,
`step_type` text NOT NULL,
`status` varchar(255) NOT NULL,
`started_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`finished_at` text,
`output` text,
`error` text,
`truncated` boolean NOT NULL DEFAULT false,
CONSTRAINT `automation_run_steps_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `automation_runs` (
`id` int AUTO_INCREMENT NOT NULL,
`automation_id` int NOT NULL,
`user_id` varchar(255) NOT NULL,
`trigger_type` text NOT NULL,
`trigger_context` text,
`status` varchar(255) NOT NULL,
`started_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`finished_at` text,
`duration_ms` int,
`error` text,
`dry_run` boolean NOT NULL DEFAULT false,
`parent_run_id` int,
CONSTRAINT `automation_runs_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `automation_schedules` (
`id` int AUTO_INCREMENT NOT NULL,
`automation_id` int NOT NULL,
`cron` text,
`interval_seconds` int,
`timezone` text,
`next_due_at` varchar(255),
`last_tick_at` text,
CONSTRAINT `automation_schedules_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_automation_schedules_automation` UNIQUE(`automation_id`)
);
--> statement-breakpoint
CREATE TABLE `automation_trigger_state` (
`id` int AUTO_INCREMENT NOT NULL,
`automation_id` int NOT NULL,
`state_key` varchar(255) NOT NULL,
`breach_started_at` text,
`last_fired_at` text,
`last_value` double,
`last_observed_state` text,
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `automation_trigger_state_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_automation_trigger_state_key` UNIQUE(`automation_id`,`state_key`)
);
--> statement-breakpoint
CREATE TABLE `automations` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` text,
`enabled` boolean NOT NULL DEFAULT true,
`definition` text NOT NULL,
`definition_version` int NOT NULL DEFAULT 1,
`concurrency_policy` text NOT NULL DEFAULT ('skip'),
`max_run_seconds` int NOT NULL DEFAULT 300,
`dry_run` boolean NOT NULL DEFAULT false,
`last_run_at` text,
`last_run_status` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `automations_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `alert_rules` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `alert_rules` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `api_keys` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `c2s_tunnel_presets` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `c2s_tunnel_presets` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `credential_sidebar_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `dashboard_service_links` MODIFY COLUMN `label` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `dashboard_service_links` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `dashboard_service_links` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `file_manager_shortcuts` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `fleets` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `fleets` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `homepage_items` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `homepage_items` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `homepage_layouts` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_access` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_health_checks` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_health_checks` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_metrics_preferences` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_metrics_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `host_sidebar_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_data` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_data` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `network_topology` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `network_topology` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `notification_channels` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `opkssh_tokens` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `proxmox_stats_preferences` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `proxmox_stats_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `roles` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `roles` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `session_shares` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `sessions` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `shared_host_auth_overrides` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `shared_host_auth_overrides` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `shared_host_secrets` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `shared_host_secrets` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `snippet_access` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `snippet_folders` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `snippet_folders` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `snippets` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `snippets` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_credentials` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_credentials` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_folders` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ssh_folders` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `sso_providers` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `sso_providers` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `termix_identities` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `termix_identities` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `termix_identity_ca` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `termix_identity_ca` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `termix_identity_keys` MODIFY COLUMN `label` varchar(255);--> statement-breakpoint
ALTER TABLE `termix_identity_keys` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `tmux_session_tags` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `trusted_devices` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `ui_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_open_tabs` MODIFY COLUMN `label` varchar(255) NOT NULL;--> statement-breakpoint
ALTER TABLE `user_open_tabs` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_open_tabs` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_preferences` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_workspaces` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_workspaces` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `vault_profiles` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `vault_profiles` MODIFY COLUMN `updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `vault_tokens` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `webauthn_credentials` MODIFY COLUMN `created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
ALTER TABLE `user_preferences` ADD `ai_assistant_enabled` boolean;--> statement-breakpoint
ALTER TABLE `user_preferences` ADD `ai_read_only_commands` boolean;--> statement-breakpoint
ALTER TABLE `ai_conversations` ADD CONSTRAINT `ai_conversations_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ai_messages` ADD CONSTRAINT `ai_messages_conversation_id_ai_conversations_id_fk` FOREIGN KEY (`conversation_id`) REFERENCES `ai_conversations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ai_proposals` ADD CONSTRAINT `ai_proposals_conversation_id_ai_conversations_id_fk` FOREIGN KEY (`conversation_id`) REFERENCES `ai_conversations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ai_proposals` ADD CONSTRAINT `ai_proposals_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `ai_providers` ADD CONSTRAINT `ai_providers_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_channels` ADD CONSTRAINT `automation_channels_automation_id_automations_id_fk` FOREIGN KEY (`automation_id`) REFERENCES `automations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_channels` ADD CONSTRAINT `automation_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 `automation_run_steps` ADD CONSTRAINT `automation_run_steps_run_id_automation_runs_id_fk` FOREIGN KEY (`run_id`) REFERENCES `automation_runs`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_runs` ADD CONSTRAINT `automation_runs_automation_id_automations_id_fk` FOREIGN KEY (`automation_id`) REFERENCES `automations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_runs` ADD CONSTRAINT `automation_runs_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_schedules` ADD CONSTRAINT `automation_schedules_automation_id_automations_id_fk` FOREIGN KEY (`automation_id`) REFERENCES `automations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automation_trigger_state` ADD CONSTRAINT `automation_trigger_state_automation_id_automations_id_fk` FOREIGN KEY (`automation_id`) REFERENCES `automations`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `automations` ADD CONSTRAINT `automations_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_ai_conversations_user` ON `ai_conversations` (`user_id`,`updated_at`);--> statement-breakpoint
CREATE INDEX `idx_ai_messages_conversation` ON `ai_messages` (`conversation_id`,`created_at`);--> statement-breakpoint
CREATE INDEX `idx_ai_proposals_user` ON `ai_proposals` (`user_id`,`status`);--> statement-breakpoint
CREATE INDEX `idx_ai_proposals_conversation` ON `ai_proposals` (`conversation_id`);--> statement-breakpoint
CREATE INDEX `idx_automation_run_steps_run` ON `automation_run_steps` (`run_id`,`step_index`);--> statement-breakpoint
CREATE INDEX `idx_automation_runs_automation` ON `automation_runs` (`automation_id`,`started_at`);--> statement-breakpoint
CREATE INDEX `idx_automation_runs_user` ON `automation_runs` (`user_id`,`started_at`);--> statement-breakpoint
CREATE INDEX `idx_automation_schedules_due` ON `automation_schedules` (`next_due_at`);--> statement-breakpoint
CREATE INDEX `idx_automations_user` ON `automations` (`user_id`,`enabled`);
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE `user_preferences` ADD `terminal_defaults` text;--> statement-breakpoint
ALTER TABLE `user_preferences` ADD `rdp_defaults` text;
-1
View File
@@ -1 +0,0 @@
ALTER TABLE `user_preferences` ADD `terminal_macros` text;
-34
View File
@@ -1,34 +0,0 @@
CREATE TABLE `collab_room_members` (
`id` int AUTO_INCREMENT NOT NULL,
`room_id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`room_role` text NOT NULL DEFAULT ('member'),
`added_by` varchar(255),
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `collab_room_members_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_collab_room_members_room_user` UNIQUE(`room_id`,`user_id`)
);
--> statement-breakpoint
CREATE TABLE `collab_rooms` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`owner_user_id` varchar(255) NOT NULL,
`persistent` boolean NOT NULL DEFAULT false,
`presenter_user_id` varchar(255),
`stage_protocol` text,
`stage_host_id` int,
`stage_share_id` varchar(255),
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`ended_at` text,
CONSTRAINT `collab_rooms_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `collab_room_members` ADD CONSTRAINT `collab_room_members_room_id_collab_rooms_id_fk` FOREIGN KEY (`room_id`) REFERENCES `collab_rooms`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `collab_room_members` ADD CONSTRAINT `collab_room_members_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `collab_room_members` ADD CONSTRAINT `collab_room_members_added_by_users_id_fk` FOREIGN KEY (`added_by`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `collab_rooms` ADD CONSTRAINT `collab_rooms_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 `collab_rooms` ADD CONSTRAINT `collab_rooms_presenter_user_id_users_id_fk` FOREIGN KEY (`presenter_user_id`) REFERENCES `users`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `collab_rooms` ADD CONSTRAINT `collab_rooms_stage_host_id_ssh_data_id_fk` FOREIGN KEY (`stage_host_id`) REFERENCES `ssh_data`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `collab_rooms` ADD CONSTRAINT `collab_rooms_stage_share_id_session_shares_id_fk` FOREIGN KEY (`stage_share_id`) REFERENCES `session_shares`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_collab_room_members_user` ON `collab_room_members` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_collab_rooms_owner` ON `collab_rooms` (`owner_user_id`);
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE `collab_rooms` ADD `guest_link_token` varchar(255);--> statement-breakpoint
ALTER TABLE `collab_rooms` ADD CONSTRAINT `idx_collab_rooms_guest_token` UNIQUE(`guest_link_token`);
-15
View File
@@ -1,15 +0,0 @@
CREATE TABLE `secret_sources` (
`id` varchar(255) NOT NULL,
`user_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`kind` text NOT NULL DEFAULT ('onepassword-connect'),
`base_url` text NOT NULL,
`token` text NOT NULL,
`shared` boolean NOT NULL DEFAULT false,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `secret_sources_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `secret_sources` ADD CONSTRAINT `secret_sources_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_secret_sources_user` ON `secret_sources` (`user_id`);
-42
View File
@@ -1,42 +0,0 @@
CREATE TABLE `credential_access` (
`id` int AUTO_INCREMENT NOT NULL,
`credential_id` int NOT NULL,
`user_id` varchar(255),
`role_id` int,
`granted_by` varchar(255) NOT NULL,
`permission_level` text NOT NULL DEFAULT ('use'),
`expires_at` varchar(255),
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `credential_access_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `shared_credential_secrets` (
`id` int AUTO_INCREMENT NOT NULL,
`credential_access_id` int NOT NULL,
`target_user_id` varchar(255) NOT NULL,
`credential_id` int NOT NULL,
`encrypted_username` text,
`auth_type` text NOT NULL DEFAULT ('password'),
`encrypted_password` text,
`encrypted_key` text,
`encrypted_key_password` text,
`key_type` text,
`public_key` text,
`cert_public_key` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `shared_credential_secrets_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_shared_credential_secrets_scope` UNIQUE(`credential_access_id`,`target_user_id`)
);
--> statement-breakpoint
ALTER TABLE `credential_access` ADD CONSTRAINT `credential_access_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 `credential_access` ADD CONSTRAINT `credential_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `credential_access` ADD CONSTRAINT `credential_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `credential_access` ADD CONSTRAINT `credential_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_credential_secrets` ADD CONSTRAINT `shared_cred_secrets_access_id_fk` FOREIGN KEY (`credential_access_id`) REFERENCES `credential_access`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `shared_credential_secrets` ADD CONSTRAINT `shared_credential_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_credential_secrets` ADD CONSTRAINT `shared_credential_secrets_credential_id_ssh_credentials_id_fk` FOREIGN KEY (`credential_id`) REFERENCES `ssh_credentials`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_credential_access_user_id` ON `credential_access` (`user_id`);--> statement-breakpoint
CREATE INDEX `idx_credential_access_role_id` ON `credential_access` (`role_id`);--> statement-breakpoint
CREATE INDEX `idx_credential_access_credential_id` ON `credential_access` (`credential_id`);--> statement-breakpoint
CREATE INDEX `idx_shared_credential_secrets_target` ON `shared_credential_secrets` (`target_user_id`,`credential_id`);
-22
View File
@@ -1,22 +0,0 @@
CREATE TABLE `folder_access` (
`id` int AUTO_INCREMENT NOT NULL,
`owner_user_id` varchar(255) NOT NULL,
`folder` varchar(255) NOT NULL,
`user_id` varchar(255),
`role_id` int,
`granted_by` varchar(255) NOT NULL,
`permission_level` text NOT NULL DEFAULT ('connect'),
`expires_at` varchar(255),
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `folder_access_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `ssh_data` MODIFY COLUMN `folder` varchar(255);--> statement-breakpoint
ALTER TABLE `snippets` MODIFY COLUMN `folder` varchar(255);--> statement-breakpoint
ALTER TABLE `ssh_credentials` MODIFY COLUMN `folder` varchar(255);--> statement-breakpoint
ALTER TABLE `vault_profiles` MODIFY COLUMN `folder` varchar(255);--> statement-breakpoint
ALTER TABLE `folder_access` ADD CONSTRAINT `folder_access_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 `folder_access` ADD CONSTRAINT `folder_access_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `folder_access` ADD CONSTRAINT `folder_access_role_id_roles_id_fk` FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `folder_access` ADD CONSTRAINT `folder_access_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_folder_access_owner_folder` ON `folder_access` (`owner_user_id`,`folder`);
-2
View File
@@ -1,2 +0,0 @@
ALTER TABLE `ssh_data` ADD `enable_web_ui` boolean DEFAULT false NOT NULL;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD `web_ui_config` text;
-1
View File
@@ -1 +0,0 @@
ALTER TABLE `ssh_data` ADD `enable_ai_assistant` boolean DEFAULT false NOT NULL;
-51
View File
@@ -1,51 +0,0 @@
CREATE TABLE `plugin_install_counts` (
`id` int AUTO_INCREMENT NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`registry_id` varchar(255) NOT NULL,
`count` int NOT NULL DEFAULT 0,
`source` text NOT NULL DEFAULT ('aggregate-telemetry'),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `plugin_install_counts_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_plugin_install_counts_plugin_registry` UNIQUE(`plugin_id`,`registry_id`)
);
--> statement-breakpoint
CREATE TABLE `plugin_permission_grants` (
`id` int AUTO_INCREMENT NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`capability` varchar(255) NOT NULL,
`granted_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`granted_by` varchar(255) NOT NULL,
CONSTRAINT `plugin_permission_grants_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_plugin_permission_grants_plugin_capability` UNIQUE(`plugin_id`,`capability`)
);
--> statement-breakpoint
CREATE TABLE `plugin_registries` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`url` text NOT NULL,
`kind` text NOT NULL DEFAULT ('community'),
`enabled` boolean NOT NULL DEFAULT true,
`signing_key` text,
`last_checked_at` text,
`last_index_hash` text,
CONSTRAINT `plugin_registries_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `plugins` (
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`version` text NOT NULL,
`tier` text NOT NULL DEFAULT ('available'),
`source` text NOT NULL DEFAULT ('community'),
`registry_id` varchar(255),
`state` text NOT NULL DEFAULT ('disabled'),
`installed_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
`auto_update` boolean NOT NULL DEFAULT false,
`manifest_json` text NOT NULL,
CONSTRAINT `plugins_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
ALTER TABLE `plugin_permission_grants` ADD CONSTRAINT `plugin_permission_grants_plugin_id_plugins_id_fk` FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `plugin_permission_grants` ADD CONSTRAINT `plugin_permission_grants_granted_by_users_id_fk` FOREIGN KEY (`granted_by`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_plugins_registry_id` ON `plugins` (`registry_id`);
-1
View File
@@ -1 +0,0 @@
ALTER TABLE `user_preferences` ADD `show_pin_app_rail_button` boolean;
-11
View File
@@ -1,11 +0,0 @@
CREATE TABLE `plugin_storage` (
`id` int AUTO_INCREMENT NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`key` varchar(255) NOT NULL,
`value` text NOT NULL,
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `plugin_storage_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_plugin_storage_plugin_key` UNIQUE(`plugin_id`,`key`)
);
--> statement-breakpoint
ALTER TABLE `plugin_storage` ADD CONSTRAINT `plugin_storage_plugin_id_plugins_id_fk` FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON DELETE cascade ON UPDATE no action;
-3
View File
@@ -1,3 +0,0 @@
DROP TABLE `alert_firings`;--> statement-breakpoint
DROP TABLE `alert_rule_channels`;--> statement-breakpoint
DROP TABLE `alert_rules`;
-3
View File
@@ -1,3 +0,0 @@
ALTER TABLE `plugin_permission_grants` MODIFY COLUMN `granted_by` varchar(255);--> statement-breakpoint
ALTER TABLE `plugin_permission_grants` ADD `source` text DEFAULT ('admin') NOT NULL;--> statement-breakpoint
ALTER TABLE `plugins` ADD `last_error` text;
@@ -1,11 +0,0 @@
CREATE TABLE `plugin_migrations` (
`id` int AUTO_INCREMENT NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`migration_id` varchar(255) NOT NULL,
`checksum` text NOT NULL,
`applied_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `plugin_migrations_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_plugin_migrations_plugin_migration` UNIQUE(`plugin_id`,`migration_id`)
);
--> statement-breakpoint
ALTER TABLE `plugin_migrations` ADD CONSTRAINT `plugin_migrations_plugin_id_plugins_id_fk` FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON DELETE cascade ON UPDATE no action;
-17
View File
@@ -1,17 +0,0 @@
CREATE TABLE `rbac_applied_defaults` (
`id` int AUTO_INCREMENT NOT NULL,
`role_name` varchar(255) NOT NULL,
`permission` varchar(255) NOT NULL,
`applied_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `rbac_applied_defaults_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_rbac_applied_defaults_role_permission` UNIQUE(`role_name`,`permission`)
);
--> statement-breakpoint
CREATE TABLE `rbac_known_permissions` (
`id` int AUTO_INCREMENT NOT NULL,
`permission` varchar(255) NOT NULL,
`plugin_id` varchar(255),
`first_seen_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `rbac_known_permissions_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_rbac_known_permissions_permission` UNIQUE(`permission`)
);
@@ -1,15 +0,0 @@
CREATE TABLE `plugin_settings` (
`id` int AUTO_INCREMENT NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`scope` enum('admin','user','host','secret') NOT NULL,
`scope_id` varchar(255),
`key` varchar(255) NOT NULL,
`value` text,
`encrypted` boolean NOT NULL DEFAULT false,
`updated_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `plugin_settings_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_plugin_settings_scope_key` UNIQUE(`plugin_id`,`scope`,`scope_id`,`key`)
);
--> statement-breakpoint
ALTER TABLE `plugin_settings` ADD CONSTRAINT `plugin_settings_plugin_id_plugins_id_fk` FOREIGN KEY (`plugin_id`) REFERENCES `plugins`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_plugin_settings_plugin_scope` ON `plugin_settings` (`plugin_id`,`scope`);
-24
View File
@@ -1,24 +0,0 @@
CREATE TABLE `user_external_identities` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`provider_id` varchar(255) NOT NULL,
`subject` varchar(255) NOT NULL,
`email` text,
`created_at` varchar(255) NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `user_external_identities_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_user_external_identities_provider_subject` UNIQUE(`provider_id`,`subject`)
);
--> statement-breakpoint
CREATE TABLE `user_second_factors` (
`id` int AUTO_INCREMENT NOT NULL,
`user_id` varchar(255) NOT NULL,
`plugin_id` varchar(255) NOT NULL,
`factor_id` varchar(255) NOT NULL,
`enrolled_at` text NOT NULL DEFAULT (CURRENT_TIMESTAMP),
CONSTRAINT `user_second_factors_id` PRIMARY KEY(`id`),
CONSTRAINT `idx_user_second_factors_user_factor` UNIQUE(`user_id`,`plugin_id`,`factor_id`)
);
--> statement-breakpoint
ALTER TABLE `user_external_identities` ADD CONSTRAINT `user_external_identities_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `user_second_factors` ADD CONSTRAINT `user_second_factors_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `idx_user_external_identities_user` ON `user_external_identities` (`user_id`);
@@ -1,5 +0,0 @@
-- user_workspaces moved to the workspaces plugin, whose first migration renames it
-- to p_workspaces_workspaces. Dropping it here would lose every saved workspace,
-- so this only stops drizzle tracking it. SELECT 1 because MySQL rejects an
-- empty query.
SELECT 1;
-4
View File
@@ -1,4 +0,0 @@
-- network_topology moved to the network-topology plugin, which adopts (renames)
-- this table in its own migration before this drizzle migration would run.
-- No DROP here: it would delete every row before the plugin's rename applies.
SELECT 1;
-4
View File
@@ -1,4 +0,0 @@
-- snippets, snippet_folders and snippet_access moved to the snippets plugin
-- (p_snippets_*), adopted by rename rather than dropped. A real DROP TABLE
-- here would run before the plugin's adoption migration and delete every row.
SELECT 1;
-2
View File
@@ -1,2 +0,0 @@
-- dropped, see plugin adoption (fleets plugin, migrations/mysql/0001_adopt_fleets.sql)
SELECT 1;
-6
View File
@@ -1,6 +0,0 @@
-- proxmox_node_history and proxmox_stats_preferences moved to the proxmox
-- plugin (p_proxmox_*), adopted by rename rather than dropped. The four
-- ssh_data proxmox columns are copied into the plugin's host settings after
-- plugins load, which is after this migration, so a real DROP COLUMN here
-- would delete the data first. The columns stay in place, unused, until 3.0.0.
SELECT 1;
-5
View File
@@ -1,5 +0,0 @@
-- file_manager_pinned, file_manager_recent, file_manager_shortcuts and
-- transfer_recent are adopted (renamed, not dropped) by the file-manager
-- plugin's own migration, which runs at plugin activation, after core's
-- drizzle migrations. A DROP TABLE here would run first and delete the data.
SELECT 1;
@@ -1,5 +0,0 @@
-- c2s_tunnel_presets moved to the tunnels plugin, whose first migration renames
-- it to p_tunnels_presets. Dropping it here would lose every saved preset, so
-- this only stops drizzle tracking it. SELECT 1 because MySQL rejects an
-- empty query.
SELECT 1;
-5
View File
@@ -1,5 +0,0 @@
-- command_history moved to the ssh-terminal plugin, whose first migration
-- renames it to p_ssh_terminal_command_history. Dropping it here would lose
-- every saved command, so this only stops drizzle tracking it. SELECT 1
-- because MySQL rejects an empty query.
SELECT 1;
-5
View File
@@ -1,5 +0,0 @@
-- tmux_session_tags moved to the tmux-monitor plugin, whose first migration
-- renames it to p_tmux_monitor_session_tags. Dropping it here would lose
-- every saved tag, so this only stops drizzle tracking it. SELECT 1 because
-- MySQL rejects an empty query.
SELECT 1;
-6
View File
@@ -1,6 +0,0 @@
-- session_shares, session_share_participants, collab_rooms and
-- collab_room_members moved to the session-sharing plugin, whose first
-- migration renames them. Dropping them here would lose every share and
-- room, so this only stops drizzle tracking them. SELECT 1 because MySQL
-- rejects an empty query.
SELECT 1;
-5
View File
@@ -1,5 +0,0 @@
-- session_recordings moved to the session-recording plugin, whose first
-- migration renames it. Dropping it here would lose every recording, so
-- this only stops drizzle tracking it. SELECT 1 because MySQL rejects an
-- empty query.
SELECT 1;
@@ -1,7 +0,0 @@
-- The remote desktop host options (enable_rdp, enable_vnc, enable_telnet,
-- the three ports, rdp_security, rdp_ignore_cert, security, ignore_cert,
-- guacamole_config) and user_preferences.rdp_defaults moved to the
-- remote-desktop plugin's settings. The copy runs after plugins load, which
-- is after this migration, so a real DROP COLUMN here would delete the data
-- first. The columns stay in place, unused, until 3.0.0.
SELECT 1;
@@ -1,9 +0,0 @@
-- host_metrics_preferences, host_health_checks, host_health_history and
-- host_metrics_history moved to the host-metrics plugin, which adopts them by
-- rename in its own first migration. That runs after this one, so a real
-- DROP TABLE here would delete every row first.
SELECT 1;--> statement-breakpoint
-- Status checks are core: the per-host switch and interval, copied out of
-- stats_config at boot by host-status-config-migration.ts.
ALTER TABLE `ssh_data` ADD `status_check_enabled` boolean DEFAULT true NOT NULL;--> statement-breakpoint
ALTER TABLE `ssh_data` ADD `status_check_interval` int;
@@ -1,6 +0,0 @@
-- ssh_data.stats_config split: its status half moved to status_check_enabled
-- and status_check_interval, its metrics half to the host-metrics plugin's
-- host settings. Both copies run at boot, after this migration, so a real
-- DROP COLUMN here would delete the data first. The column stays in place,
-- unused, until 3.0.0.
SELECT 1;
-5
View File
@@ -1,5 +0,0 @@
-- ssh_data.enable_docker and docker_config moved to the docker plugin's host
-- settings. The copy runs at boot, after this migration, so a real DROP
-- COLUMN here would delete the data first. The columns stay in place,
-- unused, until 3.0.0.
SELECT 1;
-4
View File
@@ -1,4 +0,0 @@
-- The automation tables moved to the automations plugin, which renames them
-- into its own namespace at activation. That runs after this migration, so a
-- real DROP TABLE here would delete every automation first.
SELECT 1;
-8
View File
@@ -1,8 +0,0 @@
-- The ai tables moved to the ai plugin, which renames them into its own
-- namespace at activation, and ssh_data.enable_ai_assistant and the two
-- user_preferences ai columns moved to its settings. Both run after this
-- migration, so a real DROP here would delete the data first. The columns
-- stay in place, unused, until 3.0.0. The timestamp and label type changes
-- drizzle-kit also wrote are generator drift (see FINISH-LIST.md), not a
-- change to the database, and are left out on purpose.
SELECT 1;
-6
View File
@@ -1,6 +0,0 @@
-- dashboard_service_links, homepage_items and homepage_layouts moved to the
-- homepage plugin, which adopts them by rename at activation (see
-- plugins/homepage/migrations/mysql/0001_adopt_homepage_tables.sql).
-- Dropping them here would delete every row before the plugin's migration
-- ever runs, since this runs at boot before the plugins table exists.
SELECT 1;
-4
View File
@@ -1,4 +0,0 @@
-- mac_address and wol_broadcast_address move to the wake-on-lan plugin's
-- host settings at boot, after this migration runs. Dropping them here would
-- delete the data before the copy has a chance to run.
SELECT 1;
Loaded 100 of 1982 files, more files were not shown because too many files have changed in this diff. Show more